tmux-weblink 2.2.26 → 2.2.28
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.
- package/dist/assets/shell-client.js +22 -21
- package/dist/browser/shell-client.js +14 -13
- package/dist/lib/agent-channel.js +1 -1
- package/dist/lib/agent-client.js +1 -1
- package/dist/lib/agent-relay.js +3 -3
- package/dist/lib/build-app.js +6 -6
- package/dist/lib/pages/shell.js +22 -1
- package/package.json +1 -1
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}selectRows(t,s){let n=this.terminal.buffer.active,[
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function
|
|
4
|
-
\x1B[31m`+fe(
|
|
5
|
-
`).then(()=>{Me();try{
|
|
1
|
+
var bn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function et(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function fe(e){return e.replace(bn,"")}var He;function En(){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 Ln=class Kt{terminal;fitAddon;container;constructor(t,s,n){this.container=t,this.terminal=s,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,s,n){let[{Terminal:i},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),En()]),c=new i({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:s,convertEol:!1,smoothScrollDuration:120,theme:n});return new Kt(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(s=>this.terminal.write(t,s))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,s=!1){this.terminal.scrollToLine(Math.max(0,t),s)}scrollLines(t){let s=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=xn(this.container);if(t.width<=0||t.height<=0)return!1;let s=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!s?.width||!s?.height)return this.fitAddon.fit(),!0;let n=s.width,i=s.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-n/2)/n)),u=Math.max(2,Math.floor(t.height/i));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,s){let n=this.terminal.buffer.active,[i,r]=t<=s?[t,s]:[s,t],c=n.viewportY,o=[];for(let u=i;u<=r;u++){let m=n.getLine(c+u);o.push(m?m.translateToString(!0):"")}return o.join(`
|
|
2
|
+
`)}selectRows(t,s){let n=this.terminal.buffer.active,[i,r]=t<=s?[t,s]:[s,t];this.terminal.selectLines(n.viewportY+i,n.viewportY+r)}selectCells(t,s,n,i){let r=this.terminal.buffer.active,c=Math.min(s,i),o=Math.max(s,i);if(c===o){let u=Math.min(t,n),m=Math.max(t,n);this.terminal.select(u,r.viewportY+c,m-u+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+o)}getCellsText(t,s,n,i){let r=this.terminal.buffer.active,c=Math.min(s,i),o=Math.max(s,i),u=s<i?t:s>i?n:Math.min(t,n),m=s<i?n:s>i?t:Math.max(t,n),B=r.viewportY,L=[];for(let F=c;F<=o;F++){let I=r.getLine(B+F);if(!I){L.push("");continue}let b=F===c?u:0,O=F===o?m:this.terminal.cols-1,ie="";for(let ke=b;ke<=O;ke++){let W=I.getCell(ke);W&&(ie+=W.getChars())}L.push(ie.replace(/\s+$/,""))}return L.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function xn(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function Cn(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let n=await Ln.create(e,s.scrollback,s.theme),i,r=0,c,o=null,u=0,m=0,B=.2,L=10,F=.95,I=.04,b=null,O=null;function ie(a){b||(b=document.createElement("div"),b.style.cssText=["position:absolute","left:0","width:40%","padding:10px 0","text-align:center","font:600 14px/1.4 system-ui, -apple-system, sans-serif","color:rgba(255,255,255,0.95)","background:rgba(20,30,50,0.55)","border:1px solid rgba(255,255,255,0.25)","border-radius:10px","pointer-events:none","opacity:0","transition:opacity 0.15s ease","z-index:6"].join(";"),e.appendChild(b)),b.textContent=a?"\u2191 \u4E0A\u7FFB\u4E00\u9875 (PgUp)":"\u2193 \u4E0B\u7FFB\u4E00\u9875 (PgDn)",b.style.top=a?"10%":"auto",b.style.bottom=a?"auto":"10%",b.style.opacity="1",O&&clearTimeout(O),O=setTimeout(()=>{b&&(b.style.opacity="0")},900)}function ke(){O&&clearTimeout(O),b&&(b.style.opacity="0")}function W(){u&&(cancelAnimationFrame(u),u=0),m=0}function Vt(a){if(W(),Math.abs(a)<B)return;m=Math.max(-L,Math.min(L,a));let l=()=>{if(_){u=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,v=p>0?Math.round(m*16/p):m>0?1:-1;v===0&&(v=m>0?1:-1);let A=n.viewportY()-v,X=n.baseY(),me=Math.max(0,Math.min(A,X));if(n.scrollToLine(me,!0),m*=F,me!==A){u=0;return}if(Math.abs(m)<I){u=0;return}u=requestAnimationFrame(l)};u=requestAnimationFrame(l)}let Se=0,$="connecting",Be=0,oe=!1,Te=[],Q="",_=!1,le=1e3,Xt=location.protocol==="https:"?"wss:":"ws:",dt=s.wsBase||"/ws",Jt=Xt+"//"+location.host+dt+"/"+encodeURIComponent(t),Zt=(dt.replace(/^\/ws/,"")||"")+"/api/session/"+encodeURIComponent(t)+"/upload",Gt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Qt(){return Te.join("")+Q}async function en(a,l=0){let d=a?n.viewportY():0,p=fe(Qt());if(n.reset(),!p){a||n.scrollToBottom();return}await n.write(p),a?n.scrollToLine(l+d):n.scrollToBottom()}function K(a){i?.readyState===WebSocket.OPEN&&i.send(JSON.stringify(a))}function tn(a){let l;try{l=JSON.parse(a)}catch{$==="live"&&(Q+=a,n.write(fe(a)));return}if(l.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?K({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(l.type==="attach_failed"&&typeof l.message=="string"){Ie=!0,$="dead",n.reset(),n.write(`\r
|
|
4
|
+
\x1B[31m`+fe(l.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{Me();try{i?.close()}catch{}});return}if(l.type==="auth.ok"){l.token&&localStorage.setItem("tmux-web-token",l.token),le=1e3,V(),K({type:"resize",cols:n.cols,rows:n.rows}),T();return}if(l.type==="auth.failed"){if(l.permanentLock||l.retryAfterMs&&l.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(l.message);return}return}if(l.type==="snapshot"&&typeof l.data=="string"){let d=et(fe(l.data));Te=[d],Q="",Be=typeof l.lines=="number"?l.lines:s.terminal.initialLines,$="live",n.reset(),V(),K({type:"resize",cols:n.cols,rows:n.rows}),n.write(d).then(()=>n.scrollToBottom());return}if(l.type==="window_changed"&&typeof l.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(l.activeIndex)&&(d.searchParams.set("window",String(l.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:l.windows}));return}if(l.type==="history"&&typeof l.data=="string"){l.lines>0&&l.data?(Te.unshift(et(l.data)),Be+=l.lines,en(!0,l.lines).finally(()=>{oe=!1})):oe=!1;return}if(l.type==="data"&&typeof l.data=="string"){let d=et(fe(l.data));if($==="connecting"){$="live",Q=d,n.write(d);return}$==="live"&&(Q+=d,n.write(d))}}let Ie=!1;function ut(){if(Ie)return;let a=Jt,l=localStorage.getItem("tmux-web-token");l&&(a+=(a.includes("?")?"&":"?")+"token="+encodeURIComponent(l)),a+=(a.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,i=new WebSocket(a),i.onopen=()=>{$="connecting",Be=0,oe=!1,Te=[],Q="",n.reset(),le=1e3},i.onmessage=d=>{typeof d.data=="string"&&tn(d.data)},i.onclose=()=>{_||($="connecting",!Ie&&setTimeout(()=>{le=Math.min(le*2,1e4),!_&&!Ie&&ut()},le))},i.onerror=()=>i?.close()}function ee(a){K({type:"input",data:a})}function V(){return n.fit()}function Me(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),V(),r=requestAnimationFrame(()=>{r=0,V()&&Me()}),c=setTimeout(()=>{V()&&Me()},120)}function nn(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function pt(a){return typeof a=="string"&&a.startsWith("image/")}function mt(a){return a?.files?.length?a.files[0]??null:null}function sn(a){if(!a?.items)return null;for(let l=0;l<a.items.length;l++){let d=a.items[l];if(d?.kind==="file"&&pt(d.type))return d.getAsFile()}return null}async function an(a){let l=new FormData,d=a instanceof File&&a.name?a.name:"upload";l.append("file",a,d);let p=localStorage.getItem("tmux-web-token"),v={};p&&(v.Authorization="Bearer "+p);let A=await fetch(Zt,{method:"POST",body:l,headers:v});if(!A.ok){let me="upload failed";try{let bt=await A.json();bt.error&&(me=bt.error)}catch{}throw new Error(me)}let X=await A.json();if(!X.path)throw new Error("no path in response");return X.path}async function Ze(a){try{let l=await an(a);ee(l)}catch{}}n.onData(a=>ee(a)),n.onResize(({cols:a,rows:l})=>K({type:"resize",cols:a,rows:l})),n.onScroll(()=>{$!=="live"||oe||!n.isNearScrollbackTop()||(oe=!0,K({type:"load_history",before:Be}))});let re=!1,ce=!1,E=null,x=null,de=document.createElement("div");de.className="terminal-copy-banner",de.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',de.hidden=!0,e.appendChild(de);let z=document.createElement("div");z.className="terminal-copy-banner terminal-copy-toast",z.textContent="Copied",z.hidden=!0,e.appendChild(z);function ft(a,l){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),v=p.height>0&&n.rows>0?p.height/n.rows:18,A=p.width>0&&n.cols>0?p.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((l-p.top)/v))),col:Math.max(0,Math.min(n.cols-1,Math.floor((a-p.left)/A)))}}async function De(a){if(!a)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(a);return}}catch{}let l=document.createElement("textarea");l.value=a,l.style.position="fixed",l.style.opacity="0",l.style.pointerEvents="none",document.body.appendChild(l),l.focus(),l.select();try{document.execCommand("copy")}catch{}l.remove()}let ue;function Fe(){z.hidden=!1,ue&&clearTimeout(ue),ue=setTimeout(()=>{z.hidden=!0},1e3)}function te(a){re=a,de.hidden=!a,e.classList.toggle("terminal-copy-mode-active",a),a&&n.focus()}function ht(){te(!re)}function on(a){_||a.button!==0||ce||(re||a.shiftKey)&&(a.preventDefault(),a.stopPropagation(),ce=!0,E=ft(a.clientX,a.clientY),x=E,n.selectCells(E.col,E.row,x.col,x.row),document.addEventListener("mousemove",Ge,!0),document.addEventListener("mouseup",Qe,!0))}function Ge(a){_||!ce||!E||(a.preventDefault(),a.stopPropagation(),x=ft(a.clientX,a.clientY),n.selectCells(E.col,E.row,x.col,x.row))}function Qe(a){if(_||!ce||!E||!x)return;a.preventDefault(),a.stopPropagation(),document.removeEventListener("mousemove",Ge,!0),document.removeEventListener("mouseup",Qe,!0),ce=!1;let l=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),te(!1),l&&(z.textContent="\u5DF2\u590D\u5236",De(l).then(Fe)),n.focus()}e.addEventListener("mousedown",on,!0),document.addEventListener("keydown",ln,!0);function ln(a){if(!(_||!n.isFocused()||a.defaultPrevented||a.isComposing)){if(re){if(a.key==="Escape"){a.preventDefault(),a.stopPropagation(),E=null,x=null,n.clearSelection(),te(!1);return}if(a.key==="Enter"||(a.ctrlKey||a.metaKey)&&a.code==="KeyC"){a.preventDefault(),a.stopPropagation(),De(n.getScreenText()).then(Fe),te(!1);return}}if(a.altKey&&a.code==="KeyC"&&!a.ctrlKey&&!a.metaKey){if(a.preventDefault(),a.stopPropagation(),E&&x){let l=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),te(!1),l&&(z.textContent="Copied",De(l).then(Fe))}else{let l=n.getSelection();l?(n.clearSelection(),z.textContent="Copied",De(l).then(Fe)):ht()}return}if((a.ctrlKey||a.metaKey)&&a.code==="KeyV"){a.preventDefault(),a.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let l=await navigator.clipboard.read();for(let d of l)for(let p of d.types)if(pt(p)){let v=await d.getType(p);await Ze(v);return}}}catch{}try{let l=await navigator.clipboard?.readText();l&&n.pasteText(vt(l))}catch{}})();return}}}let Ae=0;function vt(a){return a.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
9
|
-
<span>${h(e.name)}</span>${s?"":'<span class="meta">offline</span>'}
|
|
8
|
+
`).join("\r")}async function rn(a){let l=sn(a.clipboardData);if(l){a.preventDefault(),a.stopPropagation();let v=Date.now();if(v-Ae<50)return;Ae=v,await Ze(l);return}let d=a.clipboardData?.getData("text/plain");if(!d)return;a.preventDefault(),a.stopPropagation();let p=Date.now();p-Ae<50||(Ae=p,n.pasteText(vt(d)))}let pe=0;function cn(a){a.preventDefault(),pe++,mt(a.dataTransfer)&&e.classList.add("terminal-drag-over")}function dn(a){a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="copy")}function un(a){a.preventDefault(),pe--,pe<=0&&(pe=0,e.classList.remove("terminal-drag-over"))}async function pn(a){a.preventDefault(),pe=0,e.classList.remove("terminal-drag-over");let l=mt(a.dataTransfer);l&&await Ze(l)}function gt(a){!Gt||a.key!=="Escape"||re||a.defaultPrevented||a.repeat||a.metaKey||a.ctrlKey||a.altKey||n.isFocused()&&(a.preventDefault(),a.stopPropagation(),ee("\x1B"))}e.addEventListener("paste",rn),e.addEventListener("dragenter",cn),e.addEventListener("dragover",dn),e.addEventListener("dragleave",un),e.addEventListener("drop",pn),document.addEventListener("keydown",gt,!0);function mn(a){if(a.touches.length!==1){o=null;return}let l=a.touches[0];if(!l)return;W();let d=e.getBoundingClientRect(),p=l.clientX-d.left<d.width*.4?"tui":"history",v=l.clientY-d.top<d.height*.5;o={startX:l.clientX,startY:l.clientY,lastX:l.clientX,lastY:l.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:p,tuiUp:v,sent:!1},a.stopPropagation()}function fn(a){if(!o||a.touches.length!==1)return;let l=a.touches[0];if(!l)return;let d=l.clientY-o.startY,p=l.clientX-o.startX;if(!o.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(p))return;o.scrolling=!0,Se=Date.now()+500}a.preventDefault(),a.stopPropagation();let v=performance.now(),A=l.clientY-o.lastY;if(o.zone==="tui"){o.sent||(o.sent=!0,ee(o.tuiUp?"\x1B[5~":"\x1B[6~"),ie(o.tuiUp)),o.lastX=l.clientX,o.lastY=l.clientY,o.lastT=v;return}let X=v-o.lastT;X>0&&(o.velocity=A/X),yn(-A),o.lastX=l.clientX,o.lastY=l.clientY,o.lastT=v}function hn(a){if(!o)return;let l=o.scrolling,d=o.zone,p=o.tuiUp,v=o.velocity;if(o=null,a.stopPropagation(),d==="tui"){l||(ee(p?"\x1B[5~":"\x1B[6~"),ie(p),Se=Date.now()+500,a.preventDefault());return}if(!l){n.focus();return}Se=Date.now()+500,a.preventDefault(),v&&Vt(v)}function vn(a){o=null,W(),a.stopPropagation()}function gn(a){a.pointerType==="touch"&&Date.now()<Se&&(a.preventDefault(),a.stopPropagation())}function yn(a){let l=e.querySelector(".xterm-rows > div"),d=l?l.getBoundingClientRect().height:16,p=d>0?Math.round(a/d):a>0?1:-1;p===0&&(p=a>0?1:-1),n.scrollLines(p)}e.addEventListener("touchstart",mn,{passive:!0,capture:!0}),e.addEventListener("touchmove",fn,{passive:!1,capture:!0}),e.addEventListener("touchend",hn,{passive:!1,capture:!0}),e.addEventListener("touchcancel",vn,{passive:!0,capture:!0}),e.addEventListener("pointerup",gn,!0);function yt(){T()}window.addEventListener("resize",yt),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let wt=new ResizeObserver(()=>T());wt.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",nn,!0);async function wn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(a=>setTimeout(a,1e3))])}catch{}}return requestAnimationFrame(()=>{V()&&Me(),(async()=>(await wn(),V()&&K({type:"resize",cols:n.cols,rows:n.rows}),ut(),T()))()}),{destroy(){if(_=!0,W(),te(!1),ue&&clearTimeout(ue),document.removeEventListener("mousemove",Ge,!0),document.removeEventListener("mouseup",Qe,!0),i){try{i.close(1e3,"session switch")}catch{}i=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",yt),wt.disconnect(),document.removeEventListener("keydown",gt,!0)},focus(){n.focus()},sendInput(a){ee(a)},toggleCopyMode(){ht()}}})()}var he=window.__TMUX_WEB_SHELL__,g=null,y=null,ve=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,C=ve;window.__tmuxWebScopeAgent=ve;var tt="sessions",Y="",Ne="",Re="",Et="";function k(e){return C?"/a/"+encodeURIComponent(C)+e:e}var f=document.getElementById("sidebar-content"),J=document.getElementById("main-placeholder"),M=document.getElementById("terminal-container"),ge=document.getElementById("file-editor"),kn=document.getElementById("fe-path"),H=document.getElementById("fe-status"),ye=document.getElementById("fe-content"),Z=document.getElementById("fe-save"),Sn=document.getElementById("fe-delete"),Bn=document.getElementById("fe-back"),Lt=document.getElementById("fe-new-name"),Tn=document.getElementById("fe-new-btn"),nt=document.getElementById("settings-backdrop"),xt=document.getElementById("settings-popover"),Ct=document.querySelector(".sidebar"),kt="tmux-web-force-narrow";function Pe(){try{return localStorage.getItem(kt)==="1"}catch{return!1}}function St(){document.documentElement.classList.toggle("force-narrow",Pe()),Pe()&&Bt()&&$e()}function Bt(){return Pe()?!0:window.matchMedia("(max-width: 640px)").matches}function $e(){Bt()&&Ct.classList.add("collapsed")}function Tt(){Ct.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Tt(),It("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Tt(),It("files")});function It(e){tt=e,document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(st="",j()):_n()}var st="";async function j(){if(tt!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===st)return;st=s,An(t)}catch{f.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")}),ct()}var Mt="tmux-web-sidebar-group-collapsed";function Dt(){try{return new Set(JSON.parse(localStorage.getItem(Mt)||"[]"))}catch{return new Set}}function In(e){try{localStorage.setItem(Mt,JSON.stringify([...e]))}catch{}}function Mn(e,t,s){if(!e)return"\u2026";let n=s&&(e===s||e.startsWith(s+"/"))?"~"+e.slice(s.length):e,i=n.split("/").filter(Boolean);if(i.length===0)return e;for(let r=1;r<=i.length;r++){let c=i.slice(-r).join("/");if(!t.some(o=>!o||o===e?!1:(s&&(o===s||o.startsWith(s+"/"))?"~"+o.slice(s.length):o).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return n}function Ft(e,t,s,n=!0){let i=new Map;for(let o of e){let u=o.path||"";i.has(u)||i.set(u,[]),i.get(u).push(o)}let r=[...i.keys()],c=Dt();for(let[o,u]of i){let m=Mn(o,r,t),B=c.has(o),L=document.createElement("div");L.className="session-group-header"+(B?" collapsed":""),L.title=o||"No working directory",L.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${h(m)}</span>`,L.addEventListener("click",()=>{let I=Dt(),b=L.nextElementSibling;I.has(o)?(I.delete(o),L.classList.remove("collapsed"),b?.classList.remove("collapsed")):(I.add(o),L.classList.add("collapsed"),b?.classList.add("collapsed")),In(I)});let F=document.createElement("div");F.className="session-group-body"+(B?" collapsed":"");for(let I of u)F.appendChild(s?Dn(I,s,n):Fn(I));f.appendChild(L),f.appendChild(F)}}function Dn(e,t,s){let n=document.createElement("div");n.className="session-item"+(e.name===y&&C===t?" active":"")+(s?"":" session-offline"),n.setAttribute("data-session",e.name),n.setAttribute("data-agent",t),n.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>${h(e.name)}</span>${s?"":'<span class="meta">offline</span>'}
|
|
10
|
+
<button class="session-edit-btn" data-session="${h(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>`,s&&n.addEventListener("click",r=>{r.target.closest(".session-edit-btn")||we(e.name,t)});let i=n.querySelector(".session-edit-btn");return i&&i.addEventListener("click",r=>{r.stopPropagation(),Ve(e.name,i,{agentId:t})}),n}function Fn(e){let t=document.createElement("div");t.className="session-item"+(e.name===y&&!C?" 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
11
|
<span>${h(e.name)}</span>
|
|
11
12
|
<button class="session-edit-btn" data-session="${h(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",n=>{n.target.closest(".session-edit-btn")||
|
|
13
|
+
`,t.addEventListener("click",n=>{n.target.closest(".session-edit-btn")||we(e.name)});let s=t.querySelector(".session-edit-btn");return s&&s.addEventListener("click",n=>{n.stopPropagation(),Ve(e.name,s)}),t}function An(e){f.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],s=document.createElement("div");s.className="sidebar-section-label"+(ve?" clickable":""),s.textContent=ve?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",ve&&(s.title="Back to hub",s.addEventListener("click",()=>{window.location.href="/"})),f.appendChild(s),Ft(t,e.home);let n=e.agents||[];for(let r of n){if(!r.sessions||r.sessions.length===0)continue;let c=r.agentId===C,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)+"/"})),f.appendChild(o),Ft(r.sessions,e.home,r.agentId,r.online)}let i=e.tombstones||[];if(i.length>0){let r=document.createElement("div");r.className="sidebar-section-label tombstone-label",r.textContent="\u5931\u6548\u4F1A\u8BDD",f.appendChild(r);for(let c of i){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
14
|
<span>${h(c.name)}</span><span class="meta">\u5931\u6548${u}</span>
|
|
14
|
-
<button class="session-edit-btn" data-session="${h(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>`,
|
|
15
|
+
<button class="session-edit-btn" data-session="${h(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>`,o.title="\u5931\u6548\u4F1A\u8BDD\uFF1A\u70B9\u51FB\u7BA1\u7406\uFF08\u91CD\u5EFA/\u91CD\u547D\u540D/\u5220\u9664\uFF09",o.addEventListener("click",()=>{Ve(c.name,o,{tombstone:!0,agentId:c.agentId})});let m=o.querySelector(".session-edit-btn");m&&m.addEventListener("click",B=>{B.stopPropagation(),Ve(c.name,m,{tombstone:!0,agentId:c.agentId})}),f.appendChild(o)}}}function h(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function we(e,t){if(y===e&&g&&C===(t||null))return;ge.style.display="none",J.style.display="none",M.style.display="",M.classList.add("terminal-pending"),$e(),g&&(We(),g.destroy(),g=null),y=e,C=t||null,window.__tmuxWebScopeAgent=C,window.__tmuxWebCurrentSession=y,ts(e,t);let s=rt(e,t);Ot.set(s,Date.now()+Qn);let n=ae.get(s);if(n&&n.state==="working"){n.state="idle",n.doneUntil=void 0,n.unread=!1;let i=f.querySelector(t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');i&&Je(i,s,n)}for(let i of f.querySelectorAll(".session-item"))i.classList.toggle("active",i.getAttribute("data-session")===e&&(i.getAttribute("data-agent")||null)===C);try{g=await Cn(M,e,{terminal:he.terminal,scrollback:he.scrollback,theme:he.theme,wsBase:C?"/ws/a/"+encodeURIComponent(C):he.wsBase||"/ws"}),_t()}catch(i){console.error("[shell] terminal init failed:",i);let r=i&&typeof i=="object"&&"message"in i?i.message:String(i);M.textContent="Failed to open terminal for "+e+": "+r,M.classList.remove("terminal-pending")}}var ze=[],w=null,At=!1;function Hn(e){if(!w||!w.files.length)return null;let t=w.repoRoot;if(!t||!e.startsWith(t))return null;let s=e.slice(t.length+1);for(let n of w.files)if(n.path===s)return n.status;return null}async function Nn(e){At=!0;try{let t=await fetch(k("/api/git/status?path="+encodeURIComponent(e)));t.ok?w=await t.json():w=null}catch{w=null}finally{At=!1}}function Rn(){if(!w||!w.branch)return"";let{branch:e,linesAdded:t,linesRemoved:s}=w,n=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
15
16
|
<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
|
-
${h(e)}</div>`;return(t>0||s>0)&&(n+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),n+"</div>"}
|
|
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 s=0;s<e.length;s++){let n=e[s],
|
|
17
|
+
${h(e)}</div>`;return(t>0||s>0)&&(n+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),n+"</div>"}f.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&ne&&w&&(e.stopPropagation(),$t())});var be=document.getElementById("header-git"),Ht=document.getElementById("git-diff-view"),Nt=document.getElementById("gd-path"),je=document.getElementById("gd-status"),Ue=document.getElementById("gd-content"),Pn=document.getElementById("gd-back"),ne=null;function Ee(e){if(!e||!e.branch){be.style.display="none",ne=null;return}ne=e.repoRoot;let{branch:t,linesAdded:s,linesRemoved:n}=e,i=`<span class="branch">${h(t)}</span>`;(s>0||n>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${s}</span> <span class="diff-del">-${n}</span>`),be.innerHTML=i,be.style.display="block"}be.addEventListener("click",e=>{ne&&w&&(e.stopPropagation(),$t())});var S=document.getElementById("git-popover"),_e=document.getElementById("git-popover-backdrop");function at(){let e=be.getBoundingClientRect();S.style.top=e.bottom+4+"px",S.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Rt(){let e=ne,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(qe(),e){tt="files",document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let s=document.getElementById("mode-files");s&&s.classList.add("active"),q(e)}})}var Pt="";function $t(){if(!w)return;let e=w.files||[];if(!e.length)S.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
18
|
+
<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 s=0;s<e.length;s++){let n=e[s],i=n.additions||0,r=n.deletions||0,c="";switch(n.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="${s}">
|
|
18
19
|
${c}<span class="file-path">${h(n.path)}</span>
|
|
19
|
-
<span class="file-add">${
|
|
20
|
+
<span class="file-add">${i>0?"+"+i:""}</span>
|
|
20
21
|
<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>',
|
|
22
|
+
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Pt=t,S.innerHTML=t,S.querySelectorAll(".git-popover-item").forEach(s=>{let n=parseInt(s.dataset.idx||"0",10),i=e[n];!i||i.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>jt(i.path)))})}at(),S.classList.add("open"),_e.classList.add("open"),Rt()}function $n(){return window.innerWidth>=1024}function zt(e,t){let s=(e+(t&&e?`
|
|
22
23
|
`:"")+t).split(`
|
|
23
|
-
`),n="",
|
|
24
|
+
`),n="",i=0,r=0;for(let c of s){let o=c;if(o.startsWith("@@")){let u=o.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(i=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),n+=`<div class="diff-line hunk"><span class="ln-body">${h(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")&&(n+=`<div class="diff-line header"><span class="ln-body">${h(o)}</span></div>`);continue}if(o.startsWith("+"))r++,n+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${h(o)}</span></div>`;else if(o.startsWith("-"))i++,n+=`<div class="diff-line del"><span class="ln-no">${i}</span><span class="ln-body">${h(o)}</span></div>`;else if(o.startsWith("\\"))n+=`<div class="diff-line"><span class="ln-body">${h(o)}</span></div>`;else{if(!o&&!i&&!r)continue;i++,r++,n+=`<div class="diff-line"><span class="ln-no">${i}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${h(o)}</span></div>`}}return{html:n,lineCount:s.length}}var Ye=null;async function jt(e){let t=ne;if(t)if(qe(),$n()){M.style.display="none",g&&(We(),g.destroy(),g=null,Ye=y,y=null),Ee(null),ge.style.display="none",J.style.display="none",Ht.style.display="flex",Nt.textContent=e,je.textContent="Loading\u2026",Ue.innerHTML="";try{let s=await fetch(k("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let n=await s.json();jn(e,n.diff,n.stagedDiff)}catch(s){let n=s instanceof Error?s.message:String(s);je.textContent="Error",Ue.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${h(n)}</div>`}}else{S.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
24
25
|
<button class="diff-back">\u2190 Back</button>
|
|
25
26
|
<span class="diff-filename">${h(e)}</span>
|
|
26
27
|
<span class="diff-loading">Loading\u2026</span>
|
|
27
|
-
</div></div>`,
|
|
28
|
+
</div></div>`,at(),S.classList.add("open"),_e.classList.add("open");try{let s=await fetch(k("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let n=await s.json();zn(e,n.diff,n.stagedDiff)}catch(s){let n=s instanceof Error?s.message:String(s);S.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
28
29
|
<button class="diff-back">\u2190 Back</button>
|
|
29
30
|
<span class="diff-filename">${h(e)}</span>
|
|
30
|
-
</div><div class="diff-empty">Error loading diff: ${h(n)}</div></div>`}}}function
|
|
31
|
+
</div><div class="diff-empty">Error loading diff: ${h(n)}</div></div>`}}}function zn(e,t,s){if(!t&&!s){S.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
31
32
|
<button class="diff-back">\u2190 Back</button>
|
|
32
33
|
<span class="diff-filename">${h(e)}</span>
|
|
33
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:n,lineCount:
|
|
34
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:n,lineCount:i}=zt(t,s);S.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
34
35
|
<button class="diff-back">\u2190 Back</button>
|
|
35
36
|
<span class="diff-filename">${h(e)}</span>
|
|
36
|
-
<span class="diff-filesize">${
|
|
37
|
-
</div><div class="diff-content">${n}</div></div>`,
|
|
37
|
+
<span class="diff-filesize">${i} lines</span>
|
|
38
|
+
</div><div class="diff-content">${n}</div></div>`,S.querySelector(".diff-back")?.addEventListener("click",()=>{S.innerHTML=Pt,at(),w&&S.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",()=>jt(o.path)))}),Rt()})}function jn(e,t,s){if(!t&&!s){je.textContent="No diff",Ue.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:n,lineCount:i}=zt(t,s);Nt.textContent=e,je.textContent=i+" lines",Ue.innerHTML=n}function Un(){Ht.style.display="none",Ye?(M.style.display="",M.classList.add("terminal-pending"),we(Ye),Ye=null):y?(M.style.display="",M.classList.add("terminal-pending"),_t()):J.style.display="flex"}Pn.addEventListener("click",Un);function qe(){S.classList.remove("open"),_e.classList.remove("open")}_e.addEventListener("click",qe),document.addEventListener("keydown",e=>{e.key==="Escape"&&(qe(),Xe())});async function Ut(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){let n=await s.json();w=n,Ee(n);return}}}catch{}w=null,Ee(null)}var Oe=null;function _t(){We(),y&&(Ut(y),Oe=setInterval(()=>{y&&Ut(y)},3e4))}function We(){Oe&&(clearInterval(Oe),Oe=null)}function _n(){if(ze=he.fsRoots||[],ze.length===0){if(y){Yn(y);return}f.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}f.innerHTML="";for(let e of ze){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> ${h(e)}`,t.addEventListener("click",()=>q(e)),f.appendChild(t)}}async function Yn(e){f.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(k("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?q(t.path):f.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{f.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function q(e){Y=e,f.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Nn(e);try{let s=await(await fetch(k("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,f.innerHTML="",Ee(w),f.innerHTML+=Rn();let n=qn(e);if(n&&!ze.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>q(n)),f.appendChild(r)}if(s.dirs)for(let r of s.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> ${h(c)}`,o.addEventListener("click",()=>q(r)),f.appendChild(o)}else f.innerHTML="";let i=s.files||[];for(let r of i){let c=r.split("/").pop()||r.split("\\").pop(),o=Hn(r),u="",m="";if(o)switch(o){case"M":u="git-mod",m='<span class="git-file-status M">M</span>';break;case"A":u="git-add",m='<span class="git-file-status A">A</span>';break;case"D":u="git-del",m='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",m='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",m='<span class="git-file-status R">R</span>';break}let B=document.createElement("div");B.className="file-tree-item file"+(u?" "+u:""),B.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${m}${h(c)}`,B.addEventListener("click",()=>On(r)),f.appendChild(B)}}catch{f.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function qn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function On(e){Ne=e,M.style.display="none",g&&(We(),g.destroy(),g=null,y=null),Ee(null),$e(),J.style.display="none",ge.style.display="flex",kn.textContent=e,H.textContent="Loading...",Z.style.display="none";try{let t=await fetch(k("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let n=await t.json();H.textContent=n.error||"Failed to load",ye.value="";return}let s=await t.json();ye.value=s.content,Re=s.content,Et=s.content,H.textContent=s.size+" bytes",Z.style.display=""}catch{H.textContent="Failed to load",ye.value=""}}ye.addEventListener("input",()=>{Re=ye.value}),Z.addEventListener("click",async()=>{Z.textContent="Saving...",Z.disabled=!0;try{let e=await fetch(k("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ne,content:Re})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Save failed";return}Et=Re,H.textContent="Saved"}catch{H.textContent="Save failed"}finally{Z.disabled=!1,Z.textContent="Save"}}),Sn.addEventListener("click",async()=>{if(confirm("Delete "+Ne+"?"))try{let e=await fetch(k("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ne})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Delete failed";return}ge.style.display="none",J.style.display="flex",Y&&q(Y)}catch{H.textContent="Delete failed"}}),Bn.addEventListener("click",()=>{ge.style.display="none",J.style.display="flex",Y&&q(Y)}),Tn.addEventListener("click",async()=>{let e=Lt.value.trim();if(!e)return;let t=Y+"/"+e;try{let s=await fetch(k("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!s.ok){let n=await s.json();H.textContent=n.error||"Create failed";return}Lt.value="",Y&&q(Y)}catch{H.textContent="Create failed"}});var Ke=document.getElementById("force-narrow-toggle");Ke&&(Ke.checked=Pe(),Ke.addEventListener("change",()=>{try{localStorage.setItem(kt,Ke.checked?"1":"0")}catch{}St(),j()})),St(),document.getElementById("mode-settings").addEventListener("click",()=>{let e=xt.classList.toggle("open");nt.classList.toggle("open",e)}),nt.addEventListener("click",()=>{xt.classList.remove("open"),nt.classList.remove("open")});var D="",U=!1,N,Le=document.createElement("div");Le.className="session-popover-backdrop",document.body.appendChild(Le);var R=document.createElement("div");R.id="session-popover",R.className="session-popover",R.style.display="none",R.innerHTML=`<div class="sp-field">
|
|
38
39
|
<label>Rename session</label>
|
|
39
40
|
<input type="text" id="sp-name" />
|
|
40
41
|
</div>
|
|
@@ -44,5 +45,5 @@ var hn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
|
|
|
44
45
|
</div>
|
|
45
46
|
<hr class="sp-divider" />
|
|
46
47
|
<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(
|
|
48
|
-
`).length;
|
|
48
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(R);function Wn(e){let t=e.getBoundingClientRect(),s=window.innerHeight,n=R.offsetHeight||240,i=t.bottom+4;i+n>s-8&&(i=Math.max(8,t.top-n-4)),R.style.top=i+"px",R.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Ve(e,t,s){D=e,U=!!s?.tombstone,N=s?.agentId;let n=document.getElementById("sp-name");n.value=e;let i=R.querySelector(".sp-field label");i.textContent=U?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=U?"":"none";let c=document.getElementById("sp-delete");c.textContent=U?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let o=document.getElementById("sp-save");o.textContent=U?"\u91CD\u547D\u540D":"Save",Le.classList.add("open"),R.style.display="block",Wn(t),setTimeout(()=>n.focus(),50)}function G(){Le.classList.remove("open"),R.style.display="none"}Le.addEventListener("click",G),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===D){G();return}try{let t=U?{oldName:D,newName:e,agentId:N}:{oldName:D,newName:e,...N?{agentId:N}:{}};if(!(await fetch(k(U?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;G(),j(),!U&&y===D&&(N??null)===C&&we(e,N)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+D+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{let e=await fetch(k("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:N})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}G(),j()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(U){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+D+'"\uFF1F'))return;try{if(!(await fetch(k("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:N})})).ok)return;G(),j()}catch{}return}if(confirm('Delete session "'+D+'"?'))try{if(!(await fetch(k("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,...N?{agentId:N}:{}})})).ok)return;G(),y===D&&(N??null)===C&&(g&&(g.destroy(),g=null),y=null,M.style.display="none",J.style.display="flex"),j()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",G);var Kn={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||!g)return;let s=Kn[t.dataset.key];s&&(g.sendInput(s),g.focus())});var P=document.getElementById("mk-input"),Yt=document.getElementById("mk-send");function qt(){if(!P||!g)return;let e=P.value;e&&(g.sendInput(e+"\r"),P.value="",P.rows=1,g.focus())}if(P&&(P.addEventListener("input",()=>{P.rows=1;let e=P.value.split(`
|
|
49
|
+
`).length;P.rows=Math.min(e,4)}),P.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),qt())})),Yt&&Yt.addEventListener("click",qt),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 it=document.getElementById("process-panel"),xe=document.getElementById("proc-list"),Vn=document.getElementById("proc-close"),ot=document.getElementById("proc-backdrop"),Xn=document.getElementById("header-ram");function Jn(){xe.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(k("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){xe.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let s of e){let n=s.rss,i=n<1048576?(n/1024).toFixed(0)+"K":(n/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+s.pid+'"><span class="proc-mem">'+s.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+h(s.command)+'">'+h(s.command)+'</span><button class="proc-kill" title="Kill PID '+s.pid+'">×</button></div>'}xe.innerHTML=t;for(let s of xe.querySelectorAll(".proc-row")){let n=s.querySelector(".proc-kill");n&&n.addEventListener("click",i=>{let r=i.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(k("/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(()=>{xe.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Zn(){it.classList.add("open"),ot.classList.add("open"),Jn()}function Xe(){it.classList.remove("open"),ot.classList.remove("open")}Xn.addEventListener("click",e=>{e.stopPropagation(),it.classList.contains("open")?Xe():Zn()}),Vn.addEventListener("click",Xe),ot.addEventListener("click",Xe);var Gn=2e3,Ce=null,lt=null,se=new Map,ae=new Map,Qn=5e3,Ot=new Map;function rt(e,t){return(t?"a:"+t+":":"l:")+e}function es(e,t,s){let n=rt(e,t);if(s==="working"){let m=Ot.get(n);if(m&&Date.now()<m)return}let i=ae.get(n),r=e===y&&(t??null)===C,c=s==="idle"&&i?.state==="working";ae.set(n,{session:e,agentId:t,state:s,doneUntil:c?Date.now()+Gn: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=f.querySelector(o);u&&Je(u,n,ae.get(n))}function Je(e,t,s){let n=se.get(t);n&&(clearTimeout(n),se.delete(t)),e.classList.remove("working","just-done");let i=s.session===y&&(s.agentId??null)===C;s.state==="working"?e.classList.add("working"):i&&s.doneUntil&&Date.now()<s.doneUntil?(e.classList.add("just-done"),se.set(t,setTimeout(()=>{e.classList.remove("just-done"),se.delete(t)},Math.max(0,s.doneUntil-Date.now())))):!i&&s.unread&&e.classList.add("just-done")}function ct(){for(let[e,t]of ae){let s=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])',n=f.querySelector(s);n&&Je(n,e,t)}}function ts(e,t){let s=rt(e,t),n=ae.get(s);if(!n)return;let i=se.get(s);i&&(clearTimeout(i),se.delete(s)),n.doneUntil=void 0,n.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=f.querySelector(r);c&&Je(c,s,n)}function Wt(){if(Ce||lt)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 s=new WebSocket(t);Ce=s,s.onmessage=n=>{try{let i=JSON.parse(String(n.data));i&&i.type==="session.activity"&&typeof i.session=="string"&&es(i.session,i.agentId||void 0,i.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{Ce===s&&(Ce=null,lt=setTimeout(()=>{lt=null,Wt()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{Ce=null}}window.__openSession=we,window.__refreshSidebar=j,j(),$e(),ct(),Wt(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&ct()}),setInterval(()=>{document.visibilityState==="visible"&&j()},1e4);
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import{initTerminal as
|
|
2
|
-
<span>${d(e.name)}</span>${t?"":'<span class="meta">offline</span>'}
|
|
1
|
+
import{initTerminal as qe}from"./terminal-core.js";const D=window.__TMUX_WEB_SHELL__;let f=null,u=null;const P=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let g=P;window.__tmuxWebScopeAgent=P;let de="sessions",x="",V="",J="",we="";function v(e){return g?"/a/"+encodeURIComponent(g)+e:e}const r=document.getElementById("sidebar-content"),H=document.getElementById("main-placeholder"),b=document.getElementById("terminal-container"),j=document.getElementById("file-editor"),Ve=document.getElementById("fe-path"),E=document.getElementById("fe-status"),O=document.getElementById("fe-content"),N=document.getElementById("fe-save"),Je=document.getElementById("fe-delete"),Ke=document.getElementById("fe-back"),Le=document.getElementById("fe-new-name"),Ye=document.getElementById("fe-new-btn"),pe=document.getElementById("settings-backdrop"),Ee=document.getElementById("settings-popover"),ke=document.querySelector(".sidebar"),Se="tmux-web-force-narrow";function K(){try{return localStorage.getItem(Se)==="1"}catch{return!1}}function Ce(){document.documentElement.classList.toggle("force-narrow",K()),K()&&Ie()&&Y()}function Ie(){return K()?!0:window.matchMedia("(max-width: 640px)").matches}function Y(){Ie()&&ke.classList.add("collapsed")}function Be(){ke.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Be(),xe("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Be(),xe("files")});function xe(e){de=e,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));const n=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");n&&n.classList.add("active"),e==="sessions"?(fe="",I()):dt()}let fe="";async function I(){if(de!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===fe)return;fe=s,tt(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{const n=window.__openNewSessionModal;typeof n=="function"?n():document.getElementById("new-session-modal")?.classList.add("open")}),he()}const Me="tmux-web-sidebar-group-collapsed";function Te(){try{return new Set(JSON.parse(localStorage.getItem(Me)||"[]"))}catch{return new Set}}function Xe(e){try{localStorage.setItem(Me,JSON.stringify([...e]))}catch{}}function Ze(e,n,t){if(!e)return"\u2026";const s=t&&(e===t||e.startsWith(t+"/"))?"~"+e.slice(t.length):e,i=s.split("/").filter(Boolean);if(i.length===0)return e;for(let o=1;o<=i.length;o++){const a=i.slice(-o).join("/");if(!n.some(c=>!c||c===e?!1:(t&&(c===t||c.startsWith(t+"/"))?"~"+c.slice(t.length):c).split("/").filter(Boolean).slice(-o).join("/")===a))return a}return s}function He(e,n,t,s=!0){const i=new Map;for(const l of e){const c=l.path||"";i.has(c)||i.set(c,[]),i.get(c).push(l)}const o=[...i.keys()],a=Te();for(const[l,c]of i){const p=Ze(l,o,n),L=a.has(l),h=document.createElement("div");h.className="session-group-header"+(L?" collapsed":""),h.title=l||"No working directory",h.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${d(p)}</span>`,h.addEventListener("click",()=>{const T=Te(),be=h.nextElementSibling;T.has(l)?(T.delete(l),h.classList.remove("collapsed"),be?.classList.remove("collapsed")):(T.add(l),h.classList.add("collapsed"),be?.classList.add("collapsed")),Xe(T)});const re=document.createElement("div");re.className="session-group-body"+(L?" collapsed":"");for(const T of c)re.appendChild(t?Qe(T,t,s):et(T));r.appendChild(h),r.appendChild(re)}}function Qe(e,n,t){const s=document.createElement("div");s.className="session-item"+(e.name===u&&g===n?" active":"")+(t?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",n),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>
|
|
2
|
+
<span>${d(e.name)}</span>${t?"":'<span class="meta">offline</span>'}
|
|
3
|
+
<button class="session-edit-btn" data-session="${d(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>`,t&&s.addEventListener("click",o=>{o.target.closest(".session-edit-btn")||U(e.name,n)});const i=s.querySelector(".session-edit-btn");return i&&i.addEventListener("click",o=>{o.stopPropagation(),ae(e.name,i,{agentId:n})}),s}function et(e){const n=document.createElement("div");n.className="session-item"+(e.name===u&&!g?" active":""),n.setAttribute("data-session",e.name),n.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>
|
|
3
4
|
<span>${d(e.name)}</span>
|
|
4
5
|
<button class="session-edit-btn" data-session="${d(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>
|
|
5
|
-
`,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||
|
|
6
|
+
`,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||U(e.name)});const t=n.querySelector(".session-edit-btn");return t&&t.addEventListener("click",s=>{s.stopPropagation(),ae(e.name,t)}),n}function tt(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(P?" clickable":""),t.textContent=P?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",P&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t),He(n,e.home);const s=e.agents||[];for(const o of s){if(!o.sessions||o.sessions.length===0)continue;const a=o.agentId===g,l=document.createElement("div");l.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!a&&o.online?" clickable":""),l.textContent=o.agentName+(o.online?"":" (offline)"),!a&&o.online&&(l.title="\u6253\u5F00 "+o.agentName,l.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),r.appendChild(l),He(o.sessions,e.home,o.agentId,o.online)}const i=e.tombstones||[];if(i.length>0){const o=document.createElement("div");o.className="sidebar-section-label tombstone-label",o.textContent="\u5931\u6548\u4F1A\u8BDD",r.appendChild(o);for(const a of i){const l=document.createElement("div");l.className="session-item session-tombstone",l.setAttribute("data-session",a.name),a.agentId&&l.setAttribute("data-agent",a.agentId);const c=a.agentId?a.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
|
|
6
7
|
<span>${d(a.name)}</span><span class="meta">\u5931\u6548${c}</span>
|
|
7
|
-
<button class="session-edit-btn" data-session="${d(a.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>`,l.title="\u5931\u6548\u4F1A\u8BDD\uFF1A\u70B9\u51FB\u7BA1\u7406\uFF08\u91CD\u5EFA/\u91CD\u547D\u540D/\u5220\u9664\uFF09",l.addEventListener("click",()=>{
|
|
8
|
+
<button class="session-edit-btn" data-session="${d(a.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>`,l.title="\u5931\u6548\u4F1A\u8BDD\uFF1A\u70B9\u51FB\u7BA1\u7406\uFF08\u91CD\u5EFA/\u91CD\u547D\u540D/\u5220\u9664\uFF09",l.addEventListener("click",()=>{ae(a.name,l,{tombstone:!0,agentId:a.agentId})});const p=l.querySelector(".session-edit-btn");p&&p.addEventListener("click",L=>{L.stopPropagation(),ae(a.name,p,{tombstone:!0,agentId:a.agentId})}),r.appendChild(l)}}}function d(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function U(e,n){if(u===e&&f&&g===(n||null))return;j.style.display="none",H.style.display="none",b.style.display="",b.classList.add("terminal-pending"),Y(),f&&(ie(),f.destroy(),f=null),u=e,g=n||null,window.__tmuxWebScopeAgent=g,window.__tmuxWebCurrentSession=u,kt(e,n);const t=ye(e,n);Fe.set(t,Date.now()+Lt);const s=_.get(t);if(s&&s.state==="working"){s.state="idle",s.doneUntil=void 0,s.unread=!1;const i=r.querySelector(n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');i&&ce(i,t,s)}for(const i of r.querySelectorAll(".session-item"))i.classList.toggle("active",i.getAttribute("data-session")===e&&(i.getAttribute("data-agent")||null)===g);try{f=await qe(b,e,{terminal:D.terminal,scrollback:D.scrollback,theme:D.theme,wsBase:g?"/ws/a/"+encodeURIComponent(g):D.wsBase||"/ws"}),Ue()}catch(i){console.error("[shell] terminal init failed:",i);const o=i&&typeof i=="object"&&"message"in i?i.message:String(i);b.textContent="Failed to open terminal for "+e+": "+o,b.classList.remove("terminal-pending")}}let X=[],m=null,Ne=!1;function nt(e){if(!m||!m.files.length)return null;const n=m.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of m.files)if(s.path===t)return s.status;return null}async function st(e){Ne=!0;try{const n=await fetch(v("/api/git/status?path="+encodeURIComponent(e)));n.ok?m=await n.json():m=null}catch{m=null}finally{Ne=!1}}function it(){if(!m||!m.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=m;let s=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
8
9
|
<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>
|
|
9
|
-
${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")
|
|
10
|
+
${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&$&&m&&(e.stopPropagation(),De())});const W=document.getElementById("header-git"),Re=document.getElementById("git-diff-view"),$e=document.getElementById("gd-path"),Z=document.getElementById("gd-status"),Q=document.getElementById("gd-content"),ot=document.getElementById("gd-back");let $=null;function z(e){if(!e||!e.branch){W.style.display="none",$=null;return}$=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),W.innerHTML=i,W.style.display="block"}W.addEventListener("click",e=>{$&&m&&(e.stopPropagation(),De())});const y=document.getElementById("git-popover"),ee=document.getElementById("git-popover-backdrop");function ue(){const e=W.getBoundingClientRect();y.style.top=e.bottom+4+"px",y.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Ae(){const e=$,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(ne(),e){de="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),M(e)}})}let _e="";function De(){if(!m)return;const e=m.files||[];if(!e.length)y.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
10
11
|
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let n=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let t=0;t<e.length;t++){const s=e[t],i=s.additions||0,o=s.deletions||0;let a="";switch(s.status){case"M":a='<span class="git-file-status M">M</span>';break;case"A":a='<span class="git-file-status A">A</span>';break;case"D":a='<span class="git-file-status D">D</span>';break;case"?":a='<span class="git-file-status ?">?</span>';break;case"R":a='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
|
|
11
12
|
${a}<span class="file-path">${d(s.path)}</span>
|
|
12
13
|
<span class="file-add">${i>0?"+"+i:""}</span>
|
|
13
14
|
<span class="file-del">${o>0?"-"+o:""}</span>
|
|
14
|
-
</div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',
|
|
15
|
+
</div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',_e=n,y.innerHTML=n,y.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>je(i.path)))})}ue(),y.classList.add("open"),ee.classList.add("open"),Ae()}function at(){return window.innerWidth>=1024}function Pe(e,n){const s=(e+(n&&e?`
|
|
15
16
|
`:"")+n).split(`
|
|
16
|
-
`);let i="",o=0,a=0;for(const l of s){const c=l;if(c.startsWith("@@")){const p=c.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,a=parseInt(p[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(c)}</span></div>`;continue}if(c.startsWith("diff --git")||c.startsWith("index ")||c.startsWith("--- ")||c.startsWith("+++ ")||c.startsWith("new file")||c.startsWith("deleted file")){c.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(c)}</span></div>`);continue}if(c.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(c)}</span></div>`;else if(c.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${d(c)}</span></div>`;else if(c.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(c)}</span></div>`;else{if(!c&&!o&&!a)continue;o++,a++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(c)}</span></div>`}}return{html:i,lineCount:s.length}}let
|
|
17
|
+
`);let i="",o=0,a=0;for(const l of s){const c=l;if(c.startsWith("@@")){const p=c.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,a=parseInt(p[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(c)}</span></div>`;continue}if(c.startsWith("diff --git")||c.startsWith("index ")||c.startsWith("--- ")||c.startsWith("+++ ")||c.startsWith("new file")||c.startsWith("deleted file")){c.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(c)}</span></div>`);continue}if(c.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(c)}</span></div>`;else if(c.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${d(c)}</span></div>`;else if(c.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(c)}</span></div>`;else{if(!c&&!o&&!a)continue;o++,a++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(c)}</span></div>`}}return{html:i,lineCount:s.length}}let te=null;async function je(e){const n=$;if(n)if(ne(),at()){b.style.display="none",f&&(ie(),f.destroy(),f=null,te=u,u=null),z(null),j.style.display="none",H.style.display="none",Re.style.display="flex",$e.textContent=e,Z.textContent="Loading\u2026",Q.innerHTML="";try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();ct(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);Z.textContent="Error",Q.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
17
18
|
<button class="diff-back">\u2190 Back</button>
|
|
18
19
|
<span class="diff-filename">${d(e)}</span>
|
|
19
20
|
<span class="diff-loading">Loading\u2026</span>
|
|
20
|
-
</div></div>`,
|
|
21
|
+
</div></div>`,ue(),y.classList.add("open"),ee.classList.add("open");try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();lt(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
21
22
|
<button class="diff-back">\u2190 Back</button>
|
|
22
23
|
<span class="diff-filename">${d(e)}</span>
|
|
23
|
-
</div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function
|
|
24
|
+
</div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function lt(e,n,t){if(!n&&!t){y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
24
25
|
<button class="diff-back">\u2190 Back</button>
|
|
25
26
|
<span class="diff-filename">${d(e)}</span>
|
|
26
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=
|
|
27
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=Pe(n,t);y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
27
28
|
<button class="diff-back">\u2190 Back</button>
|
|
28
29
|
<span class="diff-filename">${d(e)}</span>
|
|
29
30
|
<span class="diff-filesize">${i} lines</span>
|
|
30
|
-
</div><div class="diff-content">${s}</div></div>`,
|
|
31
|
+
</div><div class="diff-content">${s}</div></div>`,y.querySelector(".diff-back")?.addEventListener("click",()=>{y.innerHTML=_e,ue(),m&&y.querySelectorAll(".git-popover-item").forEach(o=>{const a=parseInt(o.dataset.idx||"0",10),l=m.files[a];!l||l.status==="D"||(o.style.cursor="pointer",o.addEventListener("click",()=>je(l.path)))}),Ae()})}function ct(e,n,t){if(!n&&!t){Z.textContent="No diff",Q.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=Pe(n,t);$e.textContent=e,Z.textContent=i+" lines",Q.innerHTML=s}function rt(){Re.style.display="none",te?(b.style.display="",b.classList.add("terminal-pending"),U(te),te=null):u?(b.style.display="",b.classList.add("terminal-pending"),Ue()):H.style.display="flex"}ot.addEventListener("click",rt);function ne(){y.classList.remove("open"),ee.classList.remove("open")}ee.addEventListener("click",ne),document.addEventListener("keydown",e=>{e.key==="Escape"&&(ne(),le())});async function Oe(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();m=i,z(i);return}}}catch{}m=null,z(null)}let se=null;function Ue(){ie(),u&&(Oe(u),se=setInterval(()=>{u&&Oe(u)},3e4))}function ie(){se&&(clearInterval(se),se=null)}function dt(){if(X=D.fsRoots||[],X.length===0){if(u){pt(u);return}r.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}r.innerHTML="";for(const e of X){const n=document.createElement("div");n.className="file-tree-item",n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(e)}`,n.addEventListener("click",()=>M(e)),r.appendChild(n)}}async function pt(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(v("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?M(t.path):r.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{r.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function M(e){x=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=st(e);try{const s=await(await fetch(v("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",z(m),r.innerHTML+=it();const i=ft(e);if(i&&!X.some(l=>e===l)){const l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>M(i)),r.appendChild(l)}if(s.dirs)for(const a of s.dirs){const l=a.split("/").pop()||a.split("\\").pop(),c=document.createElement("div");c.className="file-tree-item",c.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(l)}`,c.addEventListener("click",()=>M(a)),r.appendChild(c)}else r.innerHTML="";const o=s.files||[];for(const a of o){const l=a.split("/").pop()||a.split("\\").pop(),c=nt(a);let p="",L="";if(c)switch(c){case"M":p="git-mod",L='<span class="git-file-status M">M</span>';break;case"A":p="git-add",L='<span class="git-file-status A">A</span>';break;case"D":p="git-del",L='<span class="git-file-status D">D</span>';break;case"?":p="git-untracked",L='<span class="git-file-status ?">?</span>';break;case"R":p="git-mod",L='<span class="git-file-status R">R</span>';break}const h=document.createElement("div");h.className="file-tree-item file"+(p?" "+p:""),h.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${L}${d(l)}`,h.addEventListener("click",()=>ut(a)),r.appendChild(h)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function ft(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function ut(e){V=e,b.style.display="none",f&&(ie(),f.destroy(),f=null,u=null),z(null),Y(),H.style.display="none",j.style.display="flex",Ve.textContent=e,E.textContent="Loading...",N.style.display="none";try{const n=await fetch(v("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();E.textContent=s.error||"Failed to load",O.value="";return}const t=await n.json();O.value=t.content,J=t.content,we=t.content,E.textContent=t.size+" bytes",N.style.display=""}catch{E.textContent="Failed to load",O.value=""}}O.addEventListener("input",()=>{J=O.value}),N.addEventListener("click",async()=>{N.textContent="Saving...",N.disabled=!0;try{const e=await fetch(v("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:V,content:J})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Save failed";return}we=J,E.textContent="Saved"}catch{E.textContent="Save failed"}finally{N.disabled=!1,N.textContent="Save"}}),Je.addEventListener("click",async()=>{if(confirm("Delete "+V+"?"))try{const e=await fetch(v("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:V})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Delete failed";return}j.style.display="none",H.style.display="flex",x&&M(x)}catch{E.textContent="Delete failed"}}),Ke.addEventListener("click",()=>{j.style.display="none",H.style.display="flex",x&&M(x)}),Ye.addEventListener("click",async()=>{const e=Le.value.trim();if(!e)return;const n=x+"/"+e;try{const t=await fetch(v("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();E.textContent=s.error||"Create failed";return}Le.value="",x&&M(x)}catch{E.textContent="Create failed"}});const oe=document.getElementById("force-narrow-toggle");oe&&(oe.checked=K(),oe.addEventListener("change",()=>{try{localStorage.setItem(Se,oe.checked?"1":"0")}catch{}Ce(),I()})),Ce(),document.getElementById("mode-settings").addEventListener("click",()=>{const e=Ee.classList.toggle("open");pe.classList.toggle("open",e)}),pe.addEventListener("click",()=>{Ee.classList.remove("open"),pe.classList.remove("open")});let w="",B=!1,k;const F=document.createElement("div");F.className="session-popover-backdrop",document.body.appendChild(F);const S=document.createElement("div");S.id="session-popover",S.className="session-popover",S.style.display="none",S.innerHTML=`<div class="sp-field">
|
|
31
32
|
<label>Rename session</label>
|
|
32
33
|
<input type="text" id="sp-name" />
|
|
33
34
|
</div>
|
|
@@ -37,5 +38,5 @@ import{initTerminal as Ue}from"./terminal-core.js";const _=window.__TMUX_WEB_SHE
|
|
|
37
38
|
</div>
|
|
38
39
|
<hr class="sp-divider" />
|
|
39
40
|
<button class="btn primary" id="sp-rebuild" style="display:none">\u91CD\u5EFA\u540C\u540D\u540C\u8DEF\u5F84 session</button>
|
|
40
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(
|
|
41
|
-
`).length;
|
|
41
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(S);function mt(e){const n=e.getBoundingClientRect(),t=window.innerHeight,s=S.offsetHeight||240;let i=n.bottom+4;i+s>t-8&&(i=Math.max(8,n.top-s-4)),S.style.top=i+"px",S.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function ae(e,n,t){w=e,B=!!t?.tombstone,k=t?.agentId;const s=document.getElementById("sp-name");s.value=e;const i=S.querySelector(".sp-field label");i.textContent=B?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";const o=document.getElementById("sp-rebuild");o.style.display=B?"":"none";const a=document.getElementById("sp-delete");a.textContent=B?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";const l=document.getElementById("sp-save");l.textContent=B?"\u91CD\u547D\u540D":"Save",F.classList.add("open"),S.style.display="block",mt(n),setTimeout(()=>s.focus(),50)}function R(){F.classList.remove("open"),S.style.display="none"}F.addEventListener("click",R),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===w){R();return}try{const n=B?{oldName:w,newName:e,agentId:k}:{oldName:w,newName:e,...k?{agentId:k}:{}};if(!(await fetch(v(B?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).ok)return;R(),I(),!B&&u===w&&(k??null)===g&&U(e,k)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+w+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{const e=await fetch(v("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:k})});if(!e.ok){const n=await e.json();alert(n.error||"\u91CD\u5EFA\u5931\u8D25");return}R(),I()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(B){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+w+'"\uFF1F'))return;try{if(!(await fetch(v("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:k})})).ok)return;R(),I()}catch{}return}if(confirm('Delete session "'+w+'"?'))try{if(!(await fetch(v("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,...k?{agentId:k}:{}})})).ok)return;R(),u===w&&(k??null)===g&&(f&&(f.destroy(),f=null),u=null,b.style.display="none",H.style.display="flex"),I()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",R);const gt={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=>{const n=e.target.closest("button[data-key]");if(!n||!f)return;const t=gt[n.dataset.key];t&&(f.sendInput(t),f.focus())});const C=document.getElementById("mk-input"),We=document.getElementById("mk-send");function ze(){if(!C||!f)return;const e=C.value;e&&(f.sendInput(e+"\r"),C.value="",C.rows=1,f.focus())}if(C&&(C.addEventListener("input",()=>{C.rows=1;const e=C.value.split(`
|
|
42
|
+
`).length;C.rows=Math.min(e,4)}),C.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),ze())})),We&&We.addEventListener("click",ze),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const me=document.getElementById("process-panel"),G=document.getElementById("proc-list"),vt=document.getElementById("proc-close"),ge=document.getElementById("proc-backdrop"),yt=document.getElementById("header-ram");function ht(){G.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(v("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){G.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">×</button></div>'}G.innerHTML=n;for(const t of G.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const o=i.target.closest(".proc-row");!o||!confirm("Kill PID "+o.dataset.pid+"?")||fetch(v("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(o.dataset.pid,10)})}).then(a=>a.json()).then(a=>{a.ok?o.style.opacity="0.3":alert("Failed: "+(a.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{G.innerHTML='<div class="proc-empty">Failed to load</div>'})}function bt(){me.classList.add("open"),ge.classList.add("open"),ht()}function le(){me.classList.remove("open"),ge.classList.remove("open")}yt.addEventListener("click",e=>{e.stopPropagation(),me.classList.contains("open")?le():bt()}),vt.addEventListener("click",le),ge.addEventListener("click",le);const wt=2e3;let q=null,ve=null;const A=new Map,_=new Map,Lt=5e3,Fe=new Map;function ye(e,n){return(n?"a:"+n+":":"l:")+e}function Et(e,n,t){const s=ye(e,n);if(t==="working"){const p=Fe.get(s);if(p&&Date.now()<p)return}const i=_.get(s),o=e===u&&(n??null)===g,a=t==="idle"&&i?.state==="working";_.set(s,{session:e,agentId:n,state:t,doneUntil:a?Date.now()+wt:void 0,unread:a?!o:!1});const l=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=r.querySelector(l);c&&ce(c,s,_.get(s))}function ce(e,n,t){const s=A.get(n);s&&(clearTimeout(s),A.delete(n)),e.classList.remove("working","just-done");const i=t.session===u&&(t.agentId??null)===g;t.state==="working"?e.classList.add("working"):i&&t.doneUntil&&Date.now()<t.doneUntil?(e.classList.add("just-done"),A.set(n,setTimeout(()=>{e.classList.remove("just-done"),A.delete(n)},Math.max(0,t.doneUntil-Date.now())))):!i&&t.unread&&e.classList.add("just-done")}function he(){for(const[e,n]of _){const t=n.agentId?'.session-item[data-agent="'+CSS.escape(n.agentId)+'"][data-session="'+CSS.escape(n.session)+'"]':'.session-item[data-session="'+CSS.escape(n.session)+'"]:not([data-agent])',s=r.querySelector(t);s&&ce(s,e,n)}}function kt(e,n){const t=ye(e,n),s=_.get(t);if(!s)return;const i=A.get(t);i&&(clearTimeout(i),A.delete(t)),s.doneUntil=void 0,s.unread=!1;const o=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',a=r.querySelector(o);a&&ce(a,t,s)}function Ge(){if(q||ve)return;const e=localStorage.getItem("tmux-web-token");let t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{const s=new WebSocket(t);q=s,s.onmessage=i=>{try{const o=JSON.parse(String(i.data));o&&o.type==="session.activity"&&typeof o.session=="string"&&Et(o.session,o.agentId||void 0,o.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{q===s&&(q=null,ve=setTimeout(()=>{ve=null,Ge()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{q=null}}window.__openSession=U,window.__refreshSidebar=I,I(),Y(),he(),Ge(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&he()}),setInterval(()=>{document.visibilityState==="visible"&&I()},1e4);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const i=1,
|
|
1
|
+
const i=1,u=2;function f(e,t,n){const r=typeof n=="string"?Buffer.from(n,"utf-8"):Buffer.isBuffer(n)?n:Buffer.from(n),s=Buffer.allocUnsafe(5+r.length);return s[0]=e,s.writeUInt32BE(t>>>0,1),r.copy(s,5),s}function l(e){return e.length<5?null:{kind:e[0],id:e.readUInt32BE(1),payload:e.subarray(5)}}function c(e){if(typeof e!="object"||e===null)return!1;const t=e.type;return typeof t=="string"&&o.has(t)}function a(e){if(typeof e!="object"||e===null)return!1;const t=e.type;return typeof t=="string"&&_.has(t)}const o=new Set(["hello","sessions","http_resp","http_body_end","attach_ok","attach_err","ws_to_hub","ws_close","activity","rebuild_session_result","rename_session_result","kill_session_result","ping"]),_=new Set(["hello_ok","hello_err","sessions_req","http_req","http_body_end","attach","ws_to_agent","rebuild_session","rename_session","kill_session","ping","detach","pong"]);export{u as BIN_KIND_HTTP_BODY,i as BIN_KIND_TERM,l as decodeBinaryFrame,f as encodeBinaryFrame,c as isAgentToHubMessage,a as isHubToAgentMessage};
|
package/dist/lib/agent-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WebSocket as
|
|
1
|
+
import{WebSocket as d}from"ws";import{attachTerminal as z}from"./attach-terminal.js";import{encodeBinaryFrame as Q,decodeBinaryFrame as V,BIN_KIND_HTTP_BODY as R}from"./agent-channel.js";import{ActivityProbe as Z}from"./activity-probe.js";import{sessionWorkingPath as ee}from"./sessions-sidebar.js";import{newTmuxSession as te,renameSession as ne,killSession as se}from"./tmux-windows.js";const oe=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),ae=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),re=parseInt(process.env.TMUX_WEB_AGENT_PONG_TIMEOUT_MS||"45000",10),H=5,ie=6e4,ce=new Set(["content-type","content-disposition","location","cache-control","etag"]);function _e(a){const k=a.log??(t=>console.log(`[agent] ${t}`)),_=t=>console.error(`[agent] ${t}`);let s=null,P=0,m=0,B=null,I=!1;const f=new Map,S=new Map,g=new Map,N=new Set;function U(t){return N.has(t)?Promise.resolve():new Promise(e=>{g.set(t,e),setTimeout(()=>{g.delete(t)&&e()},3e4)})}let b=null,w=null,E=null,h=null,O=new Map,C="",M=null;const T={state:"idle",agentId:null,hub:a.hub,name:a.name};function y(t,e){e!==void 0&&(T.lastError=e),T.state=t,T.agentId=M,a.onStatus?.({...T})}function c(t){s&&s.readyState===d.OPEN&&s.send(JSON.stringify(t))}function p(){let t;try{t=a.listSessions()}catch{return}const e=t.map(o=>({...o,path:ee(o.name)})),n=JSON.stringify(e);n!==C&&(C=n,c({type:"sessions",sessions:e}))}function L(){b||(b=setInterval(p,ae)),w||(w=setInterval(()=>{if(s&&s.readyState===d.OPEN&&m>0&&Date.now()-m>re){k(`hub heartbeat timeout (${Date.now()-m}ms without pong), reconnecting`);try{s.close(1001,"hub heartbeat timeout")}catch{}return}c({type:"ping",ts:Date.now()})},oe)),E||(h=new Z,E=setInterval(()=>void D(),h.scanMs))}function q(){b&&(clearInterval(b),b=null),w&&(clearInterval(w),w=null),E&&(clearInterval(E),E=null),h=null}async function D(){if(!(!s||s.readyState!==d.OPEN||!h))try{const t=await h.scan(),e=[];for(const[n,o]of t)O.get(n)!==o&&e.push({session:n,state:o});O=t,e.length&&c({type:"activity",activities:e})}catch{}}async function J(t){let e;if(t.hasBody){await U(t.id),N.delete(t.id);const r=S.get(t.id);S.delete(t.id),e=r&&r.length?Buffer.concat(r):Buffer.alloc(0)}const n=new URL(t.path,"http://agent.invalid");let o;try{const r={method:t.method,headers:t.headers};e&&e.length&&(r.body=e),o=await Promise.race([a.app.request(new Request(n,r)),new Promise((le,Y)=>setTimeout(()=>Y(new Error("app.request timed out")),3e4))])}catch(r){_(`tunnel request failed: ${t.method} ${t.path} ${r.message}`),c({type:"http_resp",id:t.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const l={};o.headers.forEach((r,u)=>{ce.has(u.toLowerCase())&&(l[u]=r)});const i=Buffer.from(await o.arrayBuffer());if(c({type:"http_resp",id:t.id,status:o.status,headers:l,hasBody:i.length>0}),i.length>0){for(let u=0;u<i.length;u+=65536){if(!s||s.readyState!==d.OPEN)return;s.send(Q(R,t.id,i.subarray(u,u+65536)))}c({type:"http_body_end",id:t.id})}}function x(t,e){let n=!1,o="";try{te(t,e),n=!0}catch(l){o=l instanceof Error?l.message:"rebuild failed"}s&&s.readyState===d.OPEN&&s.send(JSON.stringify({type:"rebuild_session_result",session:t,ok:n,message:o})),p()}function G(t){let e=!1,n="";try{se(t),e=!0}catch(o){n=o instanceof Error?o.message:"kill failed"}s&&s.readyState===d.OPEN&&s.send(JSON.stringify({type:"kill_session_result",session:t,ok:e,message:n})),p()}function W(t,e){let n=!1,o="";try{ne(t,e),n=!0}catch(l){o=l instanceof Error?l.message:"rename failed"}s&&s.readyState===d.OPEN&&s.send(JSON.stringify({type:"rename_session_result",session:e,ok:n,message:o})),p()}function K(t){const e=t.session;let n=!1;try{n=a.listSessions().some(i=>i.name===e)}catch{n=!1}if(!n){c({type:"attach_err",connId:t.connId,session:e,message:"no such session"});return}let o=null;h?.suppress(e);const l=z(e,{send:i=>c({type:"ws_to_hub",connId:t.connId,msg:i}),onMessage:i=>{o=i},onClose:()=>{}},{terminalBufferConfig:a.terminalBufferConfig,getSessionPaneTarget:a.getSessionPaneTarget,capturePaneTail:a.capturePaneTail,capturePaneHistoryChunk:a.capturePaneHistoryChunk,toCrlf:a.toCrlf,handleClientMessage:a.handleClientMessage,acquireControlClient:a.acquireControlClient,initialCols:t.cols,initialRows:t.rows,tmuxSocketPath:a.tmuxSocketPath,onPtyExit:(i,r)=>{c({type:"ws_close",connId:t.connId,code:1e3,reason:`tmux exited (${r})`})},onSpawnError:(i,r)=>{_(`attach failed for session "${t.session}": ${r}`),c({type:"attach_err",connId:t.connId,session:e,message:r})},onAttached:i=>{c({type:"attach_ok",connId:t.connId,session:e})}});f.set(t.connId,{onMessage:i=>o?.(i),dispose:()=>{f.delete(t.connId),l.dispose()}})}function j(t){let e;try{e=JSON.parse(t)}catch{return}switch(e.type){case"hello_ok":M=e.agentId,P=0,m=Date.now(),k(`connected to hub as "${M}"`),y("connected"),p(),L();break;case"hello_err":if(_(`hub rejected us: ${e.reason}`),y("error",e.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":p();break;case"http_req":J(e);break;case"http_body_end":{N.add(e.id);const n=g.get(e.id);n&&(g.delete(e.id),n());break}case"attach":K(e);break;case"rebuild_session":x(e.session,e.dir);break;case"rename_session":W(e.oldName,e.newName);break;case"kill_session":G(e.session);break;case"ws_to_agent":{const n=f.get(e.connId);if(n)try{n.onMessage(JSON.stringify(e.msg))}catch{}break}case"detach":{const n=f.get(e.connId);n&&n.dispose();break}case"pong":m=Date.now();break;case"ping":c({type:"pong",ts:e.ts});break;default:break}}function F(t){const e=V(t);if(e&&e.kind===R){const n=S.get(e.id)??[];n.push(e.payload),S.set(e.id,n)}}function v(){q();for(const t of f.values())try{t.dispose()}catch{}f.clear(),S.clear(),g.clear(),N.clear(),C=""}function A(){if(s){try{s.removeAllListeners()}catch{}s=null}if(v(),I)return;y("connecting"),P+=1;const e=(P<=H?1e3:Math.min(1e3*Math.pow(2,P-H),ie))+Math.random()*500;k(`reconnecting in ${Math.round(e)}ms\u2026`),B=setTimeout($,e)}function $(){if(I||s)return;const t=a.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let e;try{e=new d(t)}catch(n){_(`bad hub URL: ${n.message} (${t})`),y("error",`bad hub URL: ${n.message}`);return}s=e,y("connecting"),e.on("open",()=>{k(`connecting to ${t}\u2026`),c({type:"hello",token:a.token,name:a.name,version:a.version})}),e.on("message",(n,o)=>{o?F(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):j(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),e.on("close",()=>{s===e&&A()}),e.on("error",n=>{_(`connection error: ${n.message}`),s===e&&A()})}function X(){if(!I){if(I=!0,B&&clearTimeout(B),v(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}M=null,y("idle")}}return $(),{stop:X,status:()=>({...T})}}export{_e as startAgentClient};
|
package/dist/lib/agent-relay.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{WebSocket as r}from"ws";import{decodeBinaryFrame as u,encodeBinaryFrame as f,BIN_KIND_TERM as g,BIN_KIND_HTTP_BODY as h}from"./agent-channel.js";const _=parseInt(process.env.TMUX_WEB_HUB_HEARTBEAT_MS||"15000",10),w=parseInt(process.env.TMUX_WEB_HUB_PONG_TIMEOUT_MS||"90000",10),N=parseInt(process.env.TMUX_WEB_MAX_AGENT_CONNS||"64",10),b=parseInt(process.env.TMUX_WEB_TUNNEL_TIMEOUT_MS||"30000",10);let O=1,m=1;class
|
|
2
|
-
\x1B[31m--- agent disconnected (${
|
|
3
|
-
`}));try{e.ws.close(1011,"agent disconnected")}catch{}}e.closed=!0}this.relays.clear()}dispose(){this.disposed||(this.disposed=!0,this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.failAll("agent disconnected"),this.onClose?.(this))}}export{
|
|
1
|
+
import{WebSocket as r}from"ws";import{decodeBinaryFrame as u,encodeBinaryFrame as f,BIN_KIND_TERM as g,BIN_KIND_HTTP_BODY as h}from"./agent-channel.js";const _=parseInt(process.env.TMUX_WEB_HUB_HEARTBEAT_MS||"15000",10),w=parseInt(process.env.TMUX_WEB_HUB_PONG_TIMEOUT_MS||"90000",10),N=parseInt(process.env.TMUX_WEB_MAX_AGENT_CONNS||"64",10),b=parseInt(process.env.TMUX_WEB_TUNNEL_TIMEOUT_MS||"30000",10);let O=1,m=1;class k{ws;agentId;pending=new Map;relays=new Map;onSessions;onActivity;onRebuildResult;onClose;lastPongAt=Date.now();heartbeatTimer=null;disposed=!1;constructor(t,e,s){this.ws=t,this.agentId=e,this.onSessions=s.onSessions,this.onActivity=s.onActivity,this.onRebuildResult=s.onRebuildResult,this.onClose=s.onClose,this.heartbeatTimer=setInterval(()=>{if(!(this.disposed||this.ws.readyState!==r.OPEN)){if(Date.now()-this.lastPongAt>w){try{this.ws.close(1001,"agent heartbeat timeout")}catch{}return}this.send({type:"ping",ts:Date.now()})}},_)}send(t){return this.disposed||this.ws.readyState!==r.OPEN?!1:(this.ws.send(JSON.stringify(t)),!0)}attach(t,e,s){return this.send({type:"attach",connId:t,session:e,cols:s?.cols,rows:s?.rows})}detach(t){this.send({type:"detach",connId:t})}sendToAgent(t,e){this.send({type:"ws_to_agent",connId:t,msg:e})}sendSessionsReq(){this.send({type:"sessions_req"})}rebuildSession(t,e){return this.send({type:"rebuild_session",session:t,dir:e})}renameSession(t,e){return this.send({type:"rename_session",oldName:t,newName:e})}killSession(t){return this.send({type:"kill_session",session:t})}async httpRequest(t,e,s,n){const i=m++,o=!!n,c=new Promise((d,a)=>{const l=setTimeout(()=>{this.pending.delete(i),a(new Error("tunnel request timed out"))},b);this.pending.set(i,{id:i,resolve:d,reject:a,status:null,headers:null,body:[],hasBody:o,timer:l})});if(!this.send({type:"http_req",id:i,method:t,path:e,headers:s,hasBody:o}))return this.pending.delete(i),Promise.reject(new Error("agent offline"));if(n){const d=n.getReader();(async()=>{for(;;){const{done:l,value:p}=await d.read();if(l)break;const y=Buffer.from(p);this.ws.send(f(h,i,y))}this.send({type:"http_body_end",id:i})})().catch(()=>{this.send({type:"http_body_end",id:i})})}return c}registerRelay(t,e,s){this.relays.set(t,{connId:t,session:e,ws:s,closed:!1})}relayClosed(t){const e=this.relays.get(t);e&&!e.closed&&(e.closed=!0,this.relays.delete(t),this.detach(t))}get relayCount(){return this.relays.size}handleData(t){const e=Buffer.isBuffer(t)?t:Buffer.from(t),s=u(e);if(s){if(s.kind===g){const n=this.relays.get(s.id);n&&!n.closed&&n.ws.readyState===r.OPEN&&n.ws.send(JSON.stringify({type:"data",data:s.payload.toString("utf-8")}));return}if(s.kind===h){const n=this.pending.get(s.id);n&&n.body.push(s.payload)}}}handleMessage(t){let e;try{e=JSON.parse(t)}catch{return}switch(e.type){case"sessions":this.onSessions?.(e.sessions);break;case"activity":this.onActivity?.(e.activities);break;case"rebuild_session_result":this.onRebuildResult?.({session:e.session,ok:e.ok,message:e.message});break;case"http_resp":{const s=this.pending.get(e.id);if(!s)break;s.status=e.status,s.headers=e.headers,e.hasBody||this.finalizeHttp(s.id);break}case"http_body_end":this.finalizeHttp(e.id);break;case"attach_ok":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify({type:"auth.ok",setupMode:!1}));break}case"attach_err":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify({type:"attach_failed",message:`Agent "${this.agentId}": cannot attach to session "${e.session}": ${e.message}`}));break}case"ws_to_hub":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify(e.msg));break}case"ws_close":{const s=this.relays.get(e.connId);if(s&&!s.closed){s.closed=!0;try{s.ws.close(e.code??1e3,e.reason??"agent closed")}catch{}this.relays.delete(e.connId)}break}case"ping":this.lastPongAt=Date.now(),this.send({type:"pong",ts:e.ts});break;default:break}}finalizeHttp(t){const e=this.pending.get(t);if(!e)return;this.pending.delete(t),clearTimeout(e.timer);const s=e.status??500,n=e.headers??{},i=Buffer.concat(e.body),o={};for(const[c,d]of Object.entries(n)){const a=c.toLowerCase();(a==="content-type"||a==="content-disposition"||a==="cache-control"||a==="etag"||a==="location")&&(o[c]=d)}e.resolve(new Response(i,{status:s,headers:o}))}failAll(t){for(const e of this.pending.values())clearTimeout(e.timer),e.reject(new Error(t));this.pending.clear();for(const e of this.relays.values()){if(!e.closed&&e.ws.readyState===r.OPEN){e.ws.send(JSON.stringify({type:"data",data:`\r
|
|
2
|
+
\x1B[31m--- agent disconnected (${t}) ---\x1B[0m\r
|
|
3
|
+
`}));try{e.ws.close(1011,"agent disconnected")}catch{}}e.closed=!0}this.relays.clear()}dispose(){this.disposed||(this.disposed=!0,this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.failAll("agent disconnected"),this.onClose?.(this))}}export{k as AgentChannel,N as MAX_RELAY_CONNS_PER_AGENT,b as TUNNEL_REQUEST_TIMEOUT_MS};
|
package/dist/lib/build-app.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{existsSync as P,readdirSync as me,statSync as T,unlinkSync as fe,writeFileSync as pe,mkdirSync as he,readFileSync as
|
|
1
|
+
import{existsSync as P,readdirSync as me,statSync as T,unlinkSync as fe,writeFileSync as pe,mkdirSync as he,readFileSync as z}from"node:fs";import{readFile as ge}from"node:fs/promises";import{execSync as I,execFileSync as L}from"node:child_process";import{tmuxEnv as ye}from"./tmux-env.js";import g from"node:path";import{fileURLToPath as je}from"node:url";import{Hono as we}from"hono";import{listSessions as D}from"../sessions.js";import{renderLoginPage as ve,renderNotesIndex as ke,renderNotesPage as qe,renderSettings as Se,renderThemeSettings as be,renderHistoryIndex as Te,renderQuickCommandsPage as Ae,renderFilesIndex as xe,renderShell as Ie}from"../frontend.js";import{renderFederationPage as Ce}from"./pages/federation.js";import{createAgentToken as _e,removeAgentToken as Ee,listAgentTokens as V}from"./agent-tokens.js";import{hashPassword as Oe,verifyPassword as Ne,validatePassword as Re}from"./auth.js";import{atomicWriteFileSync as Pe}from"./atomicWrite.js";import{resolveFsPath as A,resolveFsRoots as Q,MAX_FILE_BYTES as K,walkRecursive as Le,fsEntryCmp as X}from"./fs-access.js";import{captureSessionWindowsWithPath as De}from"./tmux-windows.js";import{audit as v}from"./auditLog.js";import{db as S}from"./db.js";import{getSessionAccessMap as Fe}from"./session-access.js";import{listWindowHistory as He,clearWindowHistory as Me}from"./window-history.js";import{saveSecurityConfig as Ue}from"./security-config.js";import{ImageUploadError as We,saveUploadedImage as $e}from"./image-upload.js";import{readSettings as Z,writeSettings as Be}from"./settings.js";import{setActiveThemeTemplate as Y}from"./theme-store.js";import{isThemeTemplateId as J,THEME_TEMPLATE_IDS as Ge}from"./themes/index.js";import{pinView as ze,unpinView as Ve,listPinnedViews as Qe}from"./pinned-views.js";import{getStoredWindows as Ke}from"./session-windows.js";import{buildSidebarSessions as Xe}from"./sessions-sidebar.js";import{upsertSessionState as ee,removeSessionState as F,renameSessionState as H,computeTombstones as Ze,getSessionState as Ye}from"./session-state.js";import{createQuickCommand as Je,deleteQuickCommand as et,listQuickCommands as te,updateQuickCommand as tt}from"./quick-commands.js";import{getSystemStatus as rt,getTopProcesses as nt,killProcess as st}from"./system-monitor.js";import{newTmuxSession as re,renameSession as ot,killSession as it,sessionExists as at,TmuxWindowsError as ne}from"./tmux-windows.js";import{registerAgentTunnel as dt}from"./agent-tunnel.js";import{injectScopeScript as ut}from"./scope-script.js";const M="tmux-web-token",ct=365;function lt(x){return x==="127.0.0.1"||x==="::1"||x==="::ffff:127.0.0.1"}function Vt(x){const{mode:se,securityConfig:y,tokenStore:w,rateLimiter:_,settings:mt,terminalBufferConfig:E,getAgentId:U,state:l,federation:k,hub:p}=x,q=se==="agent";function C(e){if(y.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*)${M}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function ft(e){const t=O(e);return t?w.validateToken(t):null}function W(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${M}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ct*86400}${r?"; Secure":""}`)}function oe(e){e.header("Set-Cookie",`${M}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}const i=()=>q?async(e,t)=>t():async(e,t)=>{const r=O(e);return!r||!w.validateToken(r)?(v("http_unauthorized",{ip:C(e)}),e.json({error:"unauthorized"},401)):t()},j=()=>q?async(e,t)=>t():async(e,t)=>{const r=O(e);if(!r||!w.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=Xe(t,Fe(),Qe()),o=[],a=new Map,u=new Map;if(p)for(const c of p.agents.list()){const d=p.agents.get(c.agentId);a.set(c.agentId,new Set((d?d.sessions:[]).map(m=>m.name))),u.set(c.agentId,c.online),o.push({agentId:c.agentId,agentName:c.name,online:c.online,sessions:d?d.sessions:[]})}const h=Ze(r,a,u);return{...n,currentSession:e??null,agents:o,tombstones:h}}function $(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 we;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(ut(n,{prefix:"",agentId:null}),e.res)});const B=g.dirname(je(import.meta.url)),ie=[g.join(B,"..","assets"),g.join(B,"assets"),g.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 ie){const n=g.join(r,t);if(!P(n))continue;const o=await ge(n),a=g.extname(t),u={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(o,200,{"Content-Type":u[a]??"application/octet-stream","Cache-Control":a===".js"||a===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const ae=`<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>`,G=e=>e.body(ae,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",G),s.get("/favicon.ico",G);const de={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(de,200,{"Cache-Control":"public, max-age=3600"}));const ue=`// 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(ue,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(ve({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:l.activeTheme}))}),s.get("/",y(),e=>{const t=z();return e.html(_e({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(ke(S.data.notes,l.activeTheme))),s.get("/notes/:session",y(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.html(qe(t,l.activeTheme))}),s.get("/history",y(),e=>{const t=D(),r=new Set(t.map(n=>n.name));return e.html(Te(He(),l.activeTheme,r))}),s.get("/quick-commands",y(),e=>e.html(Ae(te(),l.activeTheme))),s.get("/files",y(),e=>{const t=z();return e.html(xe(l.activeTheme,t))}),s.post("/api/history/clear",o(),async e=>(await Me(),e.json({ok:!0}))),s.get("/api/quick-commands",o(),e=>e.json(te())),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 Je(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 tt(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=>et(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&&!lt(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=Re(a);if(c)return e.json({error:c},400);l.settingUpPassword=!0;try{g.passwordHash=await Oe(a),Ue(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 Ne(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=>(oe(e),e.json({ok:!0}))),s.get("/settings",y(),async e=>{const t=await K();return e.html(Se({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 K(),n=t.defaultView==="recent"?"recent":"default";return await Be({...r,defaultView:n}),e.redirect("/settings?saved=1",303)}),s.get("/settings/theme",y(),e=>e.html(be({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"||!Z(r)?e.redirect("/settings/theme",303):(l.activeTheme=await X(r),e.redirect("/settings/theme?saved=1",303))});const ce={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:Ge.map(t=>({id:t,name:ce[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"||!Z(r)?e.json({error:"invalid theme template"},400):(l.activeTheme=await X(r),e.json({ok:!0,active:l.activeTheme.template}))}),s.get("/api/system/status",o(),e=>e.json(rt())),s.get("/api/system/processes",o(),e=>e.json(nt())),k&&(s.get("/settings/federation",y(),e=>e.redirect("/settings/machines",301)),s.get("/settings/machines",y(),async e=>e.html(Ce({config:k.getConfig(),status:k.status(),tokens:G().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(G().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}=Ie(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 Ee(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=st(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 re(r,n),Y(r,{path:n}),e.json({ok:!0})}catch(i){const u=i instanceof ne?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)?(ot(t,r),ee(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?(it(t),J(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=Ye().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(at(r))return e.json({error:"session already exists"},409);try{re(r,i)}catch(u){const p=u instanceof ne?u.message:"rebuild failed";return e.json({error:p},500)}return Y(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 J(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 ee(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:ye()}).trim()||null;return e.json({program:n})}catch{return e.json({program:null})}}),s.post("/api/session/:name/upload",o(),async e=>{try{const r=(await e.req.parseBody()).file;if(!(r instanceof File))return e.json({error:'expected a file field named "file"'},400);const n=Buffer.from(await r.arrayBuffer()),{path:a}=await $e(n,r.type,r.name);return e.json({path:a})}catch(t){return t instanceof We?e.json({error:t.message},t.status):e.json({error:"upload 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=De(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=T(r).isDirectory()}catch{}u||(a=h.dirname(r),i=h.basename(r).toLowerCase());try{const p=me(a),c=[],d=[];for(const m of p)if(!(i&&!m.toLowerCase().startsWith(i))){try{const f=h.join(a,m);T(f).isDirectory()?(c.push(f),n&&Le(f,c,d,0)):d.push(f)}catch{}if(c.length+d.length>=5e3)break}return c.sort(Q),d.sort(Q),e.json({dirs:c,files:d})}catch{return e.json({dirs:[],files:[]})}});function le(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
|
-
`)){if(!d.trim())continue;const m=d[0],f=d[1],b=d.substring(3).trim(),R=f!==" "?f:m;
|
|
18
|
-
`)){if(!m.trim())continue;const f=m.split(" ");if(f.length>=3){const b=parseInt(f[0],10)||0,R=parseInt(f[1],10)||0;u.set(f[2],{added:b,deleted:R})}}}catch{}for(const d of
|
|
19
|
-
`).length;d.additions=b,
|
|
16
|
+
`;s.get("/sw.js",e=>e.body(ue,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!y.passwordHash,n=e.req.query("error");return e.html(ve({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:l.activeTheme}))}),s.get("/",j(),e=>{const t=Q();return e.html(Ie({theme:l.activeTheme,fsRoots:t,terminalCfg:E,scrollback:E.initialLines+2*E.historyChunk,wsBase:q&&U?.()?`/ws/a/${U()}`:void 0}))}),s.get("/notes",j(),e=>e.html(ke(S.data.notes,l.activeTheme))),s.get("/notes/:session",j(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.html(qe(t,l.activeTheme))}),s.get("/history",j(),e=>{const t=D(),r=new Set(t.map(n=>n.name));return e.html(Te(He(),l.activeTheme,r))}),s.get("/quick-commands",j(),e=>e.html(Ae(te(),l.activeTheme))),s.get("/files",j(),e=>{const t=Q();return e.html(xe(l.activeTheme,t))}),s.post("/api/history/clear",i(),async e=>(await Me(),e.json({ok:!0}))),s.get("/api/quick-commands",i(),e=>e.json(te())),s.post("/api/quick-commands",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Je(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await tt(e.req.param("id"),t);return"error"in r?e.json({error:r.error},r.status):e.json(r)}),s.delete("/api/quick-commands/:id",i(),e=>et(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=_.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 o=typeof n.password=="string"?n.password:"";if(!y.passwordHash){if(!y.security.allowRemoteSetup&&!lt(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=Re(o);if(c)return e.json({error:c},400);l.settingUpPassword=!0;try{y.passwordHash=await Oe(o),Ue(y),v("password_set",{ip:t})}finally{l.settingUpPassword=!1}const{plaintext:d}=w.createAccessToken("setup",y.security.tokenTtlDays);return W(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await Ne(o,y.passwordHash)){const c=_.recordFailure(t);return v("auth_failed",{ip:t,method:"password",failures:c.failures,permanentLock:c.permanentLock}),c.permanentLock?(w.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)}_.recordSuccess(t);const u=`browser-${t}`,{plaintext:h}=w.createAccessToken(u,y.security.tokenTtlDays);return W(e,h),v("auth_success",{ip:t,method:"password",tokenName:u}),e.json({ok:!0,token:h})}),s.post("/api/auth/token",i(),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}`,o=typeof r.ttlDays=="number"?r.ttlDays:y.security.tokenTtlDays,{stored:a,plaintext:u}=w.createAccessToken(n,o);return v("token_created",{ip:t,name:a.name,tokenId:a.id}),e.json({id:a.id,name:a.name,token:u,expiresAt:a.expiresAt})}),s.get("/api/auth/tokens",i(),e=>q?e.json({error:"hub only"},403):e.json(w.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",i(),e=>q?e.json({error:"hub only"},403):w.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",i(),e=>(oe(e),e.json({ok:!0}))),s.get("/settings",j(),async e=>{const t=await Z();return e.html(Se({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",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await Z(),n=t.defaultView==="recent"?"recent":"default";return await Be({...r,defaultView:n}),e.redirect("/settings?saved=1",303)}),s.get("/settings/theme",j(),e=>e.html(be({theme:l.activeTheme,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!J(r)?e.redirect("/settings/theme",303):(l.activeTheme=await Y(r),e.redirect("/settings/theme?saved=1",303))});const ce={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",i(),e=>e.json({active:l.activeTheme.template,templates:Ge.map(t=>({id:t,name:ce[t]}))})),s.post("/api/theme",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!J(r)?e.json({error:"invalid theme template"},400):(l.activeTheme=await Y(r),e.json({ok:!0,active:l.activeTheme.template}))}),s.get("/api/system/status",i(),e=>e.json(rt())),s.get("/api/system/processes",i(),e=>e.json(nt())),k&&(s.get("/settings/federation",j(),e=>e.redirect("/settings/machines",301)),s.get("/settings/machines",j(),async e=>e.html(Ce({config:k.getConfig(),status:k.status(),tokens:V().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})),hostname:k.hostname},l.activeTheme))),s.get("/api/machines",i(),e=>e.json({config:k.getConfig(),status:k.status()})),s.post("/api/machines",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.hub=="string"?t.hub.trim():"",n=typeof t.token=="string"?t.token.trim():"",o=typeof t.name=="string"?t.name.trim().slice(0,64):"",a=t.enabled===!0;if(a){if(!r)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(r))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const u={hub:r||void 0,token:n||void 0,name:o||void 0,enabled:a};return await k.setConfig(u),e.json({ok:!0,status:k.status()})}),s.get("/api/agent-tokens",i(),e=>e.json(V().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})))),s.post("/api/agent-tokens",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim().slice(0,64):"";if(!r)return e.json({error:"name is required"},400);const{id:n,token:o}=_e(r);return e.json({ok:!0,id:n,name:r,token:o})}),s.delete("/api/agent-tokens/:id",i(),e=>{const t=e.req.param("id");return Ee(t)?e.json({ok:!0}):e.json({error:"no such token"},404)})),s.post("/api/system/kill",i(),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=st(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/agents",i(),e=>p?e.json(p.agents.list()):e.json([])),s.post("/api/sessions/new",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(r))return e.json({error:"name contains invalid characters"},400);const n=typeof t.dir=="string"&&t.dir.trim()?t.dir.trim():void 0;if(D().some(a=>a.name===r))return e.json({error:"session already exists"},409);try{return re(r,n),ee(r,{path:n}),e.json({ok:!0})}catch(a){const u=a instanceof ne?a.message:"failed to create session";return e.json({error:u},500)}}),s.post("/api/sessions/rename",i(),async e=>{try{const t=await e.req.json(),{oldName:r,newName:n}=t,o=typeof t.agentId=="string"?t.agentId:void 0;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);if(o){if(!p)return e.json({error:"not a hub"},400);const a=p.getChannel(o);return!a||!a.renameSession(r,n)?e.json({error:"agent offline"},409):(H(r,n,o),e.json({ok:!0,async:!0}))}return ot(r,n),H(r,n),e.json({ok:!0})}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",i(),async e=>{try{const t=await e.req.json(),{name:r}=t,n=typeof t.agentId=="string"?t.agentId:void 0;if(!r)return e.json({error:"name required"},400);if(n){if(!p)return e.json({error:"not a hub"},400);const o=p.getChannel(n);return!o||!o.killSession(r)?e.json({error:"agent offline"},409):(F(r,n),e.json({ok:!0,async:!0}))}return it(r),F(r),e.json({ok:!0})}catch{return e.json({error:"kill failed"},500)}}),s.post("/api/sessions/rebuild",i(),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,a=Ye().find(u=>u.name===r&&(u.agentId??null)===(n??null))?.path;if(n){if(!p)return e.json({error:"not a hub"},400);const u=p.getChannel(n);return u?u.rebuildSession(r,a)?e.json({ok:!0,async:!0}):e.json({error:"agent offline"},409):e.json({error:"agent offline"},409)}if(at(r))return e.json({error:"session already exists"},409);try{re(r,a)}catch(u){const h=u instanceof ne?u.message:"rebuild failed";return e.json({error:h},500)}return ee(r,{path:a}),e.json({ok:!0})}catch{return e.json({error:"rebuild failed"},500)}}),s.post("/api/sessions/dismiss",i(),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 F(r,n),e.json({ok:!0})}catch{return e.json({error:"dismiss failed"},500)}}),s.post("/api/sessions/rename-tombstone",i(),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 o=typeof t.agentId=="string"?t.agentId:void 0;return H(r,n,o),e.json({ok:!0})}catch{return e.json({error:"rename failed"},500)}}),s.get("/api/session-program",i(),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:ye()}).trim()||null;return e.json({program:n})}catch{return e.json({program:null})}}),s.post("/api/session/:name/upload",i(),async e=>{try{const r=(await e.req.parseBody()).file;if(!(r instanceof File))return e.json({error:'expected a file field named "file"'},400);const n=Buffer.from(await r.arrayBuffer()),{path:o}=await $e(n,r.type,r.name);return e.json({path:o})}catch(t){return t instanceof We?e.json({error:t.message},t.status):e.json({error:"upload failed"},500)}}),s.get("/api/fs/session-path",i(),e=>{const t=e.req.query("session");if(!t)return e.json({path:process.env.HOME??"/"});try{const r=De(t),o=r.find(a=>a.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:o})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",i(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=g.join(t,r));const n=e.req.query("recursive")==="true";let o=r,a="",u=r.endsWith("/");if(!u)try{u=T(r).isDirectory()}catch{}u||(o=g.dirname(r),a=g.basename(r).toLowerCase());try{const h=me(o),c=[],d=[];for(const m of h)if(!(a&&!m.toLowerCase().startsWith(a))){try{const f=g.join(o,m);T(f).isDirectory()?(c.push(f),n&&Le(f,c,d,0)):d.push(f)}catch{}if(c.length+d.length>=5e3)break}return c.sort(X),d.sort(X),e.json({dirs:c,files:d})}catch{return e.json({dirs:[],files:[]})}});function le(e){const t={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const r=I("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!r)return t;let n;try{n=I("git branch --show-current 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}catch{n=I("git rev-parse --short HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let o="";try{o=I("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const a=[];for(const d of o.split(`
|
|
17
|
+
`)){if(!d.trim())continue;const m=d[0],f=d[1],b=d.substring(3).trim(),R=f!==" "?f:m;a.push({path:b,status:R,staged:m,unstaged:f,additions:0,deletions:0})}const u=new Map;try{const d=I("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 b=parseInt(f[0],10)||0,R=parseInt(f[1],10)||0;u.set(f[2],{added:b,deleted:R})}}}catch{}for(const d of a){const m=u.get(d.path);m&&(d.additions=m.added,d.deletions=m.deleted)}let h=0,c=0;for(const d of a)h+=d.additions,c+=d.deletions;for(const d of a)if(d.status==="?"&&d.additions===0)try{const m=g.join(r,d.path),b=z(m,"utf-8").split(`
|
|
19
|
+
`).length;d.additions=b,h+=b}catch{}return{repoRoot:r,branch:n,files:a,linesAdded:h,linesRemoved:c}}catch{return t}}return s.get("/api/git/status",i(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=A(t),n=le(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",i(),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=A(t);if(!P(n)||!T(n).isDirectory())return e.json({error:"invalid repository path: "+n},400);try{const o=L("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),a=L("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:o,stagedDiff:a})}catch(o){throw console.error("[git/diff] execFileSync failed:",o?.code,o.message),o}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",i(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=A(t);if(!T(r).isFile())return e.json({error:"not a file"},400);const n=T(r).size;if(n>K)return e.json({error:"file too large",size:n,maxBytes:K},413);const o=z(r,"utf-8");return e.json({path:r,content:o,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",i(),async e=>{try{const t=await e.req.json();if(!t.path||typeof t.content!="string")return e.json({error:"path and content are required"},400);const r=A(t.path);return Pe(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=A(t.path);return T(r).isFile()?(fe(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",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=A(t.path);return P(r)?e.json({error:"file already exists"},409):(he(g.dirname(r),{recursive:!0}),pe(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",i(),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",i(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(Ke(t))}),s.post("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=$(t);return"error"in r?e.json({error:r.error},400):(await ze(r.sessionName,r.windowIndex),e.json(N()))}),s.delete("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=$(t);return"error"in r?e.json({error:r.error},400):(await Ve(r.sessionName,r.windowIndex),e.json(N()))}),s.get("/api/notes",i(),e=>{const t=[...S.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",i(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=S.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",i(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},o=S.data.notes.findIndex(a=>a.scope===t);return o>=0?S.data.notes[o]=n:S.data.notes.push(n),await S.write(),e.json({ok:!0})}),p&&(s.use("/a/:agentId/*",i()),dt(s,{getChannel:p.getChannel})),s}export{Vt as buildApp};
|
package/dist/lib/pages/shell.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSessionModalHTML as f,newSessionModalScript as g,reducedMotion as b}from"../shared-layout.js";import{icon as i}from"../icons.js";function
|
|
1
|
+
import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSessionModalHTML as f,newSessionModalScript as g,reducedMotion as b}from"../shared-layout.js";import{icon as i}from"../icons.js";function v(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function o(t="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${t}; outline: none;`}function y(t){const{theme:e,fsRoots:n,terminalCfg:r,scrollback:a,wsBase:s}=t,l=`
|
|
2
2
|
*, *::before, *::after { box-sizing: border-box; }
|
|
3
3
|
html, body {
|
|
4
4
|
background: var(--page-bg); color: var(--page-fg);
|
|
@@ -449,6 +449,12 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
449
449
|
padding: 8px;
|
|
450
450
|
}
|
|
451
451
|
.settings-popover.open { display: block; }
|
|
452
|
+
.force-narrow-row {
|
|
453
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
454
|
+
padding: 10px 12px; cursor: pointer; font-size: var(--text-sm); color: var(--page-fg);
|
|
455
|
+
}
|
|
456
|
+
.force-narrow-row:hover { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
457
|
+
.force-narrow-row input { width: 16px; height: 16px; accent-color: var(--panel-accent); cursor: pointer; }
|
|
452
458
|
.settings-item {
|
|
453
459
|
display: flex; align-items: center; justify-content: space-between;
|
|
454
460
|
padding: 10px 12px; border-radius: 8px;
|
|
@@ -626,6 +632,17 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
626
632
|
.mobile-keys .mk-buttons { display: none; }
|
|
627
633
|
.mobile-keys.mk-focused .mk-buttons { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
628
634
|
}
|
|
635
|
+
|
|
636
|
+
/* Forced narrow layout (settings toggle): same rules as the 640px
|
|
637
|
+
breakpoint, applied unconditionally when the html has .force-narrow. */
|
|
638
|
+
html.force-narrow .app-layout { flex-direction: column; }
|
|
639
|
+
html.force-narrow .sidebar { flex: 0 0 auto; border-right: none; border-bottom: 1px solid var(--panel-border); transition: max-height 0.2s; }
|
|
640
|
+
html.force-narrow .sidebar.collapsed .sidebar-content { display: none; }
|
|
641
|
+
html.force-narrow .sidebar.collapsed .sidebar-footer { display: none; }
|
|
642
|
+
html.force-narrow .sidebar.collapsed { max-height: 48px; overflow: hidden; }
|
|
643
|
+
html.force-narrow .mobile-keys { display: flex; }
|
|
644
|
+
html.force-narrow .mobile-keys .mk-buttons { display: none; }
|
|
645
|
+
html.force-narrow .mobile-keys.mk-focused .mk-buttons { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
629
646
|
`,x=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),p=JSON.stringify({terminal:r,scrollback:a,theme:e.terminal,fsRoots:n,wsBase:s||""}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
|
|
630
647
|
<html lang="en">
|
|
631
648
|
<head>
|
|
@@ -742,6 +759,10 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
742
759
|
<button class="theme-option" data-theme="warm-clay" title="Warm Clay"><span class="theme-dot" style="background:#b86b52"></span>Warm Clay${e.template==="warm-clay"?'<span class="theme-check">\u2713</span>':""}</button>
|
|
743
760
|
<button class="theme-option" data-theme="dark-cove" title="Dark Cove"><span class="theme-dot" style="background:#7aa2f7"></span>Dark Cove${e.template==="dark-cove"?'<span class="theme-check">\u2713</span>':""}</button>
|
|
744
761
|
</div>
|
|
762
|
+
<label class="force-narrow-row" title="\u7A84\u5C4F\u65F6\u4FA7\u8FB9\u680F\u6298\u53E0\u4E3A\u9876\u680F\u3001\u7EC8\u7AEF\u5360\u6EE1\u3001\u5E95\u90E8\u663E\u793A\u865A\u62DF\u952E\u76D8\u5DE5\u5177\u680F\uFF08\u4EC5\u5F71\u54CD\u672C\u6D4F\u89C8\u5668\uFF09">
|
|
763
|
+
<span>\u5F3A\u5236\u7A84\u5C4F\uFF08\u79FB\u52A8\u5E03\u5C40\uFF09</span>
|
|
764
|
+
<input type="checkbox" id="force-narrow-toggle" />
|
|
765
|
+
</label>
|
|
745
766
|
<hr class="settings-divider" />
|
|
746
767
|
<a href="/settings" class="settings-item" id="set-settings-link" style="cursor:pointer;color:inherit;text-decoration:none">
|
|
747
768
|
<span>Settings</span>
|