tmux-weblink 1.1.7 → 1.2.0
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 +9 -6
- package/dist/browser/terminal-client.js +1 -1
- package/dist/frontend.js +1 -1
- package/dist/index.js +5 -5
- package/dist/lib/agents-watch.js +2 -2
- package/dist/lib/mobile-toolbar.js +4 -4
- package/dist/lib/pages/agents-index.js +8 -8
- package/dist/lib/pages/files-index.js +13 -13
- package/dist/lib/pages/history-index.js +16 -16
- package/dist/lib/pages/landing.js +16 -16
- package/dist/lib/pages/notes-index.js +18 -18
- package/dist/lib/pages/quick-commands.js +15 -15
- package/dist/lib/pages/schedule-index.js +23 -23
- package/dist/lib/pages/settings.js +15 -22
- package/dist/lib/pages/shell.js +70 -19
- package/dist/lib/pages/terminal.js +34 -34
- package/dist/lib/shared-layout.js +41 -42
- package/dist/lib/system-monitor.js +1 -0
- package/dist/lib/theme-store.js +2 -2
- package/dist/lib/watched-panes.js +1 -1
- package/package.json +1 -1
- package/dist/assets/chunk-O3DBTEWH.js +0 -4
- package/dist/assets/terminal-client.js +0 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
var Ke="https://esm.sh/ghostty-web@0.4.0",Xe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function W(t){return t.replace(Xe,"")}var q;function _e(){return q||(document.querySelector("link[data-tmux-web-xterm-css]")?q=Promise.resolve():(q=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),q))}var me=class xe{terminal;fitAddon;container;constructor(e,a,o){this.container=e,this.terminal=a,this.fitAddon=o,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,a,o){let[{Terminal:s},{FitAddon:c}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),_e()]),u=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,theme:o});return new xe(e,u,new c)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(a=>this.terminal.write(e,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let e=pe(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}},Ue=class ke{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,a){this.container=e,this.terminal=a,this.terminal.open(e)}static async create(e,a,o){let s=await import(Ke);await s.init();let c=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,theme:o});return new ke(e,c)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(a=>this.terminal.write(e,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){let e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let e=pe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let a=Math.floor(e.width/this.charW),o=Math.floor(e.height/this.charH);if(a<10||o<5)return!1;if(a!==this.colsValue||o!==this.rowsValue){this.colsValue=a,this.rowsValue=o,this.terminal.resize(a,o);for(let s of this.resizeCallbacks)s({cols:a,rows:o})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let a=this.terminal.getMode?.(2004)??!1;this.input(a?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(a=>e(a))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){let a=this.container.querySelector("canvas");if(a instanceof HTMLElement&&a.offsetWidth>0&&a.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let o=a.offsetWidth/this.colsValue,s=a.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=o,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function pe(t){let e=t.getBoundingClientRect(),a=window.visualViewport;return a?{width:Math.min(e.width,a.width),height:Math.max(0,Math.min(e.height,a.height-Math.max(0,e.top)))}:e}function Je(t,e,a){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let o;if(a.renderer==="ghostty")try{o=await Ue.create(t,a.scrollback,a.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),o=await me.create(t,a.scrollback,a.theme)}else o=await me.create(t,a.scrollback,a.theme);let s,c=0,u,m=null,J=0,b="connecting",N=0,j=!1,A=[],C="",$=!1,B=1e3,Te=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),Ce="/api/session/"+encodeURIComponent(e)+"/upload",Be=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Se(){return A.join("")+C}async function Me(n,i=0){let r=n?o.viewportY():0,h=n?o.baseY():0,v=W(Se());if(o.reset(),!v){n||o.scrollToBottom();return}await o.write(v),n?o.scrollToLine(h+r+i):o.scrollToBottom()}function S(n){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(n))}function Ie(n){let i;try{i=JSON.parse(n)}catch{b==="live"&&(C+=n,o.write(W(n)));return}if(i.type==="auth.required"){let r=localStorage.getItem("tmux-web-token");r?S({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(i.type==="auth.ok"){i.token&&localStorage.setItem("tmux-web-token",i.token),B=1e3,M(),S({type:"resize",cols:o.cols,rows:o.rows}),p();return}if(i.type==="auth.failed"){if(i.permanentLock||i.retryAfterMs&&i.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(i.message);return}return}if(i.type==="snapshot"&&typeof i.data=="string"){let r=W(i.data);A=[r],C="",N=typeof i.lines=="number"?i.lines:a.terminal.initialLines,b="live",o.reset(),o.write(r).then(()=>o.scrollToBottom());return}if(i.type==="window_changed"&&typeof i.activeIndex=="number"){let r=new URL(location.href);r.searchParams.get("window")!==String(i.activeIndex)&&(r.searchParams.set("window",String(i.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:i.windows}));return}if(i.type==="history"&&typeof i.data=="string"){j=!1,i.lines>0&&i.data&&(A.unshift(i.data),N+=i.lines,Me(!0,i.lines));return}if(i.type==="data"&&typeof i.data=="string"){let r=W(i.data);if(b==="connecting"){b="live",C=r,o.write(r);return}b==="live"&&(C+=r,o.write(r))}}function ne(){let n=Te,i=localStorage.getItem("tmux-web-token");i&&(n+=(n.includes("?")?"&":"?")+"token="+encodeURIComponent(i)),s=new WebSocket(n),s.onopen=()=>{b="connecting",N=0,j=!1,A=[],C="",o.reset(),B=1e3},s.onmessage=r=>{typeof r.data=="string"&&Ie(r.data)},s.onclose=()=>{$||(b="connecting",setTimeout(()=>{B=Math.min(B*2,1e4),$||ne()},B))},s.onerror=()=>s?.close()}function R(n){S({type:"input",data:n})}function M(){return o.fit()}function Z(){t.classList.remove("terminal-pending")}function p(){c&&cancelAnimationFrame(c),u&&clearTimeout(u),M(),c=requestAnimationFrame(()=>{c=0,M()&&Z()}),u=setTimeout(()=>{M()&&Z()},120)}function He(){p(),setTimeout(p,50),setTimeout(p,150),setTimeout(p,300)}function ie(n){return typeof n=="string"&&n.startsWith("image/")}function ae(n){return n?.files?.length?n.files[0]??null:null}function De(n){if(!n?.items)return null;for(let i=0;i<n.items.length;i++){let r=n.items[i];if(r?.kind==="file"&&ie(r.type))return r.getAsFile()}return null}async function ze(n){let i=new FormData,r=n instanceof File&&n.name?n.name:"upload";i.append("file",n,r);let h=localStorage.getItem("tmux-web-token"),v={};h&&(v.Authorization="Bearer "+h);let Q=await fetch(Ce,{method:"POST",body:i,headers:v});if(!Q.ok){let de="upload failed";try{let ue=await Q.json();ue.error&&(de=ue.error)}catch{}throw new Error(de)}let ce=await Q.json();if(!ce.path)throw new Error("no path in response");return ce.path}async function G(n){try{let i=await ze(n);R(i)}catch{}}o.onData(n=>R(n)),o.onResize(({cols:n,rows:i})=>S({type:"resize",cols:n,rows:i})),o.onScroll(()=>{b!=="live"||j||!o.isNearScrollbackTop()||(j=!0,S({type:"load_history",before:N}))}),o.attachCustomKeyEventHandler(n=>n.type!=="keydown"?!0:(n.ctrlKey||n.metaKey)&&n.code==="KeyV"?(n.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let i=await navigator.clipboard.read();for(let r of i)for(let h of r.types)if(ie(h)){let v=await r.getType(h);await G(v);return}}}catch{}try{let i=await navigator.clipboard?.readText();i&&o.pasteText(oe(i))}catch{}})(),!1):!0);let O=0;function oe(n){return n.split(`\r
|
|
2
|
+
`).join(`
|
|
3
|
+
`).split(`
|
|
4
|
+
`).join("\r")}async function Ye(n){let i=De(n.clipboardData);if(i){n.preventDefault(),n.stopPropagation();let v=Date.now();if(v-O<50)return;O=v,await G(i);return}let r=n.clipboardData?.getData("text/plain");if(!r)return;n.preventDefault(),n.stopPropagation();let h=Date.now();h-O<50||(O=h,o.pasteText(oe(r)))}let I=0;function Pe(n){n.preventDefault(),I++,ae(n.dataTransfer)&&t.classList.add("terminal-drag-over")}function Ve(n){n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="copy")}function Fe(n){n.preventDefault(),I--,I<=0&&(I=0,t.classList.remove("terminal-drag-over"))}async function Ne(n){n.preventDefault(),I=0,t.classList.remove("terminal-drag-over");let i=ae(n.dataTransfer);i&&await G(i)}function se(n){!Be||n.key!=="Escape"||n.defaultPrevented||n.repeat||n.metaKey||n.ctrlKey||n.altKey||o.isFocused()&&(n.preventDefault(),n.stopPropagation(),R("\x1B"))}t.addEventListener("paste",Ye),t.addEventListener("dragenter",Pe),t.addEventListener("dragover",Ve),t.addEventListener("dragleave",Fe),t.addEventListener("drop",Ne),document.addEventListener("keydown",se,!0);function je(n){if(n.touches.length!==1){m=null;return}let i=n.touches[0];i&&(m={startX:i.clientX,startY:i.clientY,lastX:i.clientX,lastY:i.clientY,scrolling:!1},n.stopPropagation())}function Ae(n){if(!m||n.touches.length!==1)return;let i=n.touches[0];if(!i)return;let r=i.clientY-m.startY,h=i.clientX-m.startX;if(!m.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(h))return;m.scrolling=!0,J=Date.now()+500}n.preventDefault(),n.stopPropagation(),qe(-(i.clientY-m.lastY),i.clientX,i.clientY),m.lastX=i.clientX,m.lastY=i.clientY}function Re(n){if(!m)return;let i=m.scrolling;m=null,n.stopPropagation(),i?(J=Date.now()+500,n.preventDefault()):o.focus()}function Oe(n){m=null,n.stopPropagation()}function We(n){n.pointerType==="touch"&&Date.now()<J&&(n.preventDefault(),n.stopPropagation())}function qe(n,i,r){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:n,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:i,clientY:r,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",je,{passive:!0,capture:!0}),t.addEventListener("touchmove",Ae,{passive:!1,capture:!0}),t.addEventListener("touchend",Re,{passive:!1,capture:!0}),t.addEventListener("touchcancel",Oe,{passive:!0,capture:!0}),t.addEventListener("pointerup",We,!0);function re(){p()}window.addEventListener("resize",re),window.visualViewport?.addEventListener("resize",p),window.visualViewport?.addEventListener("scroll",p);let le=new ResizeObserver(()=>p());return le.observe(t),document.fonts?.ready.then(()=>{p(),setTimeout(p,50),setTimeout(p,200)}),t.addEventListener("focusin",He,!0),requestAnimationFrame(()=>{M()&&Z(),ne(),p()}),{destroy(){if($=!0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",re),le.disconnect(),document.removeEventListener("keydown",se,!0)},focus(){o.focus()},sendInput(n){R(n)}}})()}var H=window.__TMUX_WEB_SHELL__,l=null,w=null,$e="sessions",E="",K="",X="",he="",d=document.getElementById("sidebar-content"),D=document.getElementById("main-placeholder"),L=document.getElementById("terminal-container"),_=document.getElementById("file-editor"),Ze=document.getElementById("fe-path"),f=document.getElementById("fe-status"),z=document.getElementById("fe-content"),x=document.getElementById("fe-save"),Ge=document.getElementById("fe-delete"),Qe=document.getElementById("fe-back"),fe=document.getElementById("fe-new-name"),et=document.getElementById("fe-new-btn"),ee=document.getElementById("settings-backdrop"),ve=document.getElementById("settings-popover"),ye=document.querySelector(".sidebar");function tt(){return window.matchMedia("(max-width: 640px)").matches}function we(){tt()&&ye.classList.add("collapsed")}function ge(){ye.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{ge(),be("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{ge(),be("files")});function be(t){$e=t,document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?Y():nt()}async function Y(){d.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let a of e){let o=document.createElement("div");o.className="session-item"+(a.name===w?" active":""),o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${a.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>
|
|
5
|
+
<span>${P(a.name)}</span>
|
|
6
|
+
<button class="session-edit-btn" data-session="${P(a.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>
|
|
7
|
+
`,o.addEventListener("click",c=>{c.target.closest(".session-edit-btn")||te(a.name)}),d.appendChild(o);let s=o.querySelector(".session-edit-btn");s&&s.addEventListener("click",c=>{c.stopPropagation(),rt(a.name,s)})}}catch{d.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function P(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function te(t){if(!(w===t&&l)){_.style.display="none",D.style.display="none",L.style.display="",L.classList.add("terminal-pending"),we(),l&&(l.destroy(),l=null),w=t;try{l=await Je(L,t,{terminal:H.terminal,scrollback:H.scrollback,theme:H.theme,renderer:H.renderer})}catch(e){console.error("[shell] terminal init failed:",e);let a=e&&typeof e=="object"&&"message"in e?e.message:String(e);L.textContent="Failed to open terminal for "+t+": "+a,L.classList.remove("terminal-pending")}}}var U=[];function nt(){if(U=H.fsRoots||[],U.length===0){if(w){it(w);return}d.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}d.innerHTML="";for(let t of U){let e=document.createElement("div");e.className="file-tree-item",e.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> ${P(t)}`,e.addEventListener("click",()=>k(t)),d.appendChild(e)}}async function it(t){d.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();e.path?k(e.path):d.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{d.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function k(t){E=t,d.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/list?path="+encodeURIComponent(t))).json();d.innerHTML="";let a=at(t);if(a&&!U.some(s=>t===s)){let s=document.createElement("div");s.className="file-tree-item",s.innerHTML="..",s.addEventListener("click",()=>k(a)),d.appendChild(s)}if(e.dirs)for(let s of e.dirs){let c=s.split("/").pop()||s.split("\\").pop(),u=document.createElement("div");u.className="file-tree-item",u.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> ${P(c)}`,u.addEventListener("click",()=>k(s)),d.appendChild(u)}else d.innerHTML="";let o=e.files||[];for(let s of o){let c=s.split("/").pop()||s.split("\\").pop(),u=document.createElement("div");u.className="file-tree-item file",u.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${P(c)}`,u.addEventListener("click",()=>ot(s)),d.appendChild(u)}}catch{d.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function at(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function ot(t){K=t,L.style.display="none",l&&(l.destroy(),l=null,w=null),we(),D.style.display="none",_.style.display="flex",Ze.textContent=t,f.textContent="Loading...",x.style.display="none";try{let e=await fetch("/api/file?path="+encodeURIComponent(t));if(!e.ok){let o=await e.json();f.textContent=o.error||"Failed to load",z.value="";return}let a=await e.json();z.value=a.content,X=a.content,he=a.content,f.textContent=a.size+" bytes",x.style.display=""}catch{f.textContent="Failed to load",z.value=""}}z.addEventListener("input",()=>{X=z.value}),x.addEventListener("click",async()=>{x.textContent="Saving...",x.disabled=!0;try{let t=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:K,content:X})});if(!t.ok){let e=await t.json();f.textContent=e.error||"Save failed";return}he=X,f.textContent="Saved"}catch{f.textContent="Save failed"}finally{x.disabled=!1,x.textContent="Save"}}),Ge.addEventListener("click",async()=>{if(confirm("Delete "+K+"?"))try{let t=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:K})});if(!t.ok){let e=await t.json();f.textContent=e.error||"Delete failed";return}_.style.display="none",D.style.display="flex",E&&k(E)}catch{f.textContent="Delete failed"}}),Qe.addEventListener("click",()=>{_.style.display="none",D.style.display="flex",E&&k(E)}),et.addEventListener("click",async()=>{let t=fe.value.trim();if(!t)return;let e=E+"/"+t;try{let a=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!a.ok){let o=await a.json();f.textContent=o.error||"Create failed";return}fe.value="",E&&k(E)}catch{f.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=ve.classList.toggle("open");ee.classList.toggle("open",t)}),ee.addEventListener("click",()=>{ve.classList.remove("open"),ee.classList.remove("open")});var T="",V=document.createElement("div");V.className="session-popover-backdrop",document.body.appendChild(V);var g=document.createElement("div");g.id="session-popover",g.className="session-popover",g.style.display="none",g.innerHTML=`<div class="sp-field">
|
|
5
8
|
<label>Rename session</label>
|
|
6
9
|
<input type="text" id="sp-name" />
|
|
7
10
|
</div>
|
|
@@ -10,5 +13,5 @@ import{a as P}from"./chunk-O3DBTEWH.js";var g=window.__TMUX_WEB_SHELL__,s=null,r
|
|
|
10
13
|
<button class="btn" id="sp-cancel">Cancel</button>
|
|
11
14
|
</div>
|
|
12
15
|
<hr class="sp-divider" />
|
|
13
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(
|
|
14
|
-
`).length;
|
|
16
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(g);function st(t){let e=t.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function rt(t,e){T=t;let a=document.getElementById("sp-name");a.value=t,V.classList.add("open"),g.style.display="block",st(e),setTimeout(()=>a.focus(),50)}function F(){V.classList.remove("open"),g.style.display="none"}V.addEventListener("click",F),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===T){F();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:T,newName:t})})).ok)return;F(),Y(),w===T&&te(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+T+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:T})})).ok)return;F(),w===T&&(l&&(l.destroy(),l=null),w=null,L.style.display="none",D.style.display="flex"),Y()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",F);var lt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!l)return;let a=lt[e.dataset.key];a&&(l.sendInput(a),l.focus())});var y=document.getElementById("mk-input"),Ee=document.getElementById("mk-send");function Le(){if(!y||!l)return;let t=y.value;t&&(l.sendInput(t+"\r"),y.value="",y.rows=1,l.focus())}if(y&&(y.addEventListener("input",()=>{y.rows=1;let t=y.value.split(`
|
|
17
|
+
`).length;y.rows=Math.min(t,4)}),y.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),Le())})),Ee&&Ee.addEventListener("click",Le),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}window.__openSession=te,window.__refreshSidebar=Y,Y();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{initTerminal as
|
|
1
|
+
import{initTerminal as o}from"./terminal-core.js";const e=window.__TMUX_WEB_TERMINAL__;if(!e)throw new Error("missing tmux-web terminal config");const i=document.getElementById("terminal-container");if(!i)throw new Error("missing terminal container");const{sessionName:r,...m}=e;o(i,r,m).then(n=>{window.tmuxWeb={sendInput:t=>n.sendInput(t),focusTerminal:()=>n.focus()}});
|
package/dist/frontend.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{renderLoginPage as o}from"./lib/pages/login.js";import{renderNotesIndex as d}from"./lib/pages/notes-index.js";import{renderNotesPage as x}from"./lib/pages/notes-page.js";import{renderSettings as f,renderThemeSettings as p}from"./lib/pages/settings.js";import{renderScheduleIndex as g}from"./lib/pages/schedule-index.js";import{renderHistoryIndex as a}from"./lib/pages/history-index.js";import{renderQuickCommandsPage as I}from"./lib/pages/quick-commands.js";import{renderFilesIndex as h}from"./lib/pages/files-index.js";import{renderShell as c}from"./lib/pages/shell.js";export{h as renderFilesIndex,a as renderHistoryIndex,o as renderLoginPage,d as renderNotesIndex,x as renderNotesPage,I as renderQuickCommandsPage,g as renderScheduleIndex,f as renderSettings,c as renderShell,p as renderThemeSettings};
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{chmodSync as
|
|
2
|
+
import{chmodSync as Fe,existsSync as V,mkdirSync as Ue,readdirSync as de,readFileSync as We,statSync as N,unlinkSync as $e,writeFileSync as Be}from"node:fs";import{readFile as Ve}from"node:fs/promises";import{createRequire as Ge}from"node:module";import w from"node:path";import{fileURLToPath as ze}from"node:url";import{Hono as Ke}from"hono";import{serve as Qe}from"@hono/node-server";import{WebSocketServer as Xe,WebSocket as ue}from"ws";import*as Ye from"node-pty";try{const e=w.dirname(Ge(import.meta.url).resolve("node-pty/package.json")),t=w.join(e,"prebuilds");if(V(t))for(const r of de(t)){const n=w.join(t,r,"spawn-helper");V(n)&&!(N(n).mode&73)&&Fe(n,493)}}catch{}import{listSessions as q}from"./sessions.js";import{renderLoginPage as Je,renderNotesIndex as Ze,renderNotesPage as et,renderSettings as tt,renderThemeSettings as rt,renderScheduleIndex as nt,renderHistoryIndex as ot,renderQuickCommandsPage as st,renderFilesIndex as it,renderShell as at}from"./frontend.js";import{loadSecurityConfig as dt,saveSecurityConfig as ce}from"./lib/security-config.js";import{hashPassword as le,verifyPassword as me,validatePassword as pe,TokenStore as ut}from"./lib/auth.js";import{RateLimiter as ct}from"./lib/rateLimiter.js";import{atomicWriteFileSync as lt}from"./lib/atomicWrite.js";import{resolveFsPath as G,resolveFsRoots as fe,MAX_FILE_BYTES as he,walkRecursive as mt}from"./lib/fs-access.js";import{captureSessionWindowsWithPath as pt}from"./lib/tmux-windows.js";import{audit as c}from"./lib/auditLog.js";import{db as g}from"./lib/db.js";import{getSessionAccessMap as I}from"./lib/session-access.js";import{listWindowHistory as ft,clearWindowHistory as ht}from"./lib/window-history.js";import{loadExtensions as yt,spawnExtensionBackend as wt,registerExtensionRoutes as gt}from"./lib/ext-loader.js";import{SchedulerService as jt,isValidScheduleInput as kt,isValidRescheduleInput as vt}from"./lib/scheduler.js";import{getScheduleDelayError as ye}from"./lib/schedule-delay.js";import{handleClientMessage as xt}from"./lib/ws-message.js";import{loadDotEnv as Tt}from"./lib/load-env.js";import{cmdAdd as St,cmdRemove as bt,cmdList as qt,cmdSetup as At,cmdTheme as It,printUsage as we,printVersion as _t}from"./lib/cli.js";import{readSettings as Z,writeSettings as Ct}from"./lib/settings.js";import{readActiveTheme as Rt,setActiveThemeTemplate as ge}from"./lib/theme-store.js";import{isThemeTemplateId as je,THEME_TEMPLATE_IDS as Et}from"./lib/themes/index.js";import{installPlugin as Mt,uninstallPlugin as Ot}from"./lib/plugins.js";import{buildCommandbarSessions as C}from"./lib/commandbar.js";import{pinView as Lt,unpinView as Pt,listPinnedViews as Nt}from"./lib/pinned-views.js";import{listWindowLabels as Dt,setWindowLabel as Ht}from"./lib/window-labels.js";import{captureAndStoreWindows as ke,getStoredWindows as ve}from"./lib/session-windows.js";import{acquireControlClient as Ft,killAllControlClients as Ut}from"./lib/tmux-control.js";import{buildSidebarSessions as Wt}from"./lib/sessions-sidebar.js";import{createQuickCommand as $t,deleteQuickCommand as Bt,listQuickCommands as xe,updateQuickCommand as Vt}from"./lib/quick-commands.js";import{getSessionPaneTarget as Gt,capturePaneTail as zt,capturePaneHistoryChunk as Kt,isAlternateScreen as Qt,toCrlf as Te}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Xt}from"./lib/terminal-config.js";import{ImageUploadError as Yt,saveUploadedImage as Jt}from"./lib/image-upload.js";import{getSystemStatus as Zt}from"./lib/system-monitor.js";import{listSessionWindows as er,selectSessionWindow as tr,newSessionWindow as rr,renameSessionWindow as nr,newTmuxSession as or,renameSession as sr,killSession as ir,TmuxWindowsError as z}from"./lib/tmux-windows.js";Tt();const K=Xt(),l=dt(),f=new ut,R=new ct;let D=!1;const Q="tmux-web-token",ar=365;function Se(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function X(e){if(l.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 dr(e){if(l.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function ee(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*)${Q}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function be(e){const t=e.headers.authorization;if(typeof t=="string"&&t.startsWith("Bearer "))return t.slice(7);const r=e.headers.cookie;if(typeof r!="string")return null;const n=r.match(new RegExp(`(?:^|;\\s*)${Q}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function un(e){const t=ee(e);return t?f.validateToken(t):null}function cn(e){const t=be(e);return t?f.validateToken(t):null}function qe(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${Q}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ar*86400}${r?"; Secure":""}`)}function ur(e){e.header("Set-Cookie",`${Q}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}function a(){return async(e,t)=>{const r=ee(e);return!r||!f.validateToken(r)?(c("http_unauthorized",{ip:X(e)}),e.json({error:"unauthorized"},401)):t()}}function cr(e){const t=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${t}`,302)}function _(){return async(e,t)=>{const r=ee(e);return!r||!f.validateToken(r)?cr(e):t()}}const H=new Map;function lr(e){let t=0;for(const r of H.values())r.ip===e&&t++;return t}function te(e,t,r){try{e.close(t,r)}catch{}}function ln(e,t){e.readyState===ue.OPEN&&e.send(JSON.stringify(t))}function mr(e,t){let r="xterm";(t==="ghostty"||t==="xterm")&&(r=t);const n=process.env.TMUX_WEB_TERMINAL_RENDERER?.trim().toLowerCase();(n==="ghostty"||n==="xterm")&&(r=n);for(const i of e)i==="--ghostty"&&(r="ghostty"),i==="--xterm"&&(r="xterm");return r}const pr=7;function Ae(e){return typeof e!="number"||!Number.isFinite(e)?pr:Math.min(365,Math.max(1,Math.round(e)))}const Ie=process.argv.slice(2);{const e=Ie.filter(t=>t!=="--ghostty"&&t!=="--xterm");if(e.length>0){const[t,r]=e;switch(t){case"add":r||(console.error("usage: tmux-web add <package>"),process.exit(1)),await St(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await bt(r),process.exit(0);case"list":case"ls":await qt(),process.exit(0);case"setup":await At(e),process.exit(0);case"theme":await It(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":we(),process.exit(0);case"-V":case"--version":case"-v":_t(),process.exit(0);default:console.error(`unknown argument: ${t}`),we(),process.exit(1)}}}function v(e,t){e.readyState===ue.OPEN&&e.send(JSON.stringify(t))}const F=new Set,_e=[];await g.read(),g.data.sessionAccess??=[],g.data.pinnedViews??=[],g.data.watchedPanes??=[],g.data.triggeredTasks??=[],g.data.quickCommands??=[];const re=await Z();let j=await Rt();const h=re.commandbar===!0,ne=mr(Ie,re.terminalRenderer),Ce=Ae(re.scheduleHistoryDays),Re=w.join(process.cwd(),"extensions"),Ee=await yt(Re);for(const e of Ee)e.start&&_e.push(wt(e.dir,e));const E=new jt({db:g,historyRetentionMs:Ce*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await E.restoreFromDb();const s=new Ke;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'","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self'","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()}),gt(s,Re,Ee);const fr=w.dirname(ze(import.meta.url)),hr=[w.join(fr,"assets"),w.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 hr){const n=w.join(r,t);if(!V(n))continue;const i=await Ve(n),o=w.extname(t),d={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(i,200,{"Content-Type":d[o]??"application/octet-stream"})}return e.notFound()});const yr=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
3
3
|
<rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
|
|
4
4
|
<path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
5
|
<line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
|
|
6
|
-
</svg>`,Le=e=>e.body(Ar,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Le),s.get("/favicon.ico",Le),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!l.passwordHash,n=e.req.query("error");return e.html(rt({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:y}))}),s.get("/",I(),e=>{const t=p?C(v(),A()):[],r=je();return e.html(mt({theme:y,commandbarEnabled:p,commandbarSessions:t,agentsEnabled:_,fsRoots:r,terminalCfg:B,renderer:Z,scrollback:B.initialLines+2*B.historyChunk}))}),s.get("/s/:session",I(),async e=>{const t=decodeURIComponent(e.req.param("session"));await jt(t),oe(t);const r=v(),n=A(),a=p?C(r,n):[];return e.html(tt(t,de,{commandbarEnabled:p,commandbarSessions:a,quickCommands:p?se():[],agentsEnabled:_,terminal:B,theme:y,renderer:Z}))}),s.get("/notes",I(),e=>{const t=p?C(v(),A()):[];return e.html(nt(j.data.notes,y,p,t,_))}),s.get("/notes/:session",I(),e=>{const t=decodeURIComponent(e.req.param("session")),r=p?C(v(),A()):[];return e.html(ot(t,y,p,r))}),s.get("/schedule",I(),e=>{const t=p?C(v(),A()):[];return e.html(it(M.list(),M.listTriggered(),y,Oe,p,t,_))}),s.get("/agents",I(),e=>{if(!_)return e.redirect("/settings",303);const t=p?C(v(),A()):[];return e.html(dt(y,p,t))}),s.get("/history",I(),e=>{const t=v(),r=p?C(t,A()):[],n=new Set(t.map(a=>a.name));return e.html(ut(kt(),y,p,r,_,n))}),s.get("/quick-commands",I(),e=>{const t=p?C(v(),A()):[];return e.html(ct(se(),y,p,t,_))}),s.get("/files",I(),e=>{const t=p?C(v(),A()):[],r=je();return e.html(lt(y,p,t,_,r))}),s.post("/api/history/clear",i(),async e=>(await vt(),e.json({ok:!0}))),s.get("/api/quick-commands",i(),e=>e.json(se())),s.post("/api/quick-commands",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Qt(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Yt(e.req.param("id"),t);return"error"in r?e.json({error:r.error},r.status):e.json(r)}),s.delete("/api/quick-commands/:id",i(),async e=>await Xt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.get("/api/agents",i(),async e=>{if(!_)return e.json({error:"agents page disabled"},403);const t=Me?yr():await fr();return e.json(t)}),s.post("/api/auth/password",async e=>{const t=J(e),r=E.check(t);if(!r.allowed)return c("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const a=typeof n.password=="string"?n.password:"";if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Ae(t))return c("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(W)return e.json({error:"Password setup in progress"},409);const f=ge(a);if(f)return e.json({error:f},400);W=!0;try{l.passwordHash=await ye(a),he(l),c("password_set",{ip:t})}finally{W=!1}const{plaintext:T}=h.createAccessToken("setup",l.security.tokenTtlDays);return _e(e,T),e.json({ok:!0,token:T,setupMode:!0})}if(!await we(a,l.passwordHash)){const f=E.recordFailure(t);return c("auth_failed",{ip:t,method:"password",failures:f.failures,permanentLock:f.permanentLock}),f.permanentLock?(h.revokeAll(),c("permanent_lock",{ip:t,failures:f.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):f.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:f.retryAfterMs},429)}E.recordSuccess(t);const d=`browser-${t}`,{plaintext:S}=h.createAccessToken(d,l.security.tokenTtlDays);return _e(e,S),c("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:S})}),s.post("/api/auth/token",i(),async e=>{const t=J(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,a=typeof r.ttlDays=="number"?r.ttlDays:l.security.tokenTtlDays,{stored:o,plaintext:d}=h.createAccessToken(n,a);return c("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",i(),e=>e.json(h.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",i(),e=>h.revoke(e.req.param("id"))?(c("token_revoked",{ip:J(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",i(),e=>(kr(e),e.json({ok:!0}))),s.get("/settings",I(),async e=>{const t=await ne(),r=t.terminalRenderer??"xterm";return e.html(st({settings:t,renderer:Z,rendererOverridden:Z!==r,theme:y,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await ne(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",a=t.defaultView==="recent"?"recent":"default",o=Ce(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Lt({...r,commandbar:t.commandbar!==void 0,agents:t.agents!==void 0,agentsBackgroundWatch:t.agentsBackgroundWatch!==void 0,terminalRenderer:n,defaultView:a,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const a=r==="remove"?await Ht(n):r==="add"?await Wt(n):{ok:!1,output:"unknown action"};return a.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(a.output.slice(0,800)),303)}),s.get("/settings/theme",I(),e=>e.html(at({theme:y,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!Te(r)?e.redirect("/settings/theme",303):(y=await Se(r),e.redirect("/settings/theme?saved=1",303))});const Ir={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",i(),e=>e.json({active:y.template,templates:Dt.map(t=>({id:t,name:Ir[t]}))})),s.post("/api/theme",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!Te(r)?e.json({error:"invalid theme template"},400):(y=await Se(r),e.json({ok:!0,active:y.template}))}),s.get("/api/sessions",i(),e=>p?e.json(C(v(),A())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(r))return e.json({error:"name contains invalid characters"},400);const n=typeof t.dir=="string"&&t.dir.trim()?t.dir.trim():void 0;if(v().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return ur(r,n),e.json({ok:!0})}catch(o){const d=o instanceof X?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",i(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(cr(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",i(),async e=>{try{const{name:t}=await e.req.json();return t?(lr(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",i(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=gt(t),a=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:a})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",i(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=g.join(t,r));const n=e.req.query("recursive")==="true";let a=r,o="",d=r.endsWith("/");if(!d)try{d=D(r).isDirectory()}catch{}d||(a=g.dirname(r),o=g.basename(r).toLowerCase());try{const S=pe(a),f=[],T=[];for(const O of S)if(!O.startsWith(".")&&!(o&&!O.toLowerCase().startsWith(o))){try{const b=g.join(a,O);D(b).isDirectory()?(f.push(b),n&&wt(b,f,T,0)):T.push(b)}catch{}if(f.length+T.length>=5e3)break}return e.json({dirs:f,files:T})}catch{return e.json({dirs:[],files:[]})}}),s.get("/api/file",i(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=Q(t);if(!D(r).isFile())return e.json({error:"not a file"},400);const n=D(r).size;if(n>ke)return e.json({error:"file too large",size:n,maxBytes:ke},413);const a=Ve(r,"utf-8");return e.json({path:r,content:a,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",i(),async e=>{try{const t=await e.req.json();if(!t.path||typeof t.content!="string")return e.json({error:"path and content are required"},400);const r=Q(t.path);return yt(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=Q(t.path);return D(r).isFile()?(Ge(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=Q(t.path);return K(r)?e.json({error:"file already exists"},409):(Be(g.dirname(r),{recursive:!0}),ze(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function ue(e){return{...Kt(v(),A(),$t()),currentSession:e??null}}function Ne(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",i(),e=>{const t=e.req.query("currentSession");return e.json(ue(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",i(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(be(t))}),s.post("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Ne(t);return"error"in r?e.json({error:r.error},400):(await Ut(r.sessionName,r.windowIndex),e.json(ue()))}),s.delete("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Ne(t);return"error"in r?e.json({error:r.error},400):(await Ft(r.sessionName,r.windowIndex),e.json(ue()))}),s.get("/api/notes",i(),e=>{const t=[...j.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",i(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=j.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",i(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},a=j.data.notes.findIndex(o=>o.scope===t);return a>=0?j.data.notes[a]=n:j.data.notes.push(n),await j.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",i(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!v().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const a=n.file;if(!(a instanceof File))return e.json({error:"missing file field"},400);try{const o=await a.arrayBuffer(),d=Buffer.from(o),{path:S}=await or(d,a.type||void 0,a.name||void 0);return e.json({path:S})}catch(o){return o instanceof nr?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",i(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Bt(t).map(o=>[o.windowIndex,o.label])),n=new Map(be(t).map(o=>[o.index,o])),a=sr(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(a)}),s.post("/api/session/:session/window-label",i(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const a=typeof r.label=="string"?r.label:"",o=await Vt(t,n,a);return e.json(o)}),s.post("/api/session/:session/select-window",i(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return ar(t,n),ee(t),e.json({ok:!0})}catch(a){return a instanceof X?e.json({error:a.message},a.status):(console.error("[select-window]",a),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",i(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof r.name!="string"||!r.name.trim())return e.json({error:"name is required"},400);try{return dr(t,n,r.name),oe(t),e.json({ok:!0})}catch(a){return a instanceof X?e.json({error:a.message},a.status):(console.error("[rename-window]",a),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",i(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return ir(t),ee(t),oe(t),e.json({ok:!0})}catch(r){return r instanceof X?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",i(),e=>e.json(M.list(e.req.query("session")))),s.post("/api/schedule",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ve(t);if(r)return e.json({error:r},400);if(!qt(t))return e.json({error:"invalid body"},400);const n=await M.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",i(),async e=>await M.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ve(t);if(r)return e.json({error:r},400);if(!At(t))return e.json({error:"invalid body"},400);const n=await M.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const _r=parseInt(process.env.PORT||"21000",10),Cr=Je({fetch:s.fetch,port:_r,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)});Me&&hr();const ce=new Ze({noServer:!0});function De(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
|
|
6
|
+
</svg>`,Me=e=>e.body(yr,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Me),s.get("/favicon.ico",Me),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!l.passwordHash,n=e.req.query("error");return e.html(Je({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:j}))}),s.get("/",_(),e=>{const t=h?C(q(),I()):[],r=fe();return e.html(at({theme:j,commandbarEnabled:h,commandbarSessions:t,fsRoots:r,terminalCfg:K,renderer:ne,scrollback:K.initialLines+2*K.historyChunk}))}),s.get("/notes",_(),e=>{const t=h?C(q(),I()):[];return e.html(Ze(g.data.notes,j,h,t))}),s.get("/notes/:session",_(),e=>{const t=decodeURIComponent(e.req.param("session")),r=h?C(q(),I()):[];return e.html(et(t,j,h,r))}),s.get("/schedule",_(),e=>{const t=h?C(q(),I()):[];return e.html(nt(E.list(),E.listTriggered(),j,Ce,h,t))}),s.get("/history",_(),e=>{const t=q(),r=h?C(t,I()):[],n=new Set(t.map(i=>i.name));return e.html(ot(ft(),j,h,r,n))}),s.get("/quick-commands",_(),e=>{const t=h?C(q(),I()):[];return e.html(st(xe(),j,h,t))}),s.get("/files",_(),e=>{const t=h?C(q(),I()):[],r=fe();return e.html(it(j,h,t,r))}),s.post("/api/history/clear",a(),async e=>(await ht(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(xe())),s.post("/api/quick-commands",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await $t(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Vt(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",a(),async e=>await Bt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{const t=X(e),r=R.check(t);if(!r.allowed)return c("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const i=typeof n.password=="string"?n.password:"";if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Se(t))return c("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(D)return e.json({error:"Password setup in progress"},409);const p=pe(i);if(p)return e.json({error:p},400);D=!0;try{l.passwordHash=await le(i),ce(l),c("password_set",{ip:t})}finally{D=!1}const{plaintext:T}=f.createAccessToken("setup",l.security.tokenTtlDays);return qe(e,T),e.json({ok:!0,token:T,setupMode:!0})}if(!await me(i,l.passwordHash)){const p=R.recordFailure(t);return c("auth_failed",{ip:t,method:"password",failures:p.failures,permanentLock:p.permanentLock}),p.permanentLock?(f.revokeAll(),c("permanent_lock",{ip:t,failures:p.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):p.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:p.retryAfterMs},429)}R.recordSuccess(t);const d=`browser-${t}`,{plaintext:x}=f.createAccessToken(d,l.security.tokenTtlDays);return qe(e,x),c("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:x})}),s.post("/api/auth/token",a(),async e=>{const t=X(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,i=typeof r.ttlDays=="number"?r.ttlDays:l.security.tokenTtlDays,{stored:o,plaintext:d}=f.createAccessToken(n,i);return c("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",a(),e=>e.json(f.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",a(),e=>f.revoke(e.req.param("id"))?(c("token_revoked",{ip:X(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",a(),e=>(ur(e),e.json({ok:!0}))),s.get("/settings",_(),async e=>{const t=await Z(),r=t.terminalRenderer??"xterm";return e.html(tt({settings:t,renderer:ne,rendererOverridden:ne!==r,theme:j,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",a(),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.terminalRenderer==="ghostty"?"ghostty":"xterm",i=t.defaultView==="recent"?"recent":"default",o=Ae(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Ct({...r,commandbar:t.commandbar!==void 0,terminalRenderer:n,defaultView:i,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const i=r==="remove"?await Ot(n):r==="add"?await Mt(n):{ok:!1,output:"unknown action"};return i.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(i.output.slice(0,800)),303)}),s.get("/settings/theme",_(),e=>e.html(rt({theme:j,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",a(),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"||!je(r)?e.redirect("/settings/theme",303):(j=await ge(r),e.redirect("/settings/theme?saved=1",303))});const wr={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:j.template,templates:Et.map(t=>({id:t,name:wr[t]}))})),s.post("/api/theme",a(),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"||!je(r)?e.json({error:"invalid theme template"},400):(j=await ge(r),e.json({ok:!0,active:j.template}))}),s.get("/api/system/status",a(),e=>e.json(Zt())),s.get("/api/sessions",a(),e=>h?e.json(C(q(),I())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",a(),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(q().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return or(r,n),e.json({ok:!0})}catch(o){const d=o instanceof z?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",a(),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)?(sr(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",a(),async e=>{try{const{name:t}=await e.req.json();return t?(ir(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",a(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=pt(t),i=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:i})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",a(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=w.join(t,r));const n=e.req.query("recursive")==="true";let i=r,o="",d=r.endsWith("/");if(!d)try{d=N(r).isDirectory()}catch{}d||(i=w.dirname(r),o=w.basename(r).toLowerCase());try{const x=de(i),p=[],T=[];for(const M of x)if(!M.startsWith(".")&&!(o&&!M.toLowerCase().startsWith(o))){try{const S=w.join(i,M);N(S).isDirectory()?(p.push(S),n&&mt(S,p,T,0)):T.push(S)}catch{}if(p.length+T.length>=5e3)break}return e.json({dirs:p,files:T})}catch{return e.json({dirs:[],files:[]})}}),s.get("/api/file",a(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=G(t);if(!N(r).isFile())return e.json({error:"not a file"},400);const n=N(r).size;if(n>he)return e.json({error:"file too large",size:n,maxBytes:he},413);const i=We(r,"utf-8");return e.json({path:r,content:i,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",a(),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=G(t.path);return lt(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",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=G(t.path);return N(r).isFile()?($e(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=G(t.path);return V(r)?e.json({error:"file already exists"},409):(Ue(w.dirname(r),{recursive:!0}),Be(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function oe(e){return{...Wt(q(),I(),Nt()),currentSession:e??null}}function Oe(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",a(),e=>{const t=e.req.query("currentSession");return e.json(oe(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",a(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(ve(t))}),s.post("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Oe(t);return"error"in r?e.json({error:r.error},400):(await Lt(r.sessionName,r.windowIndex),e.json(oe()))}),s.delete("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Oe(t);return"error"in r?e.json({error:r.error},400):(await Pt(r.sessionName,r.windowIndex),e.json(oe()))}),s.get("/api/notes",a(),e=>{const t=[...g.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",a(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=g.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",a(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},i=g.data.notes.findIndex(o=>o.scope===t);return i>=0?g.data.notes[i]=n:g.data.notes.push(n),await g.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!q().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const i=n.file;if(!(i instanceof File))return e.json({error:"missing file field"},400);try{const o=await i.arrayBuffer(),d=Buffer.from(o),{path:x}=await Jt(d,i.type||void 0,i.name||void 0);return e.json({path:x})}catch(o){return o instanceof Yt?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",a(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Dt(t).map(o=>[o.windowIndex,o.label])),n=new Map(ve(t).map(o=>[o.index,o])),i=er(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(i)}),s.post("/api/session/:session/window-label",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const i=typeof r.label=="string"?r.label:"",o=await Ht(t,n,i);return e.json(o)}),s.post("/api/session/:session/select-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return tr(t,n),e.json({ok:!0})}catch(i){return i instanceof z?e.json({error:i.message},i.status):(console.error("[select-window]",i),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof r.name!="string"||!r.name.trim())return e.json({error:"name is required"},400);try{return nr(t,n,r.name),ke(t),e.json({ok:!0})}catch(i){return i instanceof z?e.json({error:i.message},i.status):(console.error("[rename-window]",i),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",a(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return rr(t),ke(t),e.json({ok:!0})}catch(r){return r instanceof z?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",a(),e=>e.json(E.list(e.req.query("session")))),s.post("/api/schedule",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ye(t);if(r)return e.json({error:r},400);if(!kt(t))return e.json({error:"invalid body"},400);const n=await E.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await E.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ye(t);if(r)return e.json({error:r},400);if(!vt(t))return e.json({error:"invalid body"},400);const n=await E.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const gr=parseInt(process.env.PORT||"21000",10),jr=Qe({fetch:s.fetch,port:gr,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),se=new Xe({noServer:!0});function Le(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
|
|
7
7
|
\r
|
|
8
|
-
`),e.destroy()}
|
|
8
|
+
`),e.destroy()}jr.on("upgrade",(e,t,r)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),i=n.pathname.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const o=dr(e),d=e.headers.origin,x=l.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(x.length===0||!x.includes(d))){c("ws_rejected_origin",{ip:o,origin:d}),Le(t,403,"Origin not allowed");return}const p=lr(o);if(p>=l.security.maxConnectionsPerIp){c("ws_rejected_per_ip_cap",{ip:o,liveFromIp:p}),Le(t,429,"Too many connections");return}const T=be(e)||n.searchParams.get("token")||"",M=T?f.validateToken(T):null;se.handleUpgrade(e,t,r,S=>{se.emit("connection",S,e,decodeURIComponent(i[1]),o,M)})}),se.on("connection",(e,t,r,n,i)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};H.set(e,o),c("ws_connected",{ip:n});let d=null;const{initialLines:x,historyChunk:p,syncIdleMs:T,syncMaxMs:M}=K;let S=!0,P=null,$=null,U=r,O=null;const B=()=>{P&&(clearTimeout(P),P=null),$&&(clearTimeout($),$=null)},ie=()=>{if(S){B(),S=!1;try{U=Gt(r)}catch{U=r}if(!Qt(U))try{const y=zt(U,x);v(e,{type:"snapshot",data:Te(y),lines:x})}catch{}}},Ne=()=>{S&&(P&&clearTimeout(P),P=setTimeout(ie,T))};function ae(){try{d=Ye.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(u){v(e,{type:"data",data:`\r
|
|
9
9
|
\x1B[31mFailed to attach to tmux session "${r}": ${u.message}\x1B[0m\r
|
|
10
|
-
`}),
|
|
10
|
+
`}),te(e,1011,"pty spawn failed");return}F.add(d);let y=-1,m="";O=Ft(r,({activeIndex:u,windows:k})=>{v(e,{type:"window_changed",activeIndex:u,windows:k});const L=k.map(b=>b.index).join(","),W=u!==y||L!==m;y=u,m=L}),$=setTimeout(ie,M),d.onData(u=>{if(S){Ne();return}v(e,{type:"data",data:u})}),d.onExit(({exitCode:u})=>{B(),d&&F.delete(d),d=null,v(e,{type:"data",data:`\r
|
|
11
11
|
\x1B[2m--- tmux exited (code ${u}) ---\x1B[0m\r
|
|
12
|
-
`}),
|
|
12
|
+
`}),te(e,1e3,"pty exited")})}function Y(y,m){o.authenticated=!0,o.authTimeout&&(clearTimeout(o.authTimeout),o.authTimeout=null),c("auth_success",{ip:n,method:y,tokenName:m}),v(e,{type:"auth.ok",setupMode:!l.passwordHash}),ae()}function A(y,m={}){v(e,{type:"auth.failed",message:y,...m})}async function De(y){let m;try{m=JSON.parse(y)}catch{return!1}if(m.type==="auth.token"&&typeof m.token=="string"){const u=f.validateToken(m.token);if(!u){c("token_auth_failed",{ip:n});const k=R.recordFailure(n);return k.permanentLock&&(f.revokeAll(),c("permanent_lock",{ip:n,failures:k.failures})),A("Invalid or expired token",{permanentLock:k.permanentLock,retryAfterMs:k.retryAfterMs}),!0}return f.touch(u.tokenHash),Y("token",u.name),!0}if(m.type==="auth"&&typeof m.password=="string"){const u=R.check(n);if(!u.allowed)return c("rate_limited",{ip:n,retryAfterMs:u.retryAfterMs,permanentLock:u.permanentLock}),A("Too many attempts",{permanentLock:u.permanentLock,retryAfterMs:u.retryAfterMs}),!0;if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Se(n))return c("setup_rejected_remote",{ip:n}),A("First-run setup must be performed from localhost"),!0;if(D)return A("Password setup in progress"),!0;const b=pe(m.password);if(b)return A(b),!0;D=!0;try{l.passwordHash=await le(m.password),ce(l),c("password_set",{ip:n})}finally{D=!1}const{stored:J,plaintext:He}=f.createAccessToken("setup",l.security.tokenTtlDays);return v(e,{type:"auth.ok",setupMode:!0,token:He}),c("token_created",{ip:n,name:J.name,tokenId:J.id}),c("auth_success",{ip:n,method:"password",tokenName:J.name}),ae(),!0}if(!l.passwordHash)return A("Server not configured"),!0;if(!await me(m.password,l.passwordHash)){const b=R.recordFailure(n);return c("auth_failed",{ip:n,method:"password",failures:b.failures,permanentLock:b.permanentLock}),b.permanentLock&&(f.revokeAll(),c("permanent_lock",{ip:n,failures:b.failures})),A("Incorrect password",{permanentLock:b.permanentLock,retryAfterMs:b.retryAfterMs}),!0}R.recordSuccess(n);const L=`ws-${n}`,{plaintext:W}=f.createAccessToken(L,l.security.tokenTtlDays);return Y("password",L),v(e,{type:"auth.ok",setupMode:!1,token:W}),!0}return!1}i?(f.touch(i.tokenHash),Y("token",i.name)):(v(e,{type:"auth.required",setupMode:!l.passwordHash}),o.authTimeout=setTimeout(()=>{o.authenticated||(c("auth_timeout",{ip:n}),A("Authentication timeout"),te(e,4e3,"Auth timeout"))},l.security.authTimeoutMs)),e.on("message",async y=>{const m=typeof y=="string"?y:y.toString("utf-8");if(!o.authenticated){if(m.length>1e6){v(e,{type:"auth.failed",message:"Message too large"});return}await De(m)||A("Authentication required");return}if(!d)return;try{if(xt(m,d))return}catch{return}let u;try{u=JSON.parse(m)}catch{return}if(u.type==="load_history"&&typeof u.before=="number"){const k=Math.max(0,Math.floor(u.before));try{const{data:L,lines:W}=Kt(U,k,p);v(e,{type:"history",data:Te(L),before:k,lines:W})}catch{v(e,{type:"history",data:"",before:k,lines:0})}}}),e.on("close",()=>{o.authTimeout&&clearTimeout(o.authTimeout),H.delete(e),c("ws_disconnected",{ip:n}),B(),O&&(O(),O=null),d&&(F.delete(d),d.kill(),d=null)}),e.on("error",()=>{o.authTimeout&&clearTimeout(o.authTimeout),H.delete(e),B(),O&&(O(),O=null),d&&(F.delete(d),d.kill(),d=null)})});function Pe(){E.cleanup(),Ut();for(const[e,t]of H){try{e.close(1001,"Server shutting down")}catch{}t.authTimeout&&clearTimeout(t.authTimeout)}H.clear();for(const e of F)try{e.kill()}catch{}F.clear();for(const e of _e)try{e.kill("SIGTERM")}catch{}R.dispose(),process.exit(0)}process.on("SIGINT",Pe),process.on("SIGTERM",Pe),setInterval(()=>{f.purgeExpired()},3600*1e3).unref();
|
package/dist/lib/agents-watch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{execFileSync as
|
|
2
|
-
`)){const c=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);c&&
|
|
1
|
+
import{execFileSync as k}from"node:child_process";import{capturePaneLines as v}from"./tmux-capture.js";import{listSessionPanes as S}from"./tmux-panes.js";import{getWatchedPanes as B,pruneWatchedPanes as M,recordWatchedPane as D}from"./watched-panes.js";import{identifyAgent as y,detectState as b}from"./agent-detect.js";function C(){const e=new Map;try{const o=k("ps",["-axo","pid=,ppid=,command="],{encoding:"utf-8",timeout:3e3,maxBuffer:8388608});for(const a of o.split(`
|
|
2
|
+
`)){const c=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);c&&e.set(parseInt(c[1],10),{ppid:parseInt(c[2],10),command:c[3]})}}catch{}return e}function A(e,o){if(o.size===0||e<=0)return[];const a=new Map;for(const[n,t]of o){const s=a.get(t.ppid);s?s.push(n):a.set(t.ppid,[n])}const c=[],f=[...a.get(e)??[]],p=o.get(e);p&&c.push(p.command);let m=0;for(;f.length&&m<200;){m++;const n=f.shift(),t=o.get(n);t&&c.push(t.command);const s=a.get(n);s&&f.push(...s)}return c}const l=new Map;function T(e){let o=5381;for(let a=0;a<e.length;a++)o=(o<<5)+o+e.charCodeAt(a)>>>0;return o}async function N(){const e=B();if(e.length===0)return[];const o=new Map;for(const n of e)o.has(n.sessionName)||o.set(n.sessionName,S(n.sessionName));const a=new Set;for(const n of o.values())for(const t of n)a.add(t.paneId);await M(a);for(const n of l.keys())a.has(n)||l.delete(n);const c=C(),f=new Set(e.map(n=>n.paneId)),p=[...e];for(const[n,t]of o){const s=t.find(d=>d.active&&d.windowActive);if(!s||f.has(s.paneId)||!y(s.command,A(s.pid,c)))continue;const u={paneId:s.paneId,sessionName:n,windowIndex:s.windowIndex,paneIndex:s.paneIndex};await D(u),f.add(s.paneId),p.unshift({...u,watchedAt:Date.now()})}const m=[];for(const n of p){const t=o.get(n.sessionName)?.find(h=>h.paneId===n.paneId);if(!t)continue;const s=A(t.pid,c),u=y(t.command,s);if(!u)continue;let d="unknown";try{const h=v(t.target,0);d=b(u,h);const g=T(h),x=l.get(t.paneId);l.set(t.paneId,g),d==="idle"&&x!==void 0&&x!==g&&(d="working")}catch{}m.push({paneId:t.paneId,sessionName:n.sessionName,windowIndex:t.windowIndex,paneIndex:t.paneIndex,agent:u,state:d,watchedAt:n.watchedAt})}return m}let i=null,I=[],P=0,r=null;const W=1500;function w(){P=Date.now(),N().then(e=>{I=e}).catch(()=>{})}function q(){return I}function z(){return i!==null}function F(){if(!i||r)return;const e=Date.now()-P;if(e>=W){w();return}r=setTimeout(()=>{r=null,w()},W-e),typeof r.unref=="function"&&r.unref()}function L(e=3500){i||(w(),i=setInterval(w,e),typeof i.unref=="function"&&i.unref())}function H(){i&&(clearInterval(i),i=null),r&&(clearTimeout(r),r=null),I=[]}export{q as getCachedAgentStatuses,z as isBackgroundWatchRunning,N as probeWatchedPanes,F as requestProbe,L as startBackgroundWatch,H as stopBackgroundWatch};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{icon as e}from"./icons.js";function
|
|
1
|
+
import{icon as e}from"./icons.js";function t(){return`
|
|
2
2
|
#mobile-toolbar { display: none; }
|
|
3
3
|
@media (max-width: 560px) {
|
|
4
4
|
#mobile-toolbar {
|
|
@@ -150,7 +150,7 @@ import{icon as e}from"./icons.js";function n(){return`
|
|
|
150
150
|
border-color: var(--panel-success); color: var(--panel-success);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
`}function
|
|
153
|
+
`}function o(){return`
|
|
154
154
|
<div id="mobile-toolbar">
|
|
155
155
|
<div class="tb-input">
|
|
156
156
|
<textarea id="tb-input" placeholder="Type or voice input\u2026" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" rows="1"></textarea>
|
|
@@ -178,7 +178,7 @@ import{icon as e}from"./icons.js";function n(){return`
|
|
|
178
178
|
<button id="type-send" type="button">Send</button>
|
|
179
179
|
<button id="type-send-enter" type="button">Send ⏎</button>
|
|
180
180
|
</div>
|
|
181
|
-
</div>`}function
|
|
181
|
+
</div>`}function n(a){return`(function() {
|
|
182
182
|
const typeBtn = document.getElementById('type-toggle');
|
|
183
183
|
const modal = document.getElementById('type-modal');
|
|
184
184
|
const backdrop = document.getElementById('type-backdrop');
|
|
@@ -295,4 +295,4 @@ import{icon as e}from"./icons.js";function n(){return`
|
|
|
295
295
|
|
|
296
296
|
tbSend.addEventListener('click', sendTb);
|
|
297
297
|
}
|
|
298
|
-
})();`}export{
|
|
298
|
+
})();`}export{t as mobileToolbarCSS,o as mobileToolbarHTML,n as mobileToolbarScript};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cssVarsStyle as
|
|
1
|
+
import{cssVarsStyle as r}from"../theme.js";import{AGENT_LABELS as o}from"../agent-detect.js";import{commandbarCSS as i,commandbarHTML as l,commandbarScript as p}from"../commandbar.js";import{notesDrawerCSS as c,notesDrawerHTML as d,notesDrawerScript as m}from"../notes-drawer.js";import{sharedLayoutCSS as g,sharedHeader as u,sharedSidebar as b,newSessionModalHTML as x,newSessionModalScript as f}from"../shared-layout.js";function h(a,e=!1,t=[]){const n=JSON.stringify(o).replace(/</g,"\\u003c"),s=`
|
|
2
2
|
.sub { font-size: var(--text-sm); color: var(--panel-muted); margin-bottom: 24px; line-height: 1.5; }
|
|
3
3
|
.agent {
|
|
4
4
|
display: flex; align-items: center; gap: 12px; text-decoration: none;
|
|
@@ -34,8 +34,8 @@ import{cssVarsStyle as s}from"../theme.js";import{AGENT_LABELS as o}from"../agen
|
|
|
34
34
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
35
35
|
<title>Agents - tmux-web</title>
|
|
36
36
|
<style>
|
|
37
|
-
${
|
|
38
|
-
${
|
|
37
|
+
${r(a.shell)}
|
|
38
|
+
${g(s)}
|
|
39
39
|
</style>
|
|
40
40
|
</head>
|
|
41
41
|
<body>
|
|
@@ -44,7 +44,7 @@ ${u({commandbarEnabled:e,title:"Agents",themeTemplate:a.template})}
|
|
|
44
44
|
|
|
45
45
|
<div class="page-wrap">
|
|
46
46
|
<div class="page-layout">
|
|
47
|
-
${
|
|
47
|
+
${b({activePage:"agents",agentsEnabled:!0,refreshHref:"/agents"})}
|
|
48
48
|
<main class="main-panel">
|
|
49
49
|
<p class="sub">AI agents in the last panes you viewed. Updates automatically.</p>
|
|
50
50
|
<div id="agents-list">
|
|
@@ -54,14 +54,14 @@ ${u({commandbarEnabled:e,title:"Agents",themeTemplate:a.template})}
|
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
|
-
${
|
|
57
|
+
${x()}
|
|
58
58
|
${e?l():""}
|
|
59
59
|
${d("Notes - Global")}
|
|
60
60
|
|
|
61
61
|
<script type="module">
|
|
62
|
-
${
|
|
62
|
+
${m("__global__")}
|
|
63
63
|
${e?p(t,[]):""}
|
|
64
|
-
${
|
|
64
|
+
${f()}
|
|
65
65
|
</script>
|
|
66
66
|
|
|
67
67
|
<script type="module">
|
|
@@ -102,4 +102,4 @@ poll();
|
|
|
102
102
|
setInterval(poll, 2500);
|
|
103
103
|
</script>
|
|
104
104
|
</body>
|
|
105
|
-
</html>`}export{
|
|
105
|
+
</html>`}export{h as renderAgentsIndex};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{cssVarsStyle as
|
|
2
|
-
`),
|
|
1
|
+
import{cssVarsStyle as l}from"../theme.js";import{escapeHtml as d}from"../html.js";import{commandbarCSS as c,commandbarHTML as f,commandbarScript as p}from"../commandbar.js";import{sharedLayoutCSS as m,sharedHeader as u,sharedSidebar as v,newSessionModalHTML as b,newSessionModalScript as y}from"../shared-layout.js";function S(a,e=!1,r=[],t){const n=t.length>0,o=t.map(i=>`<li>${d(i)}</li>`).join(`
|
|
2
|
+
`),s=`
|
|
3
3
|
.files-empty { font-size: var(--text-sm); color: var(--panel-muted); line-height: 1.7; margin-top: 12px; }
|
|
4
4
|
.files-empty code { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); padding: 3px 7px; border-radius: 6px; font-size: var(--text-xs); }
|
|
5
5
|
.files-roots { list-style: none; padding: 0; margin: 0; }
|
|
@@ -15,7 +15,7 @@ import{cssVarsStyle as d}from"../theme.js";import{escapeHtml as c}from"../html.j
|
|
|
15
15
|
.files-roots li:hover { border-color: var(--panel-accent); transform: translateY(-1px); box-shadow: 0 4px 20px color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
16
16
|
.files-roots li svg { width: 20px; height: 20px; flex-shrink: 0; color: var(--panel-accent); }
|
|
17
17
|
.files-info { font-size: var(--text-sm); color: var(--panel-muted); margin-bottom: 16px; line-height: 1.6; }
|
|
18
|
-
${e?
|
|
18
|
+
${e?c():""}`;return`<!DOCTYPE html>
|
|
19
19
|
<html lang="en">
|
|
20
20
|
<head>
|
|
21
21
|
<meta charset="UTF-8" />
|
|
@@ -23,23 +23,23 @@ import{cssVarsStyle as d}from"../theme.js";import{escapeHtml as c}from"../html.j
|
|
|
23
23
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
24
24
|
<title>Files - tmux-web</title>
|
|
25
25
|
<style>
|
|
26
|
-
${
|
|
27
|
-
${
|
|
26
|
+
${l(a.shell)}
|
|
27
|
+
${m(s)}
|
|
28
28
|
</style>
|
|
29
29
|
</head>
|
|
30
30
|
<body>
|
|
31
31
|
|
|
32
|
-
${
|
|
32
|
+
${u({commandbarEnabled:e,title:"Files",themeTemplate:a.template})}
|
|
33
33
|
|
|
34
34
|
<div class="page-wrap">
|
|
35
35
|
<div class="page-layout">
|
|
36
|
-
${
|
|
36
|
+
${v({activePage:"files",refreshHref:"/files"})}
|
|
37
37
|
<main class="main-panel">
|
|
38
38
|
|
|
39
39
|
${n?`
|
|
40
40
|
<p class="files-info">Browse and edit files from the configured directories below.</p>
|
|
41
41
|
<div class="files-roots">
|
|
42
|
-
${
|
|
42
|
+
${o}
|
|
43
43
|
</div>
|
|
44
44
|
<div id="file-browser" style="display:none">
|
|
45
45
|
<div id="fb-path" style="font-size:var(--text-sm);font-family:var(--font-mono);color:var(--panel-muted);margin-bottom:12px;word-break:break-all"></div>
|
|
@@ -66,8 +66,8 @@ ${v({commandbarEnabled:e,title:"Files",themeTemplate:a.template})}
|
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
|
-
${
|
|
70
|
-
${e?
|
|
69
|
+
${b()}
|
|
70
|
+
${e?f():""}
|
|
71
71
|
|
|
72
72
|
<script type="module">
|
|
73
73
|
${n?`const ROOTS = ${JSON.stringify(t)};
|
|
@@ -256,8 +256,8 @@ document.querySelectorAll('.files-roots li').forEach(el => {
|
|
|
256
256
|
el.addEventListener('click', () => loadDir(el.textContent));
|
|
257
257
|
});
|
|
258
258
|
`:""}
|
|
259
|
-
${e?
|
|
260
|
-
${
|
|
259
|
+
${e?p(r,[]):""}
|
|
260
|
+
${y()}
|
|
261
261
|
</script>
|
|
262
262
|
</body>
|
|
263
|
-
</html>`}export{
|
|
263
|
+
</html>`}export{S as renderFilesIndex};
|