tmux-weblink 2.0.12 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agent.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{createRequire as l}from"node:module";import{hostname as h}from"node:os";import T from"node:path";import{listSessions as E,resolveTmuxSocketPath as g}from"./sessions.js";import{loadSecurityConfig as _}from"./lib/security-config.js";import{TokenStore as w}from"./lib/auth.js";import{RateLimiter as b}from"./lib/rateLimiter.js";import{db as n}from"./lib/db.js";import{loadExtensions as k,spawnExtensionBackend as A}from"./lib/ext-loader.js";import{SchedulerService as M}from"./lib/scheduler.js";import{handleClientMessage as S}from"./lib/ws-message.js";import{loadDotEnv as x}from"./lib/load-env.js";import{readSettings as N}from"./lib/settings.js";import{readActiveTheme as y}from"./lib/theme-store.js";import{buildApp as B}from"./lib/build-app.js";import{acquireControlClient as C}from"./lib/tmux-control.js";import{getSessionPaneTarget as U,capturePaneTail as W,capturePaneHistoryChunk as G,toCrlf as P}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as D}from"./lib/terminal-config.js";import{startAgentClient as H}from"./lib/agent-client.js";x();const O=l(import.meta.url),{version:R}=O("../package.json");function X(e){let r=process.env.TMUX_WEB_AGENT_HUB||"",s=process.env.TMUX_WEB_AGENT_TOKEN||"",a=process.env.TMUX_WEB_AGENT_NAME||h();for(let o=0;o<e.length;o++){const t=e[o];if(t!=="agent"){if((t==="--hub"||t==="-h")&&e[o+1]){r=e[++o];continue}if((t==="--token"||t==="-t")&&e[o+1]){s=e[++o];continue}if((t==="--name"||t==="-n")&&e[o+1]){a=e[++o];continue}t.startsWith("--hub=")&&(r=t.slice(6)),t.startsWith("--token=")&&(s=t.slice(8)),t.startsWith("--name=")&&(a=t.slice(7))}}return r||(console.error("usage: tmux-web agent --hub wss://HOST[:PORT] --token TOKEN [--name NAME]"),console.error(" (or set TMUX_WEB_AGENT_HUB / TMUX_WEB_AGENT_TOKEN)"),process.exit(1)),s||(console.error("agent token required (--token or TMUX_WEB_AGENT_TOKEN)"),process.exit(1)),{hub:r,token:s,name:a.slice(0,64)}}const{hub:q,token:I,name:K}=X(process.argv.slice(2)),c=D(),j=_(),v=new w,F=new b;await n.read(),n.data.sessionAccess??=[],n.data.pinnedViews??=[],n.data.watchedPanes??=[],n.data.triggeredTasks??=[],n.data.quickCommands??=[];const i=await N(),L=await y(),$=i.commandbar===!0,f=J(i.scheduleHistoryDays),p=T.join(process.cwd(),"extensions"),u=await k(p);for(const e of u)e.start&&A(e.dir,e);const m=new M({db:n,historyRetentionMs:f*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await m.restoreFromDb();const V=B({mode:"agent",securityConfig:j,tokenStore:v,rateLimiter:F,scheduler:m,settings:i,commandbarEnabled:$,terminalRenderer:i.terminalRenderer==="ghostty"?"ghostty":"xterm",scheduleHistoryDays:f,extsDir:p,extensions:u,terminalBufferConfig:c,getAgentId:()=>null,state:{activeTheme:L,settingUpPassword:!1}}),z=H({hub:q,token:I,name:K,version:R,app:V,listSessions:E,terminalBufferConfig:c,getSessionPaneTarget:U,capturePaneTail:W,capturePaneHistoryChunk:G,toCrlf:P,handleClientMessage:S,acquireControlClient:C,tmuxSocketPath:g()??void 0});function d(){m.cleanup(),z.stop(),process.exit(0)}process.on("SIGINT",d),process.on("SIGTERM",d);function J(e){return typeof e!="number"||!Number.isFinite(e)?7:Math.min(365,Math.max(1,Math.round(e)))}
2
+ import{createRequire as l}from"node:module";import{hostname as h}from"node:os";import T from"node:path";import{listSessions as E,resolveTmuxSocketPath as g}from"./sessions.js";import{loadSecurityConfig as _}from"./lib/security-config.js";import{TokenStore as w}from"./lib/auth.js";import{RateLimiter as k}from"./lib/rateLimiter.js";import{db as n}from"./lib/db.js";import{loadExtensions as A,spawnExtensionBackend as M}from"./lib/ext-loader.js";import{SchedulerService as S}from"./lib/scheduler.js";import{handleClientMessage as b}from"./lib/ws-message.js";import{loadDotEnv as x}from"./lib/load-env.js";import{readSettings as N}from"./lib/settings.js";import{readActiveTheme as y}from"./lib/theme-store.js";import{buildApp as B}from"./lib/build-app.js";import{acquireControlClient as C}from"./lib/tmux-control.js";import{getSessionPaneTarget as U,capturePaneTail as W,capturePaneHistoryChunk as G,toCrlf as P}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as D}from"./lib/terminal-config.js";import{startAgentClient as H}from"./lib/agent-client.js";x();const O=l(import.meta.url),{version:R}=O("../package.json");function X(e){let r=process.env.TMUX_WEB_AGENT_HUB||"",s=process.env.TMUX_WEB_AGENT_TOKEN||"",i=process.env.TMUX_WEB_AGENT_NAME||h();for(let o=0;o<e.length;o++){const t=e[o];if(t!=="agent"){if((t==="--hub"||t==="-h")&&e[o+1]){r=e[++o];continue}if((t==="--token"||t==="-t")&&e[o+1]){s=e[++o];continue}if((t==="--name"||t==="-n")&&e[o+1]){i=e[++o];continue}t.startsWith("--hub=")&&(r=t.slice(6)),t.startsWith("--token=")&&(s=t.slice(8)),t.startsWith("--name=")&&(i=t.slice(7))}}return r||(console.error("usage: tmux-web agent --hub wss://HOST[:PORT] --token TOKEN [--name NAME]"),console.error(" (or set TMUX_WEB_AGENT_HUB / TMUX_WEB_AGENT_TOKEN)"),process.exit(1)),s||(console.error("agent token required (--token or TMUX_WEB_AGENT_TOKEN)"),process.exit(1)),{hub:r,token:s,name:i.slice(0,64)}}const{hub:q,token:I,name:K}=X(process.argv.slice(2)),m=D(),j=_(),v=new w,F=new k;await n.read(),n.data.sessionAccess??=[],n.data.pinnedViews??=[],n.data.watchedPanes??=[],n.data.triggeredTasks??=[],n.data.quickCommands??=[];const a=await N(),L=await y(),f=z(a.scheduleHistoryDays),p=T.join(process.cwd(),"extensions"),u=await A(p);for(const e of u)e.start&&M(e.dir,e);const c=new S({db:n,historyRetentionMs:f*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await c.restoreFromDb();const $=B({mode:"agent",securityConfig:j,tokenStore:v,rateLimiter:F,scheduler:c,settings:a,terminalRenderer:a.terminalRenderer==="ghostty"?"ghostty":"xterm",scheduleHistoryDays:f,extsDir:p,extensions:u,terminalBufferConfig:m,getAgentId:()=>null,state:{activeTheme:L,settingUpPassword:!1}}),V=H({hub:q,token:I,name:K,version:R,app:$,listSessions:E,terminalBufferConfig:m,getSessionPaneTarget:U,capturePaneTail:W,capturePaneHistoryChunk:G,toCrlf:P,handleClientMessage:b,acquireControlClient:C,tmuxSocketPath:g()??void 0});function d(){c.cleanup(),V.stop(),process.exit(0)}process.on("SIGINT",d),process.on("SIGTERM",d);function z(e){return typeof e!="number"||!Number.isFinite(e)?7:Math.min(365,Math.max(1,Math.round(e)))}
@@ -1,30 +1,30 @@
1
- var Ft="https://esm.sh/ghostty-web@0.4.0",Vt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ie(n){return n.replace(Vt,"")}var ye;function _t(){return ye||(document.querySelector("link[data-tmux-web-xterm-css]")?ye=Promise.resolve():(ye=new Promise(n=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>n(),{once:!0}),e.addEventListener("error",()=>n(),{once:!0}),document.head.appendChild(e)}),ye))}var Ze=class wt{terminal;fitAddon;container;constructor(e,t,i){this.container=e,this.terminal=t,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,t,i){let[{Terminal:a},{FitAddon:l}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),_t()]),c=new a({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:t,convertEol:!1,theme:i});return new wt(e,c,new l)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(t=>this.terminal.write(e,t))}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=Ge(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||e===this.terminal.textarea||this.container.contains(e)||!!e.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,t){let i=this.terminal.buffer.active,[a,l]=e<=t?[e,t]:[t,e],c=i.viewportY,r=[];for(let d=a;d<=l;d++){let p=i.getLine(c+d);r.push(p?p.translateToString(!0):"")}return r.join(`
1
+ var Ft="https://esm.sh/ghostty-web@0.4.0",Vt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ie(n){return n.replace(Vt,"")}var ye;function _t(){return ye||(document.querySelector("link[data-tmux-web-xterm-css]")?ye=Promise.resolve():(ye=new Promise(n=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>n(),{once:!0}),e.addEventListener("error",()=>n(),{once:!0}),document.head.appendChild(e)}),ye))}var Ge=class wt{terminal;fitAddon;container;constructor(e,t,i){this.container=e,this.terminal=t,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,t,i){let[{Terminal:a},{FitAddon:l}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),_t()]),c=new a({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:t,convertEol:!1,theme:i});return new wt(e,c,new l)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(t=>this.terminal.write(e,t))}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=Qe(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||e===this.terminal.textarea||this.container.contains(e)||!!e.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,t){let i=this.terminal.buffer.active,[a,l]=e<=t?[e,t]:[t,e],c=i.viewportY,r=[];for(let d=a;d<=l;d++){let p=i.getLine(c+d);r.push(p?p.translateToString(!0):"")}return r.join(`
2
2
  `)}selectRows(e,t){let i=this.terminal.buffer.active,[a,l]=e<=t?[e,t]:[t,e];this.terminal.selectLines(i.viewportY+a,i.viewportY+l)}selectCells(e,t,i,a){let l=this.terminal.buffer.active,c=Math.min(t,a),r=Math.max(t,a);if(c===r){let d=Math.min(e,i),p=Math.max(e,i);this.terminal.select(d,l.viewportY+c,p-d+1)}else this.terminal.selectLines(l.viewportY+c,l.viewportY+r)}getCellsText(e,t,i,a){let l=this.terminal.buffer.active,c=Math.min(t,a),r=Math.max(t,a),d=t<a?e:t>a?i:Math.min(e,i),p=t<a?i:t>a?e:Math.max(e,i),L=l.viewportY,S=[];for(let w=c;w<=r;w++){let E=l.getLine(L+w);if(!E){S.push("");continue}let H=w===c?d:0,A=w===r?p:this.terminal.cols-1,z="";for(let Y=H;Y<=A;Y++){let N=E.getCell(Y);N&&(z+=N.getChars())}S.push(z.replace(/\s+$/,""))}return S.join(`
3
- `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}},Ut=class bt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,t){this.container=e,this.terminal=t,this.terminal.open(e)}static async create(e,t,i){let a=await import(Ft);await a.init();let l=new a.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:t,convertEol:!1,theme:i});return new bt(e,l)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(t=>this.terminal.write(e,t))}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=Ge(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let t=Math.floor(e.width/this.charW),i=Math.floor(e.height/this.charH);if(t<10||i<5)return!1;if(t!==this.colsValue||i!==this.rowsValue){this.colsValue=t,this.rowsValue=i,this.terminal.resize(t,i);for(let a of this.resizeCallbacks)a({cols:t,rows:i})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let t=this.terminal.getMode?.(2004)??!1;this.input(t?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(t=>e(t))}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))}getScreenText(){return this.getRowsText(0,this.rowsValue-1)}getRowsText(e,t){try{let i=this.terminal.buffer?.active;if(!i||typeof i.getLine!="function")return"";let[a,l]=e<=t?[e,t]:[t,e],c=typeof i.viewportY=="number"?i.viewportY:0,r=[];for(let d=a;d<=l;d++){let p=i.getLine(c+d);r.push(typeof p?.translateToString=="function"?p.translateToString(!0):"")}return r.join(`
3
+ `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}},Ut=class bt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,t){this.container=e,this.terminal=t,this.terminal.open(e)}static async create(e,t,i){let a=await import(Ft);await a.init();let l=new a.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:t,convertEol:!1,theme:i});return new bt(e,l)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(t=>this.terminal.write(e,t))}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=Qe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let t=Math.floor(e.width/this.charW),i=Math.floor(e.height/this.charH);if(t<10||i<5)return!1;if(t!==this.colsValue||i!==this.rowsValue){this.colsValue=t,this.rowsValue=i,this.terminal.resize(t,i);for(let a of this.resizeCallbacks)a({cols:t,rows:i})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let t=this.terminal.getMode?.(2004)??!1;this.input(t?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(t=>e(t))}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))}getScreenText(){return this.getRowsText(0,this.rowsValue-1)}getRowsText(e,t){try{let i=this.terminal.buffer?.active;if(!i||typeof i.getLine!="function")return"";let[a,l]=e<=t?[e,t]:[t,e],c=typeof i.viewportY=="number"?i.viewportY:0,r=[];for(let d=a;d<=l;d++){let p=i.getLine(c+d);r.push(typeof p?.translateToString=="function"?p.translateToString(!0):"")}return r.join(`
4
4
  `)}catch{return""}}selectRows(e,t){try{if(typeof this.terminal?.selectLines!="function")return;let i=this.terminal.buffer?.active,a=typeof i?.viewportY=="number"?i.viewportY:0,[l,c]=e<=t?[e,t]:[t,e];this.terminal.selectLines(a+l,a+c)}catch{}}selectCells(e,t,i,a){try{let l=this.terminal.buffer?.active,c=typeof l?.viewportY=="number"?l.viewportY:0,r=Math.min(t,a),d=Math.max(t,a);if(r===d){if(typeof this.terminal?.select!="function"){this.selectRows(r,d);return}let p=Math.min(e,i),L=Math.max(e,i);this.terminal.select(p,c+r,L-p+1)}else this.selectRows(r,d)}catch{this.selectRows(t,a)}}getCellsText(e,t,i,a){try{let l=this.terminal.buffer?.active;if(!l||typeof l.getLine!="function")return this.getRowsText(t,a);let c=typeof l.viewportY=="number"?l.viewportY:0,r=Math.min(t,a),d=Math.max(t,a),p=t<a?e:t>a?i:Math.min(e,i),L=t<a?i:t>a?e:Math.max(e,i),S=[];for(let w=r;w<=d;w++){let E=l.getLine(c+w);if(!E){S.push("");continue}let H=w===r?p:0,A=w===d?L:this.colsValue-1,z="";if(typeof E.getCell=="function")for(let Y=H;Y<=A;Y++){let N=E.getCell(Y);N&&(z+=typeof N.getChars=="function"?N.getChars():"")}else z=typeof E.translateToString=="function"?E.translateToString(!1):"";S.push(z.replace(/\s+$/,""))}return S.join(`
5
- `)}catch{return this.getRowsText(t,a)}}getSelection(){try{if(typeof this.terminal?.getSelection=="function")return this.terminal.getSelection()}catch{}return""}clearSelection(){try{typeof this.terminal?.clearSelection=="function"&&this.terminal.clearSelection()}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let t=this.container.querySelector("canvas");if(t instanceof HTMLElement&&t.offsetWidth>0&&t.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let i=t.offsetWidth/this.colsValue,a=t.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=i,this.charH=a)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function Ge(n){let e=n.getBoundingClientRect(),t=window.visualViewport;return t?{width:Math.min(e.width,t.width),height:Math.max(0,Math.min(e.height,t.height-Math.max(0,e.top)))}:e}function Kt(n,e,t){return n.innerHTML="",n.classList.add("terminal-pending"),(async()=>{let i;if(t.renderer==="ghostty")try{i=await Ut.create(n,t.scrollback,t.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),i=await Ze.create(n,t.scrollback,t.theme)}else i=await Ze.create(n,t.scrollback,t.theme);let a,l=0,c,r=null,d=0,p="connecting",L=0,S=!1,w=[],E="",H=!1,A=1e3,z=location.protocol==="https:"?"wss:":"ws:",Y=t.wsBase||"/ws",N=z+"//"+location.host+Y+"/"+encodeURIComponent(e),Lt="/api/session/"+encodeURIComponent(e)+"/upload",Et=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function xt(){return w.join("")+E}async function kt(s,o=0){let u=s?i.viewportY():0,v=s?i.baseY():0,B=ie(xt());if(i.reset(),!B){s||i.scrollToBottom();return}await i.write(B),s?i.scrollToLine(v+u+o):i.scrollToBottom()}function X(s){a?.readyState===WebSocket.OPEN&&a.send(JSON.stringify(s))}function Tt(s){let o;try{o=JSON.parse(s)}catch{p==="live"&&(E+=s,i.write(ie(s)));return}if(o.type==="auth.required"){let u=localStorage.getItem("tmux-web-token");u?X({type:"auth.token",token:u}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(o.type==="attach_failed"&&typeof o.message=="string"){pe=!0,p="dead",i.reset(),i.write(`\r
5
+ `)}catch{return this.getRowsText(t,a)}}getSelection(){try{if(typeof this.terminal?.getSelection=="function")return this.terminal.getSelection()}catch{}return""}clearSelection(){try{typeof this.terminal?.clearSelection=="function"&&this.terminal.clearSelection()}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let t=this.container.querySelector("canvas");if(t instanceof HTMLElement&&t.offsetWidth>0&&t.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let i=t.offsetWidth/this.colsValue,a=t.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=i,this.charH=a)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function Qe(n){let e=n.getBoundingClientRect(),t=window.visualViewport;return t?{width:Math.min(e.width,t.width),height:Math.max(0,Math.min(e.height,t.height-Math.max(0,e.top)))}:e}function Kt(n,e,t){return n.innerHTML="",n.classList.add("terminal-pending"),(async()=>{let i;if(t.renderer==="ghostty")try{i=await Ut.create(n,t.scrollback,t.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),i=await Ge.create(n,t.scrollback,t.theme)}else i=await Ge.create(n,t.scrollback,t.theme);let a,l=0,c,r=null,d=0,p="connecting",L=0,S=!1,w=[],E="",H=!1,A=1e3,z=location.protocol==="https:"?"wss:":"ws:",Y=t.wsBase||"/ws",N=z+"//"+location.host+Y+"/"+encodeURIComponent(e),Lt="/api/session/"+encodeURIComponent(e)+"/upload",Et=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function xt(){return w.join("")+E}async function kt(s,o=0){let u=s?i.viewportY():0,v=s?i.baseY():0,B=ie(xt());if(i.reset(),!B){s||i.scrollToBottom();return}await i.write(B),s?i.scrollToLine(v+u+o):i.scrollToBottom()}function X(s){a?.readyState===WebSocket.OPEN&&a.send(JSON.stringify(s))}function Tt(s){let o;try{o=JSON.parse(s)}catch{p==="live"&&(E+=s,i.write(ie(s)));return}if(o.type==="auth.required"){let u=localStorage.getItem("tmux-web-token");u?X({type:"auth.token",token:u}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(o.type==="attach_failed"&&typeof o.message=="string"){pe=!0,p="dead",i.reset(),i.write(`\r
6
6
  \x1B[31m`+ie(o.message)+`\x1B[0m\r
7
- `).then(()=>{me();try{a?.close()}catch{}});return}if(o.type==="auth.ok"){o.token&&localStorage.setItem("tmux-web-token",o.token),A=1e3,J(),X({type:"resize",cols:i.cols,rows:i.rows}),C();return}if(o.type==="auth.failed"){if(o.permanentLock||o.retryAfterMs&&o.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(o.message);return}return}if(o.type==="snapshot"&&typeof o.data=="string"){let u=ie(o.data);w=[u],E="",L=typeof o.lines=="number"?o.lines:t.terminal.initialLines,p="live",i.reset(),i.write(u).then(()=>i.scrollToBottom());return}if(o.type==="window_changed"&&typeof o.activeIndex=="number"){let u=new URL(location.href);u.searchParams.get("window")!==String(o.activeIndex)&&(u.searchParams.set("window",String(o.activeIndex)),history.replaceState(history.state,"",u)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:o.windows}));return}if(o.type==="history"&&typeof o.data=="string"){S=!1,o.lines>0&&o.data&&(w.unshift(o.data),L+=o.lines,kt(!0,o.lines));return}if(o.type==="data"&&typeof o.data=="string"){let u=ie(o.data);if(p==="connecting"){p="live",E=u,i.write(u);return}p==="live"&&(E+=u,i.write(u))}}let pe=!1;function Ne(){if(pe)return;let s=N,o=localStorage.getItem("tmux-web-token");o&&(s+=(s.includes("?")?"&":"?")+"token="+encodeURIComponent(o)),a=new WebSocket(s),a.onopen=()=>{p="connecting",L=0,S=!1,w=[],E="",i.reset(),A=1e3},a.onmessage=u=>{typeof u.data=="string"&&Tt(u.data)},a.onclose=()=>{H||(p="connecting",!pe&&setTimeout(()=>{A=Math.min(A*2,1e4),!H&&!pe&&Ne()},A))},a.onerror=()=>a?.close()}function fe(s){X({type:"input",data:s})}function J(){return i.fit()}function me(){n.classList.remove("terminal-pending")}function C(){l&&cancelAnimationFrame(l),c&&clearTimeout(c),J(),l=requestAnimationFrame(()=>{l=0,J()&&me()}),c=setTimeout(()=>{J()&&me()},120)}function Ct(){C(),setTimeout(C,50),setTimeout(C,150),setTimeout(C,300)}function We(s){return typeof s=="string"&&s.startsWith("image/")}function je(s){return s?.files?.length?s.files[0]??null:null}function Mt(s){if(!s?.items)return null;for(let o=0;o<s.items.length;o++){let u=s.items[o];if(u?.kind==="file"&&We(u.type))return u.getAsFile()}return null}async function St(s){let o=new FormData,u=s instanceof File&&s.name?s.name:"upload";o.append("file",s,u);let v=localStorage.getItem("tmux-web-token"),B={};v&&(B.Authorization="Bearer "+v);let ne=await fetch(Lt,{method:"POST",body:o,headers:B});if(!ne.ok){let Xe="upload failed";try{let Je=await ne.json();Je.error&&(Xe=Je.error)}catch{}throw new Error(Xe)}let qe=await ne.json();if(!qe.path)throw new Error("no path in response");return qe.path}async function Ie(s){try{let o=await St(s);fe(o)}catch{}}i.onData(s=>fe(s)),i.onResize(({cols:s,rows:o})=>X({type:"resize",cols:s,rows:o})),i.onScroll(()=>{p!=="live"||S||!i.isNearScrollbackTop()||(S=!0,X({type:"load_history",before:L}))});let Z=!1,G=!1,b=null,x=null,Q=document.createElement("div");Q.className="terminal-copy-banner",Q.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',Q.hidden=!0,n.appendChild(Q);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,n.appendChild($);function Fe(s,o){let u=n.querySelector(".xterm-screen, canvas"),v=(u&&u.getBoundingClientRect().width>0?u:n).getBoundingClientRect(),B=v.height>0&&i.rows>0?v.height/i.rows:18,ne=v.width>0&&i.cols>0?v.width/i.cols:9;return{row:Math.max(0,Math.min(i.rows-1,Math.floor((o-v.top)/B))),col:Math.max(0,Math.min(i.cols-1,Math.floor((s-v.left)/ne)))}}async function he(s){if(!s)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(s);return}}catch{}let o=document.createElement("textarea");o.value=s,o.style.position="fixed",o.style.opacity="0",o.style.pointerEvents="none",document.body.appendChild(o),o.focus(),o.select();try{document.execCommand("copy")}catch{}o.remove()}let ee;function ve(){$.hidden=!1,ee&&clearTimeout(ee),ee=setTimeout(()=>{$.hidden=!0},1e3)}function U(s){Z=s,Q.hidden=!s,n.classList.toggle("terminal-copy-mode-active",s),s&&i.focus()}function Ve(){U(!Z)}function Bt(s){H||s.button!==0||G||(Z||s.shiftKey)&&(s.preventDefault(),s.stopPropagation(),G=!0,b=Fe(s.clientX,s.clientY),x=b,i.selectCells(b.col,b.row,x.col,x.row),document.addEventListener("mousemove",He,!0),document.addEventListener("mouseup",De,!0))}function He(s){H||!G||!b||(s.preventDefault(),s.stopPropagation(),x=Fe(s.clientX,s.clientY),i.selectCells(b.col,b.row,x.col,x.row))}function De(s){if(H||!G||!b||!x)return;s.preventDefault(),s.stopPropagation(),document.removeEventListener("mousemove",He,!0),document.removeEventListener("mouseup",De,!0),G=!1;let o=i.getCellsText(b.col,b.row,x.col,x.row);b=null,x=null,i.clearSelection(),U(!1),o&&($.textContent="\u5DF2\u590D\u5236",he(o).then(ve)),i.focus()}n.addEventListener("mousedown",Bt,!0),document.addEventListener("keydown",It,!0);function It(s){if(!(H||!i.isFocused()||s.defaultPrevented||s.isComposing)){if(Z){if(s.key==="Escape"){s.preventDefault(),s.stopPropagation(),b=null,x=null,i.clearSelection(),U(!1);return}if(s.key==="Enter"||(s.ctrlKey||s.metaKey)&&s.code==="KeyC"){s.preventDefault(),s.stopPropagation(),he(i.getScreenText()).then(ve),U(!1);return}}if(s.altKey&&s.code==="KeyC"&&!s.ctrlKey&&!s.metaKey){if(s.preventDefault(),s.stopPropagation(),b&&x){let o=i.getCellsText(b.col,b.row,x.col,x.row);b=null,x=null,i.clearSelection(),U(!1),o&&($.textContent="Copied",he(o).then(ve))}else{let o=i.getSelection();o?(i.clearSelection(),$.textContent="Copied",he(o).then(ve)):Ve()}return}if((s.ctrlKey||s.metaKey)&&s.code==="KeyV"){s.preventDefault(),s.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let o=await navigator.clipboard.read();for(let u of o)for(let v of u.types)if(We(v)){let B=await u.getType(v);await Ie(B);return}}}catch{}try{let o=await navigator.clipboard?.readText();o&&i.pasteText(_e(o))}catch{}})();return}}}let ge=0;function _e(s){return s.split(`\r
7
+ `).then(()=>{me();try{a?.close()}catch{}});return}if(o.type==="auth.ok"){o.token&&localStorage.setItem("tmux-web-token",o.token),A=1e3,J(),X({type:"resize",cols:i.cols,rows:i.rows}),C();return}if(o.type==="auth.failed"){if(o.permanentLock||o.retryAfterMs&&o.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(o.message);return}return}if(o.type==="snapshot"&&typeof o.data=="string"){let u=ie(o.data);w=[u],E="",L=typeof o.lines=="number"?o.lines:t.terminal.initialLines,p="live",i.reset(),i.write(u).then(()=>i.scrollToBottom());return}if(o.type==="window_changed"&&typeof o.activeIndex=="number"){let u=new URL(location.href);u.searchParams.get("window")!==String(o.activeIndex)&&(u.searchParams.set("window",String(o.activeIndex)),history.replaceState(history.state,"",u)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:o.windows}));return}if(o.type==="history"&&typeof o.data=="string"){S=!1,o.lines>0&&o.data&&(w.unshift(o.data),L+=o.lines,kt(!0,o.lines));return}if(o.type==="data"&&typeof o.data=="string"){let u=ie(o.data);if(p==="connecting"){p="live",E=u,i.write(u);return}p==="live"&&(E+=u,i.write(u))}}let pe=!1;function We(){if(pe)return;let s=N,o=localStorage.getItem("tmux-web-token");o&&(s+=(s.includes("?")?"&":"?")+"token="+encodeURIComponent(o)),a=new WebSocket(s),a.onopen=()=>{p="connecting",L=0,S=!1,w=[],E="",i.reset(),A=1e3},a.onmessage=u=>{typeof u.data=="string"&&Tt(u.data)},a.onclose=()=>{H||(p="connecting",!pe&&setTimeout(()=>{A=Math.min(A*2,1e4),!H&&!pe&&We()},A))},a.onerror=()=>a?.close()}function fe(s){X({type:"input",data:s})}function J(){return i.fit()}function me(){n.classList.remove("terminal-pending")}function C(){l&&cancelAnimationFrame(l),c&&clearTimeout(c),J(),l=requestAnimationFrame(()=>{l=0,J()&&me()}),c=setTimeout(()=>{J()&&me()},120)}function Ct(){C(),setTimeout(C,50),setTimeout(C,150),setTimeout(C,300)}function je(s){return typeof s=="string"&&s.startsWith("image/")}function Fe(s){return s?.files?.length?s.files[0]??null:null}function Mt(s){if(!s?.items)return null;for(let o=0;o<s.items.length;o++){let u=s.items[o];if(u?.kind==="file"&&je(u.type))return u.getAsFile()}return null}async function St(s){let o=new FormData,u=s instanceof File&&s.name?s.name:"upload";o.append("file",s,u);let v=localStorage.getItem("tmux-web-token"),B={};v&&(B.Authorization="Bearer "+v);let ne=await fetch(Lt,{method:"POST",body:o,headers:B});if(!ne.ok){let Je="upload failed";try{let Ze=await ne.json();Ze.error&&(Je=Ze.error)}catch{}throw new Error(Je)}let Xe=await ne.json();if(!Xe.path)throw new Error("no path in response");return Xe.path}async function Ie(s){try{let o=await St(s);fe(o)}catch{}}i.onData(s=>fe(s)),i.onResize(({cols:s,rows:o})=>X({type:"resize",cols:s,rows:o})),i.onScroll(()=>{p!=="live"||S||!i.isNearScrollbackTop()||(S=!0,X({type:"load_history",before:L}))});let Z=!1,G=!1,b=null,x=null,Q=document.createElement("div");Q.className="terminal-copy-banner",Q.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',Q.hidden=!0,n.appendChild(Q);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,n.appendChild($);function Ve(s,o){let u=n.querySelector(".xterm-screen, canvas"),v=(u&&u.getBoundingClientRect().width>0?u:n).getBoundingClientRect(),B=v.height>0&&i.rows>0?v.height/i.rows:18,ne=v.width>0&&i.cols>0?v.width/i.cols:9;return{row:Math.max(0,Math.min(i.rows-1,Math.floor((o-v.top)/B))),col:Math.max(0,Math.min(i.cols-1,Math.floor((s-v.left)/ne)))}}async function he(s){if(!s)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(s);return}}catch{}let o=document.createElement("textarea");o.value=s,o.style.position="fixed",o.style.opacity="0",o.style.pointerEvents="none",document.body.appendChild(o),o.focus(),o.select();try{document.execCommand("copy")}catch{}o.remove()}let ee;function ve(){$.hidden=!1,ee&&clearTimeout(ee),ee=setTimeout(()=>{$.hidden=!0},1e3)}function U(s){Z=s,Q.hidden=!s,n.classList.toggle("terminal-copy-mode-active",s),s&&i.focus()}function _e(){U(!Z)}function Bt(s){H||s.button!==0||G||(Z||s.shiftKey)&&(s.preventDefault(),s.stopPropagation(),G=!0,b=Ve(s.clientX,s.clientY),x=b,i.selectCells(b.col,b.row,x.col,x.row),document.addEventListener("mousemove",He,!0),document.addEventListener("mouseup",De,!0))}function He(s){H||!G||!b||(s.preventDefault(),s.stopPropagation(),x=Ve(s.clientX,s.clientY),i.selectCells(b.col,b.row,x.col,x.row))}function De(s){if(H||!G||!b||!x)return;s.preventDefault(),s.stopPropagation(),document.removeEventListener("mousemove",He,!0),document.removeEventListener("mouseup",De,!0),G=!1;let o=i.getCellsText(b.col,b.row,x.col,x.row);b=null,x=null,i.clearSelection(),U(!1),o&&($.textContent="\u5DF2\u590D\u5236",he(o).then(ve)),i.focus()}n.addEventListener("mousedown",Bt,!0),document.addEventListener("keydown",It,!0);function It(s){if(!(H||!i.isFocused()||s.defaultPrevented||s.isComposing)){if(Z){if(s.key==="Escape"){s.preventDefault(),s.stopPropagation(),b=null,x=null,i.clearSelection(),U(!1);return}if(s.key==="Enter"||(s.ctrlKey||s.metaKey)&&s.code==="KeyC"){s.preventDefault(),s.stopPropagation(),he(i.getScreenText()).then(ve),U(!1);return}}if(s.altKey&&s.code==="KeyC"&&!s.ctrlKey&&!s.metaKey){if(s.preventDefault(),s.stopPropagation(),b&&x){let o=i.getCellsText(b.col,b.row,x.col,x.row);b=null,x=null,i.clearSelection(),U(!1),o&&($.textContent="Copied",he(o).then(ve))}else{let o=i.getSelection();o?(i.clearSelection(),$.textContent="Copied",he(o).then(ve)):_e()}return}if((s.ctrlKey||s.metaKey)&&s.code==="KeyV"){s.preventDefault(),s.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let o=await navigator.clipboard.read();for(let u of o)for(let v of u.types)if(je(v)){let B=await u.getType(v);await Ie(B);return}}}catch{}try{let o=await navigator.clipboard?.readText();o&&i.pasteText(Ue(o))}catch{}})();return}}}let ge=0;function Ue(s){return s.split(`\r
8
8
  `).join(`
9
9
  `).split(`
10
- `).join("\r")}async function Ht(s){let o=Mt(s.clipboardData);if(o){s.preventDefault(),s.stopPropagation();let B=Date.now();if(B-ge<50)return;ge=B,await Ie(o);return}let u=s.clipboardData?.getData("text/plain");if(!u)return;s.preventDefault(),s.stopPropagation();let v=Date.now();v-ge<50||(ge=v,i.pasteText(_e(u)))}let te=0;function Dt(s){s.preventDefault(),te++,je(s.dataTransfer)&&n.classList.add("terminal-drag-over")}function Rt(s){s.preventDefault(),s.dataTransfer&&(s.dataTransfer.dropEffect="copy")}function At(s){s.preventDefault(),te--,te<=0&&(te=0,n.classList.remove("terminal-drag-over"))}async function Yt(s){s.preventDefault(),te=0,n.classList.remove("terminal-drag-over");let o=je(s.dataTransfer);o&&await Ie(o)}function Ue(s){!Et||s.key!=="Escape"||Z||s.defaultPrevented||s.repeat||s.metaKey||s.ctrlKey||s.altKey||i.isFocused()&&(s.preventDefault(),s.stopPropagation(),fe("\x1B"))}n.addEventListener("paste",Ht),n.addEventListener("dragenter",Dt),n.addEventListener("dragover",Rt),n.addEventListener("dragleave",At),n.addEventListener("drop",Yt),document.addEventListener("keydown",Ue,!0);function $t(s){if(s.touches.length!==1){r=null;return}let o=s.touches[0];o&&(r={startX:o.clientX,startY:o.clientY,lastX:o.clientX,lastY:o.clientY,scrolling:!1},s.stopPropagation())}function Pt(s){if(!r||s.touches.length!==1)return;let o=s.touches[0];if(!o)return;let u=o.clientY-r.startY,v=o.clientX-r.startX;if(!r.scrolling){if(Math.abs(u)<8||Math.abs(u)<Math.abs(v))return;r.scrolling=!0,d=Date.now()+500}s.preventDefault(),s.stopPropagation(),jt(-(o.clientY-r.lastY),o.clientX,o.clientY),r.lastX=o.clientX,r.lastY=o.clientY}function zt(s){if(!r)return;let o=r.scrolling;r=null,s.stopPropagation(),o?(d=Date.now()+500,s.preventDefault()):i.focus()}function Nt(s){r=null,s.stopPropagation()}function Wt(s){s.pointerType==="touch"&&Date.now()<d&&(s.preventDefault(),s.stopPropagation())}function jt(s,o,u){(n.querySelector(".xterm-screen")??n).dispatchEvent(new WheelEvent("wheel",{deltaY:s,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:o,clientY:u,bubbles:!0,cancelable:!0}))}n.addEventListener("touchstart",$t,{passive:!0,capture:!0}),n.addEventListener("touchmove",Pt,{passive:!1,capture:!0}),n.addEventListener("touchend",zt,{passive:!1,capture:!0}),n.addEventListener("touchcancel",Nt,{passive:!0,capture:!0}),n.addEventListener("pointerup",Wt,!0);function Ke(){C()}window.addEventListener("resize",Ke),window.visualViewport?.addEventListener("resize",C),window.visualViewport?.addEventListener("scroll",C);let Oe=new ResizeObserver(()=>C());return Oe.observe(n),document.fonts?.ready.then(()=>{C(),setTimeout(C,50),setTimeout(C,200)}),n.addEventListener("focusin",Ct,!0),requestAnimationFrame(()=>{J()&&me(),Ne(),C()}),{destroy(){if(H=!0,U(!1),ee&&clearTimeout(ee),document.removeEventListener("mousemove",He,!0),document.removeEventListener("mouseup",De,!0),a){try{a.close(1e3,"session switch")}catch{}a=void 0}n.innerHTML="",n.classList.remove("terminal-pending"),window.removeEventListener("resize",Ke),Oe.disconnect(),document.removeEventListener("keydown",Ue,!0)},focus(){i.focus()},sendInput(s){fe(s)},toggleCopyMode(){Ve()}}})()}var K=window.__TMUX_WEB_SHELL__,h=null,y=null,Qe=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,T=Qe;window.__tmuxWebScopeAgent=Qe;var et="sessions",W="",we="",be="",tt="";function M(n){return T?"/a/"+encodeURIComponent(T)+n:n}var m=document.getElementById("sidebar-content"),F=document.getElementById("main-placeholder"),I=document.getElementById("terminal-container"),se=document.getElementById("file-editor"),Ot=document.getElementById("fe-path"),D=document.getElementById("fe-status"),ae=document.getElementById("fe-content"),V=document.getElementById("fe-save"),qt=document.getElementById("fe-delete"),Xt=document.getElementById("fe-back"),nt=document.getElementById("fe-new-name"),Jt=document.getElementById("fe-new-btn"),Re=document.getElementById("settings-backdrop"),it=document.getElementById("settings-popover"),st=document.querySelector(".sidebar");function Zt(){return window.matchMedia("(max-width: 640px)").matches}function Ae(){Zt()&&st.classList.add("collapsed")}function at(){st.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{at(),ot("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{at(),ot("files")});function ot(n){et=n,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));let e=document.getElementById(n==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),n==="sessions"?(Ye="",O()):rn()}var Ye="";async function O(){try{let e=await(await fetch("/api/sidebar/sessions")).json(),t=JSON.stringify({recent:e.recent,agents:e.agents});if(t===Ye)return;Ye=t,Gt(e)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let n=document.getElementById("ns-btn");n&&n.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function Gt(n){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let e=n.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(T?" clickable":""),t.textContent=T?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",T&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(t);for(let a of e){let l=document.createElement("div");l.className="session-item"+(a.name===y&&!T?" active":""),l.setAttribute("data-session",a.name),l.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>
10
+ `).join("\r")}async function Ht(s){let o=Mt(s.clipboardData);if(o){s.preventDefault(),s.stopPropagation();let B=Date.now();if(B-ge<50)return;ge=B,await Ie(o);return}let u=s.clipboardData?.getData("text/plain");if(!u)return;s.preventDefault(),s.stopPropagation();let v=Date.now();v-ge<50||(ge=v,i.pasteText(Ue(u)))}let te=0;function Dt(s){s.preventDefault(),te++,Fe(s.dataTransfer)&&n.classList.add("terminal-drag-over")}function Rt(s){s.preventDefault(),s.dataTransfer&&(s.dataTransfer.dropEffect="copy")}function At(s){s.preventDefault(),te--,te<=0&&(te=0,n.classList.remove("terminal-drag-over"))}async function Yt(s){s.preventDefault(),te=0,n.classList.remove("terminal-drag-over");let o=Fe(s.dataTransfer);o&&await Ie(o)}function Ke(s){!Et||s.key!=="Escape"||Z||s.defaultPrevented||s.repeat||s.metaKey||s.ctrlKey||s.altKey||i.isFocused()&&(s.preventDefault(),s.stopPropagation(),fe("\x1B"))}n.addEventListener("paste",Ht),n.addEventListener("dragenter",Dt),n.addEventListener("dragover",Rt),n.addEventListener("dragleave",At),n.addEventListener("drop",Yt),document.addEventListener("keydown",Ke,!0);function $t(s){if(s.touches.length!==1){r=null;return}let o=s.touches[0];o&&(r={startX:o.clientX,startY:o.clientY,lastX:o.clientX,lastY:o.clientY,scrolling:!1},s.stopPropagation())}function Pt(s){if(!r||s.touches.length!==1)return;let o=s.touches[0];if(!o)return;let u=o.clientY-r.startY,v=o.clientX-r.startX;if(!r.scrolling){if(Math.abs(u)<8||Math.abs(u)<Math.abs(v))return;r.scrolling=!0,d=Date.now()+500}s.preventDefault(),s.stopPropagation(),jt(-(o.clientY-r.lastY),o.clientX,o.clientY),r.lastX=o.clientX,r.lastY=o.clientY}function zt(s){if(!r)return;let o=r.scrolling;r=null,s.stopPropagation(),o?(d=Date.now()+500,s.preventDefault()):i.focus()}function Nt(s){r=null,s.stopPropagation()}function Wt(s){s.pointerType==="touch"&&Date.now()<d&&(s.preventDefault(),s.stopPropagation())}function jt(s,o,u){(n.querySelector(".xterm-screen")??n).dispatchEvent(new WheelEvent("wheel",{deltaY:s,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:o,clientY:u,bubbles:!0,cancelable:!0}))}n.addEventListener("touchstart",$t,{passive:!0,capture:!0}),n.addEventListener("touchmove",Pt,{passive:!1,capture:!0}),n.addEventListener("touchend",zt,{passive:!1,capture:!0}),n.addEventListener("touchcancel",Nt,{passive:!0,capture:!0}),n.addEventListener("pointerup",Wt,!0);function Oe(){C()}window.addEventListener("resize",Oe),window.visualViewport?.addEventListener("resize",C),window.visualViewport?.addEventListener("scroll",C);let qe=new ResizeObserver(()=>C());return qe.observe(n),document.fonts?.ready.then(()=>{C(),setTimeout(C,50),setTimeout(C,200)}),n.addEventListener("focusin",Ct,!0),requestAnimationFrame(()=>{J()&&me(),We(),C()}),{destroy(){if(H=!0,U(!1),ee&&clearTimeout(ee),document.removeEventListener("mousemove",He,!0),document.removeEventListener("mouseup",De,!0),a){try{a.close(1e3,"session switch")}catch{}a=void 0}n.innerHTML="",n.classList.remove("terminal-pending"),window.removeEventListener("resize",Oe),qe.disconnect(),document.removeEventListener("keydown",Ke,!0)},focus(){i.focus()},sendInput(s){fe(s)},toggleCopyMode(){_e()}}})()}var K=window.__TMUX_WEB_SHELL__,h=null,y=null,et=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,T=et;window.__tmuxWebScopeAgent=et;var Re="sessions",W="",we="",be="",tt="";function M(n){return T?"/a/"+encodeURIComponent(T)+n:n}var m=document.getElementById("sidebar-content"),F=document.getElementById("main-placeholder"),I=document.getElementById("terminal-container"),se=document.getElementById("file-editor"),Ot=document.getElementById("fe-path"),D=document.getElementById("fe-status"),ae=document.getElementById("fe-content"),V=document.getElementById("fe-save"),qt=document.getElementById("fe-delete"),Xt=document.getElementById("fe-back"),nt=document.getElementById("fe-new-name"),Jt=document.getElementById("fe-new-btn"),Ae=document.getElementById("settings-backdrop"),it=document.getElementById("settings-popover"),st=document.querySelector(".sidebar");function Zt(){return window.matchMedia("(max-width: 640px)").matches}function Ye(){Zt()&&st.classList.add("collapsed")}function at(){st.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{at(),ot("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{at(),ot("files")});function ot(n){Re=n,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));let e=document.getElementById(n==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),n==="sessions"?($e="",O()):rn()}var $e="";async function O(){if(Re!=="sessions")return;try{let e=await(await fetch("/api/sidebar/sessions")).json(),t=JSON.stringify({recent:e.recent,agents:e.agents});if(t===$e)return;$e=t,Gt(e)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let n=document.getElementById("ns-btn");n&&n.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function Gt(n){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let e=n.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(T?" clickable":""),t.textContent=T?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",T&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(t);for(let a of e){let l=document.createElement("div");l.className="session-item"+(a.name===y&&!T?" active":""),l.setAttribute("data-session",a.name),l.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>
11
11
  <span>${f(a.name)}</span>
12
12
  <button class="session-edit-btn" data-session="${f(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>
13
13
  `,l.addEventListener("click",r=>{r.target.closest(".session-edit-btn")||oe(a.name)}),m.appendChild(l);let c=l.querySelector(".session-edit-btn");c&&c.addEventListener("click",r=>{r.stopPropagation(),fn(a.name,c)})}let i=n.agents||[];for(let a of i){if(!a.sessions||a.sessions.length===0)continue;let l=a.agentId===T,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(a.online?"":" agent-offline-label")+(!l&&a.online?" clickable":""),c.textContent=a.agentName+(a.online?"":" (offline)"),!l&&a.online&&(c.title="\u6253\u5F00 "+a.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(a.agentId)+"/"})),m.appendChild(c);for(let r of a.sessions){let d=document.createElement("div");d.className="session-item"+(r.name===y&&T===a.agentId?" active":"")+(a.online?"":" session-offline"),d.setAttribute("data-session",r.name),d.setAttribute("data-agent",a.agentId),d.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${r.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>
14
- <span>${f(r.name)}</span><span class="meta">${a.online?"":"offline"}</span>`,a.online&&d.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||oe(r.name,a.agentId)}),m.appendChild(d)}}}function f(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function oe(n,e){if(!(y===n&&h&&T===(e||null))){se.style.display="none",F.style.display="none",I.style.display="",I.classList.add("terminal-pending"),Ae(),h&&(Se(),h.destroy(),h=null),y=n,T=e||null,window.__tmuxWebScopeAgent=T;for(let t of m.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===n&&(t.getAttribute("data-agent")||null)===T);try{h=await Kt(I,n,{terminal:K.terminal,scrollback:K.scrollback,theme:K.theme,renderer:K.renderer,wsBase:T?"/ws/a/"+encodeURIComponent(T):K.wsBase||"/ws"}),vt()}catch(t){console.error("[shell] terminal init failed:",t);let i=t&&typeof t=="object"&&"message"in t?t.message:String(t);I.textContent="Failed to open terminal for "+n+": "+i,I.classList.remove("terminal-pending")}}}var Le=[],g=null,lt=!1;function Qt(n){if(!g||!g.files.length)return null;let e=g.repoRoot;if(!e||!n.startsWith(e))return null;let t=n.slice(e.length+1);for(let i of g.files)if(i.path===t)return i.status;return null}async function en(n){lt=!0;try{let e=await fetch(M("/api/git/status?path="+encodeURIComponent(n)));e.ok?g=await e.json():g=null}catch{g=null}finally{lt=!1}}function tn(){if(!g||!g.branch)return"";let{branch:n,linesAdded:e,linesRemoved:t}=g,i=`<div class="sidebar-git"><div class="git-branch-badge">
14
+ <span>${f(r.name)}</span><span class="meta">${a.online?"":"offline"}</span>`,a.online&&d.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||oe(r.name,a.agentId)}),m.appendChild(d)}}}function f(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function oe(n,e){if(!(y===n&&h&&T===(e||null))){se.style.display="none",F.style.display="none",I.style.display="",I.classList.add("terminal-pending"),Ye(),h&&(Se(),h.destroy(),h=null),y=n,T=e||null,window.__tmuxWebScopeAgent=T;for(let t of m.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===n&&(t.getAttribute("data-agent")||null)===T);try{h=await Kt(I,n,{terminal:K.terminal,scrollback:K.scrollback,theme:K.theme,renderer:K.renderer,wsBase:T?"/ws/a/"+encodeURIComponent(T):K.wsBase||"/ws"}),vt()}catch(t){console.error("[shell] terminal init failed:",t);let i=t&&typeof t=="object"&&"message"in t?t.message:String(t);I.textContent="Failed to open terminal for "+n+": "+i,I.classList.remove("terminal-pending")}}}var Le=[],g=null,lt=!1;function Qt(n){if(!g||!g.files.length)return null;let e=g.repoRoot;if(!e||!n.startsWith(e))return null;let t=n.slice(e.length+1);for(let i of g.files)if(i.path===t)return i.status;return null}async function en(n){lt=!0;try{let e=await fetch(M("/api/git/status?path="+encodeURIComponent(n)));e.ok?g=await e.json():g=null}catch{g=null}finally{lt=!1}}function tn(){if(!g||!g.branch)return"";let{branch:n,linesAdded:e,linesRemoved:t}=g,i=`<div class="sidebar-git"><div class="git-branch-badge">
15
15
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
16
- ${f(n)}</div>`;return(e>0||t>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${t}</span></div>`),i+"</div>"}m.addEventListener("click",n=>{n.target.closest(".sidebar-git")&&q&&g&&(n.stopPropagation(),pt())});var le=document.getElementById("header-git"),rt=document.getElementById("git-diff-view"),ct=document.getElementById("gd-path"),Ee=document.getElementById("gd-status"),xe=document.getElementById("gd-content"),nn=document.getElementById("gd-back"),q=null;function re(n){if(!n||!n.branch){le.style.display="none",q=null;return}q=n.repoRoot;let{branch:e,linesAdded:t,linesRemoved:i}=n,a=`<span class="branch">${f(e)}</span>`;(t>0||i>0)&&(a+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${i}</span>`),le.innerHTML=a,le.style.display="block"}le.addEventListener("click",n=>{q&&g&&(n.stopPropagation(),pt())});var k=document.getElementById("git-popover"),ke=document.getElementById("git-popover-backdrop");function $e(){let n=le.getBoundingClientRect();k.style.top=n.bottom+4+"px",k.style.left=Math.max(8,Math.min(n.left,window.innerWidth-400))+"px"}function dt(){let n=q,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(Ce(),n){et="files",document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let t=document.getElementById("mode-files");t&&t.classList.add("active"),j(n)}})}var ut="";function pt(){if(!g)return;let n=g.files||[];if(!n.length)k.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
16
+ ${f(n)}</div>`;return(e>0||t>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${t}</span></div>`),i+"</div>"}m.addEventListener("click",n=>{n.target.closest(".sidebar-git")&&q&&g&&(n.stopPropagation(),pt())});var le=document.getElementById("header-git"),rt=document.getElementById("git-diff-view"),ct=document.getElementById("gd-path"),Ee=document.getElementById("gd-status"),xe=document.getElementById("gd-content"),nn=document.getElementById("gd-back"),q=null;function re(n){if(!n||!n.branch){le.style.display="none",q=null;return}q=n.repoRoot;let{branch:e,linesAdded:t,linesRemoved:i}=n,a=`<span class="branch">${f(e)}</span>`;(t>0||i>0)&&(a+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${i}</span>`),le.innerHTML=a,le.style.display="block"}le.addEventListener("click",n=>{q&&g&&(n.stopPropagation(),pt())});var k=document.getElementById("git-popover"),ke=document.getElementById("git-popover-backdrop");function Pe(){let n=le.getBoundingClientRect();k.style.top=n.bottom+4+"px",k.style.left=Math.max(8,Math.min(n.left,window.innerWidth-400))+"px"}function dt(){let n=q,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(Ce(),n){Re="files",document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let t=document.getElementById("mode-files");t&&t.classList.add("active"),j(n)}})}var ut="";function pt(){if(!g)return;let n=g.files||[];if(!n.length)k.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
17
17
  <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${n.length} file${n.length>1?"s":""} changed</div>`;for(let t=0;t<n.length;t++){let i=n[t],a=i.additions||0,l=i.deletions||0,c="";switch(i.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}e+=`<div class="git-popover-item" data-idx="${t}">
18
18
  ${c}<span class="file-path">${f(i.path)}</span>
19
19
  <span class="file-add">${a>0?"+"+a:""}</span>
20
20
  <span class="file-del">${l>0?"-"+l:""}</span>
21
- </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ut=e,k.innerHTML=e,k.querySelectorAll(".git-popover-item").forEach(t=>{let i=parseInt(t.dataset.idx||"0",10),a=n[i];!a||a.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>mt(a.path)))})}$e(),k.classList.add("open"),ke.classList.add("open"),dt()}function sn(){return window.innerWidth>=1024}function ft(n,e){let t=(n+(e&&n?`
21
+ </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ut=e,k.innerHTML=e,k.querySelectorAll(".git-popover-item").forEach(t=>{let i=parseInt(t.dataset.idx||"0",10),a=n[i];!a||a.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>mt(a.path)))})}Pe(),k.classList.add("open"),ke.classList.add("open"),dt()}function sn(){return window.innerWidth>=1024}function ft(n,e){let t=(n+(e&&n?`
22
22
  `:"")+e).split(`
23
23
  `),i="",a=0,l=0;for(let c of t){let r=c;if(r.startsWith("@@")){let d=r.match(/@@\s+-(\d+)[^+]*\+(\d+)/);d&&(a=parseInt(d[1],10)-1,l=parseInt(d[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${f(r)}</span></div>`;continue}if(r.startsWith("diff --git")||r.startsWith("index ")||r.startsWith("--- ")||r.startsWith("+++ ")||r.startsWith("new file")||r.startsWith("deleted file")){r.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${f(r)}</span></div>`);continue}if(r.startsWith("+"))l++,i+=`<div class="diff-line add"><span class="ln-no">${l}</span><span class="ln-body">${f(r)}</span></div>`;else if(r.startsWith("-"))a++,i+=`<div class="diff-line del"><span class="ln-no">${a}</span><span class="ln-body">${f(r)}</span></div>`;else if(r.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${f(r)}</span></div>`;else{if(!r&&!a&&!l)continue;a++,l++,i+=`<div class="diff-line"><span class="ln-no">${a}</span><span class="ln-no" style="margin-right:12px">${l}</span><span class="ln-body">${f(r)}</span></div>`}}return{html:i,lineCount:t.length}}var Te=null;async function mt(n){let e=q;if(e)if(Ce(),sn()){I.style.display="none",h&&(Se(),h.destroy(),h=null,Te=y,y=null),re(null),se.style.display="none",F.style.display="none",rt.style.display="flex",ct.textContent=n,Ee.textContent="Loading\u2026",xe.innerHTML="";try{let t=await fetch(M("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(n)));if(!t.ok)throw new Error(await t.text());let i=await t.json();on(n,i.diff,i.stagedDiff)}catch(t){let i=t instanceof Error?t.message:String(t);Ee.textContent="Error",xe.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${f(i)}</div>`}}else{k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
25
  <span class="diff-filename">${f(n)}</span>
26
26
  <span class="diff-loading">Loading\u2026</span>
27
- </div></div>`,$e(),k.classList.add("open"),ke.classList.add("open");try{let t=await fetch(M("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(n)));if(!t.ok)throw new Error(await t.text());let i=await t.json();an(n,i.diff,i.stagedDiff)}catch(t){let i=t instanceof Error?t.message:String(t);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
27
+ </div></div>`,Pe(),k.classList.add("open"),ke.classList.add("open");try{let t=await fetch(M("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(n)));if(!t.ok)throw new Error(await t.text());let i=await t.json();an(n,i.diff,i.stagedDiff)}catch(t){let i=t instanceof Error?t.message:String(t);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
28
28
  <button class="diff-back">\u2190 Back</button>
29
29
  <span class="diff-filename">${f(n)}</span>
30
30
  </div><div class="diff-empty">Error loading diff: ${f(i)}</div></div>`}}}function an(n,e,t){if(!e&&!t){k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
@@ -34,7 +34,7 @@ var Ft="https://esm.sh/ghostty-web@0.4.0",Vt=/\x1b\](?:10|11|110|111|104)(?:;[^\
34
34
  <button class="diff-back">\u2190 Back</button>
35
35
  <span class="diff-filename">${f(n)}</span>
36
36
  <span class="diff-filesize">${a} lines</span>
37
- </div><div class="diff-content">${i}</div></div>`,k.querySelector(".diff-back")?.addEventListener("click",()=>{k.innerHTML=ut,$e(),g&&k.querySelectorAll(".git-popover-item").forEach(l=>{let c=parseInt(l.dataset.idx||"0",10),r=g.files[c];!r||r.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>mt(r.path)))}),dt()})}function on(n,e,t){if(!e&&!t){Ee.textContent="No diff",xe.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:i,lineCount:a}=ft(e,t);ct.textContent=n,Ee.textContent=a+" lines",xe.innerHTML=i}function ln(){rt.style.display="none",Te?(I.style.display="",I.classList.add("terminal-pending"),oe(Te),Te=null):y?(I.style.display="",I.classList.add("terminal-pending"),vt()):F.style.display="flex"}nn.addEventListener("click",ln);function Ce(){k.classList.remove("open"),ke.classList.remove("open")}ke.addEventListener("click",Ce),document.addEventListener("keydown",n=>{n.key==="Escape"&&(Ce(),Be())});async function ht(n){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(n))).json();if(e.path){let t=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(t.ok){let i=await t.json();g=i,re(i);return}}}catch{}g=null,re(null)}var Me=null;function vt(){Se(),y&&(ht(y),Me=setInterval(()=>{y&&ht(y)},3e4))}function Se(){Me&&(clearInterval(Me),Me=null)}function rn(){if(Le=K.fsRoots||[],Le.length===0){if(y){cn(y);return}m.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}m.innerHTML="";for(let n of Le){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> ${f(n)}`,e.addEventListener("click",()=>j(n)),m.appendChild(e)}}async function cn(n){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch(M("/api/fs/session-path?session="+encodeURIComponent(n)))).json();e.path?j(e.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function j(n){W=n,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=en(n);try{let t=await(await fetch(M("/api/fs/list?path="+encodeURIComponent(n)))).json();await e,m.innerHTML="",re(g),m.innerHTML+=tn();let i=dn(n);if(i&&!Le.some(l=>n===l)){let l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>j(i)),m.appendChild(l)}if(t.dirs)for(let l of t.dirs){let c=l.split("/").pop()||l.split("\\").pop(),r=document.createElement("div");r.className="file-tree-item",r.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${f(c)}`,r.addEventListener("click",()=>j(l)),m.appendChild(r)}else m.innerHTML="";let a=t.files||[];for(let l of a){let c=l.split("/").pop()||l.split("\\").pop(),r=Qt(l),d="",p="";if(r)switch(r){case"M":d="git-mod",p='<span class="git-file-status M">M</span>';break;case"A":d="git-add",p='<span class="git-file-status A">A</span>';break;case"D":d="git-del",p='<span class="git-file-status D">D</span>';break;case"?":d="git-untracked",p='<span class="git-file-status ?">?</span>';break;case"R":d="git-mod",p='<span class="git-file-status R">R</span>';break}let L=document.createElement("div");L.className="file-tree-item file"+(d?" "+d:""),L.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${p}${f(c)}`,L.addEventListener("click",()=>un(l)),m.appendChild(L)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function dn(n){let e=n.lastIndexOf("/");return e<=0?null:n.substring(0,e)}async function un(n){we=n,I.style.display="none",h&&(Se(),h.destroy(),h=null,y=null),re(null),Ae(),F.style.display="none",se.style.display="flex",Ot.textContent=n,D.textContent="Loading...",V.style.display="none";try{let e=await fetch(M("/api/file?path="+encodeURIComponent(n)));if(!e.ok){let i=await e.json();D.textContent=i.error||"Failed to load",ae.value="";return}let t=await e.json();ae.value=t.content,be=t.content,tt=t.content,D.textContent=t.size+" bytes",V.style.display=""}catch{D.textContent="Failed to load",ae.value=""}}ae.addEventListener("input",()=>{be=ae.value}),V.addEventListener("click",async()=>{V.textContent="Saving...",V.disabled=!0;try{let n=await fetch(M("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we,content:be})});if(!n.ok){let e=await n.json();D.textContent=e.error||"Save failed";return}tt=be,D.textContent="Saved"}catch{D.textContent="Save failed"}finally{V.disabled=!1,V.textContent="Save"}}),qt.addEventListener("click",async()=>{if(confirm("Delete "+we+"?"))try{let n=await fetch(M("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we})});if(!n.ok){let e=await n.json();D.textContent=e.error||"Delete failed";return}se.style.display="none",F.style.display="flex",W&&j(W)}catch{D.textContent="Delete failed"}}),Xt.addEventListener("click",()=>{se.style.display="none",F.style.display="flex",W&&j(W)}),Jt.addEventListener("click",async()=>{let n=nt.value.trim();if(!n)return;let e=W+"/"+n;try{let t=await fetch(M("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!t.ok){let i=await t.json();D.textContent=i.error||"Create failed";return}nt.value="",W&&j(W)}catch{D.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let n=it.classList.toggle("open");Re.classList.toggle("open",n)}),Re.addEventListener("click",()=>{it.classList.remove("open"),Re.classList.remove("open")});var _="",ce=document.createElement("div");ce.className="session-popover-backdrop",document.body.appendChild(ce);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
37
+ </div><div class="diff-content">${i}</div></div>`,k.querySelector(".diff-back")?.addEventListener("click",()=>{k.innerHTML=ut,Pe(),g&&k.querySelectorAll(".git-popover-item").forEach(l=>{let c=parseInt(l.dataset.idx||"0",10),r=g.files[c];!r||r.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>mt(r.path)))}),dt()})}function on(n,e,t){if(!e&&!t){Ee.textContent="No diff",xe.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:i,lineCount:a}=ft(e,t);ct.textContent=n,Ee.textContent=a+" lines",xe.innerHTML=i}function ln(){rt.style.display="none",Te?(I.style.display="",I.classList.add("terminal-pending"),oe(Te),Te=null):y?(I.style.display="",I.classList.add("terminal-pending"),vt()):F.style.display="flex"}nn.addEventListener("click",ln);function Ce(){k.classList.remove("open"),ke.classList.remove("open")}ke.addEventListener("click",Ce),document.addEventListener("keydown",n=>{n.key==="Escape"&&(Ce(),Be())});async function ht(n){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(n))).json();if(e.path){let t=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(t.ok){let i=await t.json();g=i,re(i);return}}}catch{}g=null,re(null)}var Me=null;function vt(){Se(),y&&(ht(y),Me=setInterval(()=>{y&&ht(y)},3e4))}function Se(){Me&&(clearInterval(Me),Me=null)}function rn(){if(Le=K.fsRoots||[],Le.length===0){if(y){cn(y);return}m.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}m.innerHTML="";for(let n of Le){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> ${f(n)}`,e.addEventListener("click",()=>j(n)),m.appendChild(e)}}async function cn(n){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch(M("/api/fs/session-path?session="+encodeURIComponent(n)))).json();e.path?j(e.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function j(n){W=n,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=en(n);try{let t=await(await fetch(M("/api/fs/list?path="+encodeURIComponent(n)))).json();await e,m.innerHTML="",re(g),m.innerHTML+=tn();let i=dn(n);if(i&&!Le.some(l=>n===l)){let l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>j(i)),m.appendChild(l)}if(t.dirs)for(let l of t.dirs){let c=l.split("/").pop()||l.split("\\").pop(),r=document.createElement("div");r.className="file-tree-item",r.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${f(c)}`,r.addEventListener("click",()=>j(l)),m.appendChild(r)}else m.innerHTML="";let a=t.files||[];for(let l of a){let c=l.split("/").pop()||l.split("\\").pop(),r=Qt(l),d="",p="";if(r)switch(r){case"M":d="git-mod",p='<span class="git-file-status M">M</span>';break;case"A":d="git-add",p='<span class="git-file-status A">A</span>';break;case"D":d="git-del",p='<span class="git-file-status D">D</span>';break;case"?":d="git-untracked",p='<span class="git-file-status ?">?</span>';break;case"R":d="git-mod",p='<span class="git-file-status R">R</span>';break}let L=document.createElement("div");L.className="file-tree-item file"+(d?" "+d:""),L.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${p}${f(c)}`,L.addEventListener("click",()=>un(l)),m.appendChild(L)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function dn(n){let e=n.lastIndexOf("/");return e<=0?null:n.substring(0,e)}async function un(n){we=n,I.style.display="none",h&&(Se(),h.destroy(),h=null,y=null),re(null),Ye(),F.style.display="none",se.style.display="flex",Ot.textContent=n,D.textContent="Loading...",V.style.display="none";try{let e=await fetch(M("/api/file?path="+encodeURIComponent(n)));if(!e.ok){let i=await e.json();D.textContent=i.error||"Failed to load",ae.value="";return}let t=await e.json();ae.value=t.content,be=t.content,tt=t.content,D.textContent=t.size+" bytes",V.style.display=""}catch{D.textContent="Failed to load",ae.value=""}}ae.addEventListener("input",()=>{be=ae.value}),V.addEventListener("click",async()=>{V.textContent="Saving...",V.disabled=!0;try{let n=await fetch(M("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we,content:be})});if(!n.ok){let e=await n.json();D.textContent=e.error||"Save failed";return}tt=be,D.textContent="Saved"}catch{D.textContent="Save failed"}finally{V.disabled=!1,V.textContent="Save"}}),qt.addEventListener("click",async()=>{if(confirm("Delete "+we+"?"))try{let n=await fetch(M("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we})});if(!n.ok){let e=await n.json();D.textContent=e.error||"Delete failed";return}se.style.display="none",F.style.display="flex",W&&j(W)}catch{D.textContent="Delete failed"}}),Xt.addEventListener("click",()=>{se.style.display="none",F.style.display="flex",W&&j(W)}),Jt.addEventListener("click",async()=>{let n=nt.value.trim();if(!n)return;let e=W+"/"+n;try{let t=await fetch(M("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!t.ok){let i=await t.json();D.textContent=i.error||"Create failed";return}nt.value="",W&&j(W)}catch{D.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let n=it.classList.toggle("open");Ae.classList.toggle("open",n)}),Ae.addEventListener("click",()=>{it.classList.remove("open"),Ae.classList.remove("open")});var _="",ce=document.createElement("div");ce.className="session-popover-backdrop",document.body.appendChild(ce);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
38
38
  <label>Rename session</label>
39
39
  <input type="text" id="sp-name" />
40
40
  </div>
@@ -44,4 +44,4 @@ var Ft="https://esm.sh/ghostty-web@0.4.0",Vt=/\x1b\](?:10|11|110|111|104)(?:;[^\
44
44
  </div>
45
45
  <hr class="sp-divider" />
46
46
  <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(P);function pn(n){let e=n.getBoundingClientRect();P.style.top=e.bottom+4+"px",P.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function fn(n,e){_=n;let t=document.getElementById("sp-name");t.value=n,ce.classList.add("open"),P.style.display="block",pn(e),setTimeout(()=>t.focus(),50)}function de(){ce.classList.remove("open"),P.style.display="none"}ce.addEventListener("click",de),document.getElementById("sp-save").addEventListener("click",async()=>{let n=document.getElementById("sp-name").value.trim();if(!n||n===_){de();return}try{if(!(await fetch(M("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:_,newName:n})})).ok)return;de(),O(),y===_&&oe(n)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+_+'"?'))try{if(!(await fetch(M("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:_})})).ok)return;de(),y===_&&(h&&(h.destroy(),h=null),y=null,I.style.display="none",F.style.display="flex"),O()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",de);var mn={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",n=>{let e=n.target.closest("button[data-key]");if(!e||!h)return;let t=mn[e.dataset.key];t&&(h.sendInput(t),h.focus())});var R=document.getElementById("mk-input"),gt=document.getElementById("mk-send");function yt(){if(!R||!h)return;let n=R.value;n&&(h.sendInput(n+"\r"),R.value="",R.rows=1,h.focus())}if(R&&(R.addEventListener("input",()=>{R.rows=1;let n=R.value.split(`
47
- `).length;R.rows=Math.min(n,4)}),R.addEventListener("keydown",n=>{n.key==="Enter"&&!n.shiftKey&&(n.preventDefault(),yt())})),gt&&gt.addEventListener("click",yt),window.visualViewport){let n=document.querySelector(".app-layout"),e=()=>{n&&(n.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})}var Pe=document.getElementById("process-panel"),ue=document.getElementById("proc-list"),hn=document.getElementById("proc-close"),ze=document.getElementById("proc-backdrop"),vn=document.getElementById("header-ram");function gn(){ue.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(M("/api/system/processes")).then(n=>n.json()).then(n=>{if(!Array.isArray(n)||!n.length){ue.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let t of n){let i=t.rss,a=i<1048576?(i/1024).toFixed(0)+"K":(i/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+a+'</span><span class="proc-cmd" title="'+f(t.command)+'">'+f(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}ue.innerHTML=e;for(let t of ue.querySelectorAll(".proc-row")){let i=t.querySelector(".proc-kill");i&&i.addEventListener("click",a=>{let l=a.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch(M("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?l.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ue.innerHTML='<div class="proc-empty">Failed to load</div>'})}function yn(){Pe.classList.add("open"),ze.classList.add("open"),gn()}function Be(){Pe.classList.remove("open"),ze.classList.remove("open")}vn.addEventListener("click",n=>{n.stopPropagation(),Pe.classList.contains("open")?Be():yn()}),hn.addEventListener("click",Be),ze.addEventListener("click",Be),window.__openSession=oe,window.__refreshSidebar=O,O(),Ae(),setInterval(()=>{document.visibilityState==="visible"&&O()},1e4);
47
+ `).length;R.rows=Math.min(n,4)}),R.addEventListener("keydown",n=>{n.key==="Enter"&&!n.shiftKey&&(n.preventDefault(),yt())})),gt&&gt.addEventListener("click",yt),window.visualViewport){let n=document.querySelector(".app-layout"),e=()=>{n&&(n.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})}var ze=document.getElementById("process-panel"),ue=document.getElementById("proc-list"),hn=document.getElementById("proc-close"),Ne=document.getElementById("proc-backdrop"),vn=document.getElementById("header-ram");function gn(){ue.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(M("/api/system/processes")).then(n=>n.json()).then(n=>{if(!Array.isArray(n)||!n.length){ue.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let t of n){let i=t.rss,a=i<1048576?(i/1024).toFixed(0)+"K":(i/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+a+'</span><span class="proc-cmd" title="'+f(t.command)+'">'+f(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}ue.innerHTML=e;for(let t of ue.querySelectorAll(".proc-row")){let i=t.querySelector(".proc-kill");i&&i.addEventListener("click",a=>{let l=a.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch(M("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?l.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ue.innerHTML='<div class="proc-empty">Failed to load</div>'})}function yn(){ze.classList.add("open"),Ne.classList.add("open"),gn()}function Be(){ze.classList.remove("open"),Ne.classList.remove("open")}vn.addEventListener("click",n=>{n.stopPropagation(),ze.classList.contains("open")?Be():yn()}),hn.addEventListener("click",Be),Ne.addEventListener("click",Be),window.__openSession=oe,window.__refreshSidebar=O,O(),Ye(),setInterval(()=>{document.visibilityState==="visible"&&O()},1e4);
@@ -1,21 +1,21 @@
1
- import{initTerminal as ke}from"./terminal-core.js";const M=window.__TMUX_WEB_SHELL__;let p=null,u=null;const se=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let v=se;window.__tmuxWebScopeAgent=se;let ie="sessions",E="",j="",W="",oe="";function y(e){return v?"/a/"+encodeURIComponent(v)+e:e}const r=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),h=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ce=document.getElementById("fe-path"),b=document.getElementById("fe-status"),$=document.getElementById("fe-content"),I=document.getElementById("fe-save"),Ie=document.getElementById("fe-delete"),Be=document.getElementById("fe-back"),ae=document.getElementById("fe-new-name"),xe=document.getElementById("fe-new-btn"),Y=document.getElementById("settings-backdrop"),le=document.getElementById("settings-popover"),ce=document.querySelector(".sidebar");function Me(){return window.matchMedia("(max-width: 640px)").matches}function Z(){Me()&&ce.classList.add("collapsed")}function de(){ce.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{de(),re("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{de(),re("files")});function re(e){ie=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"?(Q="",S()):_e()}let Q="";async function S(){try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents});if(s===Q)return;Q=s,Se(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function Se(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"+(v?" clickable":""),t.textContent=v?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",v&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t);for(const i of n){const l=document.createElement("div");l.className="session-item"+(i.name===u&&!v?" active":""),l.setAttribute("data-session",i.name),l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.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>
1
+ import{initTerminal as ke}from"./terminal-core.js";const M=window.__TMUX_WEB_SHELL__;let p=null,u=null;const ie=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let v=ie;window.__tmuxWebScopeAgent=ie;let Y="sessions",E="",j="",W="",oe="";function y(e){return v?"/a/"+encodeURIComponent(v)+e:e}const r=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),h=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ce=document.getElementById("fe-path"),b=document.getElementById("fe-status"),$=document.getElementById("fe-content"),I=document.getElementById("fe-save"),Ie=document.getElementById("fe-delete"),Be=document.getElementById("fe-back"),ae=document.getElementById("fe-new-name"),xe=document.getElementById("fe-new-btn"),Z=document.getElementById("settings-backdrop"),le=document.getElementById("settings-popover"),ce=document.querySelector(".sidebar");function Me(){return window.matchMedia("(max-width: 640px)").matches}function Q(){Me()&&ce.classList.add("collapsed")}function de(){ce.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{de(),re("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{de(),re("files")});function re(e){Y=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"?(ee="",S()):_e()}let ee="";async function S(){if(Y!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents});if(s===ee)return;ee=s,Se(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function Se(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"+(v?" clickable":""),t.textContent=v?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",v&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t);for(const i of n){const l=document.createElement("div");l.className="session-item"+(i.name===u&&!v?" active":""),l.setAttribute("data-session",i.name),l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.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
2
  <span>${d(i.name)}</span>
3
3
  <button class="session-edit-btn" data-session="${d(i.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>
4
4
  `,l.addEventListener("click",c=>{c.target.closest(".session-edit-btn")||R(i.name)}),r.appendChild(l);const o=l.querySelector(".session-edit-btn");o&&o.addEventListener("click",c=>{c.stopPropagation(),Oe(i.name,o)})}const s=e.agents||[];for(const i of s){if(!i.sessions||i.sessions.length===0)continue;const l=i.agentId===v,o=document.createElement("div");o.className="sidebar-section-label machine-label"+(i.online?"":" agent-offline-label")+(!l&&i.online?" clickable":""),o.textContent=i.agentName+(i.online?"":" (offline)"),!l&&i.online&&(o.title="\u6253\u5F00 "+i.agentName,o.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(i.agentId)+"/"})),r.appendChild(o);for(const c of i.sessions){const a=document.createElement("div");a.className="session-item"+(c.name===u&&v===i.agentId?" active":"")+(i.online?"":" session-offline"),a.setAttribute("data-session",c.name),a.setAttribute("data-agent",i.agentId),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${c.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>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&a.addEventListener("click",g=>{g.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function R(e,n){if(!(u===e&&p&&v===(n||null))){H.style.display="none",C.style.display="none",h.style.display="",h.classList.add("terminal-pending"),Z(),p&&(K(),p.destroy(),p=null),u=e,v=n||null,window.__tmuxWebScopeAgent=v;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===v);try{p=await ke(h,e,{terminal:M.terminal,scrollback:M.scrollback,theme:M.theme,renderer:M.renderer,wsBase:v?"/ws/a/"+encodeURIComponent(v):M.wsBase||"/ws"}),we()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);h.textContent="Failed to open terminal for "+e+": "+s,h.classList.remove("terminal-pending")}}}let z=[],f=null,pe=!1;function Te(e){if(!f||!f.files.length)return null;const n=f.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of f.files)if(s.path===t)return s.status;return null}async function He(e){pe=!0;try{const n=await fetch(y("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{pe=!1}}function $e(){if(!f||!f.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
5
+ <span>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&a.addEventListener("click",g=>{g.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function R(e,n){if(!(u===e&&p&&v===(n||null))){H.style.display="none",C.style.display="none",h.style.display="",h.classList.add("terminal-pending"),Q(),p&&(K(),p.destroy(),p=null),u=e,v=n||null,window.__tmuxWebScopeAgent=v;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===v);try{p=await ke(h,e,{terminal:M.terminal,scrollback:M.scrollback,theme:M.theme,renderer:M.renderer,wsBase:v?"/ws/a/"+encodeURIComponent(v):M.wsBase||"/ws"}),we()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);h.textContent="Failed to open terminal for "+e+": "+s,h.classList.remove("terminal-pending")}}}let z=[],f=null,pe=!1;function Te(e){if(!f||!f.files.length)return null;const n=f.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of f.files)if(s.path===t)return s.status;return null}async function He(e){pe=!0;try{const n=await fetch(y("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{pe=!1}}function $e(){if(!f||!f.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
6
6
  <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>
7
- ${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")&&T&&f&&(e.stopPropagation(),ge())});const N=document.getElementById("header-git"),fe=document.getElementById("git-diff-view"),ue=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Re=document.getElementById("gd-back");let T=null;function D(e){if(!e||!e.branch){N.style.display="none",T=null;return}T=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>`),N.innerHTML=i,N.style.display="block"}N.addEventListener("click",e=>{T&&f&&(e.stopPropagation(),ge())});const m=document.getElementById("git-popover"),G=document.getElementById("git-popover-backdrop");function ee(){const e=N.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function me(){const e=T,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(q(),e){ie="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),k(e)}})}let ve="";function ge(){if(!f)return;const e=f.files||[];if(!e.length)m.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
7
+ ${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")&&T&&f&&(e.stopPropagation(),ge())});const N=document.getElementById("header-git"),fe=document.getElementById("git-diff-view"),ue=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Re=document.getElementById("gd-back");let T=null;function D(e){if(!e||!e.branch){N.style.display="none",T=null;return}T=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>`),N.innerHTML=i,N.style.display="block"}N.addEventListener("click",e=>{T&&f&&(e.stopPropagation(),ge())});const m=document.getElementById("git-popover"),G=document.getElementById("git-popover-backdrop");function te(){const e=N.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function me(){const e=T,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(q(),e){Y="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),k(e)}})}let ve="";function ge(){if(!f)return;const e=f.files||[];if(!e.length)m.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
8
8
  <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,l=s.deletions||0;let o="";switch(s.status){case"M":o='<span class="git-file-status M">M</span>';break;case"A":o='<span class="git-file-status A">A</span>';break;case"D":o='<span class="git-file-status D">D</span>';break;case"?":o='<span class="git-file-status ?">?</span>';break;case"R":o='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
9
9
  ${o}<span class="file-path">${d(s.path)}</span>
10
10
  <span class="file-add">${i>0?"+"+i:""}</span>
11
11
  <span class="file-del">${l>0?"-"+l:""}</span>
12
- </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ve=n,m.innerHTML=n,m.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",()=>he(i.path)))})}ee(),m.classList.add("open"),G.classList.add("open"),me()}function Ne(){return window.innerWidth>=1024}function ye(e,n){const s=(e+(n&&e?`
12
+ </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ve=n,m.innerHTML=n,m.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",()=>he(i.path)))})}te(),m.classList.add("open"),G.classList.add("open"),me()}function Ne(){return window.innerWidth>=1024}function ye(e,n){const s=(e+(n&&e?`
13
13
  `:"")+n).split(`
14
14
  `);let i="",l=0,o=0;for(const c of s){const a=c;if(a.startsWith("@@")){const g=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);g&&(l=parseInt(g[1],10)-1,o=parseInt(g[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(a)}</span></div>`);continue}if(a.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("-"))l++,i+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(a)}</span></div>`;else{if(!a&&!l&&!o)continue;l++,o++,i+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(a)}</span></div>`}}return{html:i,lineCount:s.length}}let V=null;async function he(e){const n=T;if(n)if(q(),Ne()){h.style.display="none",p&&(K(),p.destroy(),p=null,V=u,u=null),D(null),H.style.display="none",C.style.display="none",fe.style.display="flex",ue.textContent=e,O.textContent="Loading\u2026",U.innerHTML="";try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();Pe(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);O.textContent="Error",U.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
15
15
  <button class="diff-back">\u2190 Back</button>
16
16
  <span class="diff-filename">${d(e)}</span>
17
17
  <span class="diff-loading">Loading\u2026</span>
18
- </div></div>`,ee(),m.classList.add("open"),G.classList.add("open");try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();De(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
18
+ </div></div>`,te(),m.classList.add("open"),G.classList.add("open");try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();De(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
19
19
  <button class="diff-back">\u2190 Back</button>
20
20
  <span class="diff-filename">${d(e)}</span>
21
21
  </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function De(e,n,t){if(!n&&!t){m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
@@ -25,7 +25,7 @@ import{initTerminal as ke}from"./terminal-core.js";const M=window.__TMUX_WEB_SHE
25
25
  <button class="diff-back">\u2190 Back</button>
26
26
  <span class="diff-filename">${d(e)}</span>
27
27
  <span class="diff-filesize">${i} lines</span>
28
- </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ve,ee(),f&&m.querySelectorAll(".git-popover-item").forEach(l=>{const o=parseInt(l.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>he(c.path)))}),me()})}function Pe(e,n,t){if(!n&&!t){O.textContent="No diff",U.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}=ye(n,t);ue.textContent=e,O.textContent=i+" lines",U.innerHTML=s}function Ae(){fe.style.display="none",V?(h.style.display="",h.classList.add("terminal-pending"),R(V),V=null):u?(h.style.display="",h.classList.add("terminal-pending"),we()):C.style.display="flex"}Re.addEventListener("click",Ae);function q(){m.classList.remove("open"),G.classList.remove("open")}G.addEventListener("click",q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(q(),X())});async function be(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();f=i,D(i);return}}}catch{}f=null,D(null)}let J=null;function we(){K(),u&&(be(u),J=setInterval(()=>{u&&be(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function _e(){if(z=M.fsRoots||[],z.length===0){if(u){Fe(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 z){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",()=>k(e)),r.appendChild(n)}}async function Fe(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(y("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?k(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 k(e){E=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=He(e);try{const s=await(await fetch(y("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",D(f),r.innerHTML+=$e();const i=je(e);if(i&&!z.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>k(i)),r.appendChild(c)}if(s.dirs)for(const o of s.dirs){const c=o.split("/").pop()||o.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.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(c)}`,a.addEventListener("click",()=>k(o)),r.appendChild(a)}else r.innerHTML="";const l=s.files||[];for(const o of l){const c=o.split("/").pop()||o.split("\\").pop(),a=Te(o);let g="",x="";if(a)switch(a){case"M":g="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":g="git-add",x='<span class="git-file-status A">A</span>';break;case"D":g="git-del",x='<span class="git-file-status D">D</span>';break;case"?":g="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":g="git-mod",x='<span class="git-file-status R">R</span>';break}const F=document.createElement("div");F.className="file-tree-item file"+(g?" "+g:""),F.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${d(c)}`,F.addEventListener("click",()=>We(o)),r.appendChild(F)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function je(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function We(e){j=e,h.style.display="none",p&&(K(),p.destroy(),p=null,u=null),D(null),Z(),C.style.display="none",H.style.display="flex",Ce.textContent=e,b.textContent="Loading...",I.style.display="none";try{const n=await fetch(y("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();b.textContent=s.error||"Failed to load",$.value="";return}const t=await n.json();$.value=t.content,W=t.content,oe=t.content,b.textContent=t.size+" bytes",I.style.display=""}catch{b.textContent="Failed to load",$.value=""}}$.addEventListener("input",()=>{W=$.value}),I.addEventListener("click",async()=>{I.textContent="Saving...",I.disabled=!0;try{const e=await fetch(y("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j,content:W})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Save failed";return}oe=W,b.textContent="Saved"}catch{b.textContent="Save failed"}finally{I.disabled=!1,I.textContent="Save"}}),Ie.addEventListener("click",async()=>{if(confirm("Delete "+j+"?"))try{const e=await fetch(y("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Delete failed";return}H.style.display="none",C.style.display="flex",E&&k(E)}catch{b.textContent="Delete failed"}}),Be.addEventListener("click",()=>{H.style.display="none",C.style.display="flex",E&&k(E)}),xe.addEventListener("click",async()=>{const e=ae.value.trim();if(!e)return;const n=E+"/"+e;try{const t=await fetch(y("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();b.textContent=s.error||"Create failed";return}ae.value="",E&&k(E)}catch{b.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=le.classList.toggle("open");Y.classList.toggle("open",e)}),Y.addEventListener("click",()=>{le.classList.remove("open"),Y.classList.remove("open")});let B="";const P=document.createElement("div");P.className="session-popover-backdrop",document.body.appendChild(P);const L=document.createElement("div");L.id="session-popover",L.className="session-popover",L.style.display="none",L.innerHTML=`<div class="sp-field">
28
+ </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ve,te(),f&&m.querySelectorAll(".git-popover-item").forEach(l=>{const o=parseInt(l.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>he(c.path)))}),me()})}function Pe(e,n,t){if(!n&&!t){O.textContent="No diff",U.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}=ye(n,t);ue.textContent=e,O.textContent=i+" lines",U.innerHTML=s}function Ae(){fe.style.display="none",V?(h.style.display="",h.classList.add("terminal-pending"),R(V),V=null):u?(h.style.display="",h.classList.add("terminal-pending"),we()):C.style.display="flex"}Re.addEventListener("click",Ae);function q(){m.classList.remove("open"),G.classList.remove("open")}G.addEventListener("click",q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(q(),X())});async function be(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();f=i,D(i);return}}}catch{}f=null,D(null)}let J=null;function we(){K(),u&&(be(u),J=setInterval(()=>{u&&be(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function _e(){if(z=M.fsRoots||[],z.length===0){if(u){Fe(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 z){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",()=>k(e)),r.appendChild(n)}}async function Fe(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(y("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?k(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 k(e){E=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=He(e);try{const s=await(await fetch(y("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",D(f),r.innerHTML+=$e();const i=je(e);if(i&&!z.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>k(i)),r.appendChild(c)}if(s.dirs)for(const o of s.dirs){const c=o.split("/").pop()||o.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.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(c)}`,a.addEventListener("click",()=>k(o)),r.appendChild(a)}else r.innerHTML="";const l=s.files||[];for(const o of l){const c=o.split("/").pop()||o.split("\\").pop(),a=Te(o);let g="",x="";if(a)switch(a){case"M":g="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":g="git-add",x='<span class="git-file-status A">A</span>';break;case"D":g="git-del",x='<span class="git-file-status D">D</span>';break;case"?":g="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":g="git-mod",x='<span class="git-file-status R">R</span>';break}const F=document.createElement("div");F.className="file-tree-item file"+(g?" "+g:""),F.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${d(c)}`,F.addEventListener("click",()=>We(o)),r.appendChild(F)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function je(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function We(e){j=e,h.style.display="none",p&&(K(),p.destroy(),p=null,u=null),D(null),Q(),C.style.display="none",H.style.display="flex",Ce.textContent=e,b.textContent="Loading...",I.style.display="none";try{const n=await fetch(y("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();b.textContent=s.error||"Failed to load",$.value="";return}const t=await n.json();$.value=t.content,W=t.content,oe=t.content,b.textContent=t.size+" bytes",I.style.display=""}catch{b.textContent="Failed to load",$.value=""}}$.addEventListener("input",()=>{W=$.value}),I.addEventListener("click",async()=>{I.textContent="Saving...",I.disabled=!0;try{const e=await fetch(y("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j,content:W})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Save failed";return}oe=W,b.textContent="Saved"}catch{b.textContent="Save failed"}finally{I.disabled=!1,I.textContent="Save"}}),Ie.addEventListener("click",async()=>{if(confirm("Delete "+j+"?"))try{const e=await fetch(y("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Delete failed";return}H.style.display="none",C.style.display="flex",E&&k(E)}catch{b.textContent="Delete failed"}}),Be.addEventListener("click",()=>{H.style.display="none",C.style.display="flex",E&&k(E)}),xe.addEventListener("click",async()=>{const e=ae.value.trim();if(!e)return;const n=E+"/"+e;try{const t=await fetch(y("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();b.textContent=s.error||"Create failed";return}ae.value="",E&&k(E)}catch{b.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=le.classList.toggle("open");Z.classList.toggle("open",e)}),Z.addEventListener("click",()=>{le.classList.remove("open"),Z.classList.remove("open")});let B="";const P=document.createElement("div");P.className="session-popover-backdrop",document.body.appendChild(P);const L=document.createElement("div");L.id="session-popover",L.className="session-popover",L.style.display="none",L.innerHTML=`<div class="sp-field">
29
29
  <label>Rename session</label>
30
30
  <input type="text" id="sp-name" />
31
31
  </div>
@@ -35,4 +35,4 @@ import{initTerminal as ke}from"./terminal-core.js";const M=window.__TMUX_WEB_SHE
35
35
  </div>
36
36
  <hr class="sp-divider" />
37
37
  <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(L);function ze(e){const n=e.getBoundingClientRect();L.style.top=n.bottom+4+"px",L.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function Oe(e,n){B=e;const t=document.getElementById("sp-name");t.value=e,P.classList.add("open"),L.style.display="block",ze(n),setTimeout(()=>t.focus(),50)}function A(){P.classList.remove("open"),L.style.display="none"}P.addEventListener("click",A),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===B){A();return}try{if(!(await fetch(y("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:B,newName:e})})).ok)return;A(),S(),u===B&&R(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+B+'"?'))try{if(!(await fetch(y("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B})})).ok)return;A(),u===B&&(p&&(p.destroy(),p=null),u=null,h.style.display="none",C.style.display="flex"),S()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",A);const Ue={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",e=>{const n=e.target.closest("button[data-key]");if(!n||!p)return;const t=Ue[n.dataset.key];t&&(p.sendInput(t),p.focus())});const w=document.getElementById("mk-input"),Le=document.getElementById("mk-send");function Ee(){if(!w||!p)return;const e=w.value;e&&(p.sendInput(e+"\r"),w.value="",w.rows=1,p.focus())}if(w&&(w.addEventListener("input",()=>{w.rows=1;const e=w.value.split(`
38
- `).length;w.rows=Math.min(e,4)}),w.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ee())})),Le&&Le.addEventListener("click",Ee),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 te=document.getElementById("process-panel"),_=document.getElementById("proc-list"),Ge=document.getElementById("proc-close"),ne=document.getElementById("proc-backdrop"),Ve=document.getElementById("header-ram");function qe(){_.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(y("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){_.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+'">&times;</button></div>'}_.innerHTML=n;for(const t of _.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const l=i.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch(y("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?l.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{_.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Je(){te.classList.add("open"),ne.classList.add("open"),qe()}function X(){te.classList.remove("open"),ne.classList.remove("open")}Ve.addEventListener("click",e=>{e.stopPropagation(),te.classList.contains("open")?X():Je()}),Ge.addEventListener("click",X),ne.addEventListener("click",X),window.__openSession=R,window.__refreshSidebar=S,S(),Z(),setInterval(()=>{document.visibilityState==="visible"&&S()},1e4);
38
+ `).length;w.rows=Math.min(e,4)}),w.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ee())})),Le&&Le.addEventListener("click",Ee),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 ne=document.getElementById("process-panel"),_=document.getElementById("proc-list"),Ge=document.getElementById("proc-close"),se=document.getElementById("proc-backdrop"),Ve=document.getElementById("header-ram");function qe(){_.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(y("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){_.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+'">&times;</button></div>'}_.innerHTML=n;for(const t of _.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const l=i.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch(y("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?l.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{_.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Je(){ne.classList.add("open"),se.classList.add("open"),qe()}function X(){ne.classList.remove("open"),se.classList.remove("open")}Ve.addEventListener("click",e=>{e.stopPropagation(),ne.classList.contains("open")?X():Je()}),Ge.addEventListener("click",X),se.addEventListener("click",X),window.__openSession=R,window.__refreshSidebar=S,S(),Q(),setInterval(()=>{document.visibilityState==="visible"&&S()},1e4);
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{chmodSync as he,existsSync as z,readdirSync as ge,statSync as ye}from"node:fs";import{createRequire as V}from"node:module";import{hostname as $}from"node:os";const ke=V(import.meta.url),{version:_e}=ke("../package.json");import I from"node:path";import{fileURLToPath as Te}from"node:url";import{serve as we}from"@hono/node-server";import{WebSocketServer as Se,WebSocket as D}from"ws";try{const e=I.dirname(V(import.meta.url).resolve("node-pty/package.json")),n=I.join(e,"prebuilds");if(z(n))for(const r of ge(n)){const t=I.join(n,r,"spawn-helper");z(t)&&!(ye(t).mode&73)&&he(t,493)}}catch{}import{loadSecurityConfig as xe}from"./lib/security-config.js";import{TokenStore as Ae,hashPassword as ve,verifyPassword as Ce,validatePassword as be}from"./lib/auth.js";import{RateLimiter as Re}from"./lib/rateLimiter.js";import{audit as l}from"./lib/auditLog.js";import{db as b}from"./lib/db.js";import{loadExtensions as Me,spawnExtensionBackend as Ie}from"./lib/ext-loader.js";import{SchedulerService as Ee}from"./lib/scheduler.js";import{handleClientMessage as X}from"./lib/ws-message.js";import{loadDotEnv as Oe}from"./lib/load-env.js";import{listSessions as Pe,resolveTmuxSocketPath as Le}from"./sessions.js";import{cmdAdd as $e,cmdRemove as Ue,cmdList as Ne,cmdSetup as He,cmdTheme as Fe,printUsage as K,printVersion as De}from"./lib/cli.js";import{readSettings as je}from"./lib/settings.js";import{readActiveTheme as Be}from"./lib/theme-store.js";import{buildApp as Q}from"./lib/build-app.js";import{attachTerminal as We,killAllAttachedPtys as Je}from"./lib/attach-terminal.js";import{acquireControlClient as Z,killAllControlClients as Ge}from"./lib/tmux-control.js";import{getSessionPaneTarget as q,capturePaneTail as ee,capturePaneHistoryChunk as te,toCrlf as ne}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Ye}from"./lib/terminal-config.js";import{AgentRegistry as ze}from"./lib/agent-registry.js";import{AgentChannel as Ve,MAX_RELAY_CONNS_PER_AGENT as Xe}from"./lib/agent-relay.js";import{findAgentTokenByPlaintext as Ke,listAgentTokens as Qe}from"./lib/agent-tokens.js";import{saveSecurityConfig as Ze}from"./lib/security-config.js";import{readFederationConfig as qe,writeFederationConfig as et}from"./lib/federation-config.js";import{startAgentClient as tt}from"./lib/agent-client.js";Oe();const U=Ye(),h=xe(),y=new Ae,S=new Re;let j=!1;const nt="tmux-web-token";function B(e){if(h.security.trustProxy){const n=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(n)return n}return e.socket.remoteAddress||"unknown"}function W(e){const n=e.headers.authorization;if(typeof n=="string"&&n.startsWith("Bearer "))return n.slice(7);const r=e.headers.cookie;if(typeof r!="string")return null;const t=r.match(new RegExp(`(?:^|;\\s*)${nt}=([^;]+)`));return t?decodeURIComponent(t[1]):null}function Kt(e){const n=W(e);return n?y.validateToken(n):null}const E=new Map;function ot(e){let n=0;for(const r of E.values())r.ip===e&&n++;return n}function A(e,n,r){try{e.close(n,r)}catch{}}function oe(e,n){e.readyState===D.OPEN&&e.send(JSON.stringify(n))}const N=process.argv.slice(2);{const e=N.filter(n=>n!=="--ghostty"&&n!=="--xterm");if(e.length>0){const[n,r]=e;switch(n){case"add":r||(console.error("usage: tmux-web add <package>"),process.exit(1)),await $e(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await Ue(r),process.exit(0);case"list":case"ls":await Ne(),process.exit(0);case"setup":await He(e),process.exit(0);case"theme":await Fe(e.slice(1)),process.exit(0);case"agent":{const t=I.join(I.dirname(Te(import.meta.url)),"agent.js"),{spawn:u}=await import("node:child_process"),s=u(process.execPath,[t,...N],{stdio:"inherit"});s.on("error",o=>{console.error(o),process.exit(1)}),s.on("exit",o=>process.exit(o??0)),await new Promise(()=>{})}case"agent-token":await kt(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":K(),process.exit(0);case"-V":case"--version":case"-v":De(),process.exit(0);default:console.error(`unknown argument: ${n}`),K(),process.exit(1)}}}function R(e,n){e.readyState===D.OPEN&&e.send(JSON.stringify(n))}const re=new Set,se=[],rt=7;function st(e){return typeof e!="number"||!Number.isFinite(e)?rt:Math.min(365,Math.max(1,Math.round(e)))}await b.read(),b.data.sessionAccess??=[],b.data.pinnedViews??=[],b.data.watchedPanes??=[],b.data.triggeredTasks??=[],b.data.quickCommands??=[];const O=await je();let at=await Be();const ae=O.commandbar===!0,J=st(O.scheduleHistoryDays),G=I.join(process.cwd(),"extensions"),Y=await Me(G);for(const e of Y)e.start&&se.push(Ie(e.dir,e));const H=new Ee({db:b,historyRetentionMs:J*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await H.restoreFromDb();const g=new ze({onChange:()=>{}}),T=new Map;function ie(e){return g.get(e)?T.get(e)??null:null}const ce={activeTheme:at,settingUpPassword:!1},it=Q({mode:"hub",securityConfig:h,tokenStore:y,rateLimiter:S,scheduler:H,settings:O,commandbarEnabled:ae,terminalRenderer:me(N,O.terminalRenderer),scheduleHistoryDays:J,extsDir:G,extensions:Y,terminalBufferConfig:U,state:ce,federation:{getConfig:()=>C,setConfig:async e=>{C=e,await et(e),ue(e)},status:lt,hostname:$()},hub:{agents:g,getChannel:ie}}),ct=Q({mode:"agent",securityConfig:h,tokenStore:y,rateLimiter:S,scheduler:H,settings:O,commandbarEnabled:ae,terminalRenderer:me(N,O.terminalRenderer),scheduleHistoryDays:J,extsDir:G,extensions:Y,terminalBufferConfig:U,state:ce});let C=await qe(),P=null;function le(){P&&(P.stop(),P=null)}function ue(e){le(),!(!e.enabled||!e.hub||!e.token)&&(P=tt({hub:e.hub,token:e.token,name:e.name?.trim()||$(),version:_e,app:ct,listSessions:Pe,terminalBufferConfig:U,getSessionPaneTarget:q,capturePaneTail:ee,capturePaneHistoryChunk:te,toCrlf:ne,handleClientMessage:X,acquireControlClient:Z}))}function lt(){return C.enabled?P?P.status():{state:"idle",agentId:null,hub:C.hub??"",name:C.name??$()}:{state:"idle",agentId:null,hub:C.hub??"",name:C.name??$()}}ue(C);const ut=parseInt(process.env.PORT||"21000",10),dt=we({fetch:it.fetch,port:ut,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),L=new Se({noServer:!0});function M(e,n,r){e.write(`HTTP/1.1 ${n} ${r}\r
2
+ import{chmodSync as pe,existsSync as z,readdirSync as he,statSync as ge}from"node:fs";import{createRequire as V}from"node:module";import{hostname as $}from"node:os";const ye=V(import.meta.url),{version:ke}=ye("../package.json");import I from"node:path";import{fileURLToPath as _e}from"node:url";import{serve as Te}from"@hono/node-server";import{WebSocketServer as we,WebSocket as D}from"ws";try{const e=I.dirname(V(import.meta.url).resolve("node-pty/package.json")),n=I.join(e,"prebuilds");if(z(n))for(const r of he(n)){const t=I.join(n,r,"spawn-helper");z(t)&&!(ge(t).mode&73)&&pe(t,493)}}catch{}import{loadSecurityConfig as Se}from"./lib/security-config.js";import{TokenStore as xe,hashPassword as Ae,verifyPassword as ve,validatePassword as Ce}from"./lib/auth.js";import{RateLimiter as be}from"./lib/rateLimiter.js";import{audit as l}from"./lib/auditLog.js";import{db as b}from"./lib/db.js";import{loadExtensions as Re,spawnExtensionBackend as Me}from"./lib/ext-loader.js";import{SchedulerService as Ie}from"./lib/scheduler.js";import{handleClientMessage as X}from"./lib/ws-message.js";import{loadDotEnv as Ee}from"./lib/load-env.js";import{listSessions as Oe,resolveTmuxSocketPath as Pe}from"./sessions.js";import{cmdAdd as Le,cmdRemove as $e,cmdList as Ue,cmdSetup as Ne,cmdTheme as He,printUsage as K,printVersion as Fe}from"./lib/cli.js";import{readSettings as De}from"./lib/settings.js";import{readActiveTheme as je}from"./lib/theme-store.js";import{buildApp as Q}from"./lib/build-app.js";import{attachTerminal as Be,killAllAttachedPtys as We}from"./lib/attach-terminal.js";import{acquireControlClient as Z,killAllControlClients as Je}from"./lib/tmux-control.js";import{getSessionPaneTarget as q,capturePaneTail as ee,capturePaneHistoryChunk as te,toCrlf as ne}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Ge}from"./lib/terminal-config.js";import{AgentRegistry as Ye}from"./lib/agent-registry.js";import{AgentChannel as ze,MAX_RELAY_CONNS_PER_AGENT as Ve}from"./lib/agent-relay.js";import{findAgentTokenByPlaintext as Xe,listAgentTokens as Ke}from"./lib/agent-tokens.js";import{saveSecurityConfig as Qe}from"./lib/security-config.js";import{readFederationConfig as Ze,writeFederationConfig as qe}from"./lib/federation-config.js";import{startAgentClient as et}from"./lib/agent-client.js";Ee();const U=Ge(),h=Se(),y=new xe,S=new be;let j=!1;const tt="tmux-web-token";function B(e){if(h.security.trustProxy){const n=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(n)return n}return e.socket.remoteAddress||"unknown"}function W(e){const n=e.headers.authorization;if(typeof n=="string"&&n.startsWith("Bearer "))return n.slice(7);const r=e.headers.cookie;if(typeof r!="string")return null;const t=r.match(new RegExp(`(?:^|;\\s*)${tt}=([^;]+)`));return t?decodeURIComponent(t[1]):null}function Xt(e){const n=W(e);return n?y.validateToken(n):null}const E=new Map;function nt(e){let n=0;for(const r of E.values())r.ip===e&&n++;return n}function A(e,n,r){try{e.close(n,r)}catch{}}function oe(e,n){e.readyState===D.OPEN&&e.send(JSON.stringify(n))}const N=process.argv.slice(2);{const e=N.filter(n=>n!=="--ghostty"&&n!=="--xterm");if(e.length>0){const[n,r]=e;switch(n){case"add":r||(console.error("usage: tmux-web add <package>"),process.exit(1)),await Le(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await $e(r),process.exit(0);case"list":case"ls":await Ue(),process.exit(0);case"setup":await Ne(e),process.exit(0);case"theme":await He(e.slice(1)),process.exit(0);case"agent":{const t=I.join(I.dirname(_e(import.meta.url)),"agent.js"),{spawn:u}=await import("node:child_process"),s=u(process.execPath,[t,...N],{stdio:"inherit"});s.on("error",o=>{console.error(o),process.exit(1)}),s.on("exit",o=>process.exit(o??0)),await new Promise(()=>{})}case"agent-token":await yt(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":K(),process.exit(0);case"-V":case"--version":case"-v":Fe(),process.exit(0);default:console.error(`unknown argument: ${n}`),K(),process.exit(1)}}}function R(e,n){e.readyState===D.OPEN&&e.send(JSON.stringify(n))}const re=new Set,se=[],ot=7;function rt(e){return typeof e!="number"||!Number.isFinite(e)?ot:Math.min(365,Math.max(1,Math.round(e)))}await b.read(),b.data.sessionAccess??=[],b.data.pinnedViews??=[],b.data.watchedPanes??=[],b.data.triggeredTasks??=[],b.data.quickCommands??=[];const P=await De();let st=await je();const J=rt(P.scheduleHistoryDays),G=I.join(process.cwd(),"extensions"),Y=await Re(G);for(const e of Y)e.start&&se.push(Me(e.dir,e));const H=new Ie({db:b,historyRetentionMs:J*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await H.restoreFromDb();const g=new Ye({onChange:()=>{}}),T=new Map;function ae(e){return g.get(e)?T.get(e)??null:null}const ie={activeTheme:st,settingUpPassword:!1},at=Q({mode:"hub",securityConfig:h,tokenStore:y,rateLimiter:S,scheduler:H,settings:P,terminalRenderer:de(N,P.terminalRenderer),scheduleHistoryDays:J,extsDir:G,extensions:Y,terminalBufferConfig:U,state:ie,federation:{getConfig:()=>C,setConfig:async e=>{C=e,await qe(e),le(e)},status:ct,hostname:$()},hub:{agents:g,getChannel:ae}}),it=Q({mode:"agent",securityConfig:h,tokenStore:y,rateLimiter:S,scheduler:H,settings:P,terminalRenderer:de(N,P.terminalRenderer),scheduleHistoryDays:J,extsDir:G,extensions:Y,terminalBufferConfig:U,state:ie});let C=await Ze(),O=null;function ce(){O&&(O.stop(),O=null)}function le(e){ce(),!(!e.enabled||!e.hub||!e.token)&&(O=et({hub:e.hub,token:e.token,name:e.name?.trim()||$(),version:ke,app:it,listSessions:Oe,terminalBufferConfig:U,getSessionPaneTarget:q,capturePaneTail:ee,capturePaneHistoryChunk:te,toCrlf:ne,handleClientMessage:X,acquireControlClient:Z}))}function ct(){return C.enabled?O?O.status():{state:"idle",agentId:null,hub:C.hub??"",name:C.name??$()}:{state:"idle",agentId:null,hub:C.hub??"",name:C.name??$()}}le(C);const lt=parseInt(process.env.PORT||"21000",10),ut=Te({fetch:at.fetch,port:lt,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),L=new we({noServer:!0});function M(e,n,r){e.write(`HTTP/1.1 ${n} ${r}\r
3
3
  \r
4
- `),e.destroy()}dt.on("upgrade",(e,n,r)=>{const t=new URL(e.url||"/",`http://${e.headers.host}`),u=t.pathname;if(u==="/agent/ws"){ht(e,n,r);return}const s=u.match(/^\/ws\/a\/([^/]+)\/(.+)$/);if(s){mt(e,n,r,decodeURIComponent(s[1]),decodeURIComponent(s[2]));return}const o=u.match(/^\/ws\/(.+)$/);if(!o){n.destroy();return}const f=B(e),i=e.headers.origin,d=h.security.allowedOrigins;if(i&&!(i===`http://${e.headers.host}`||i===`https://${e.headers.host}`)&&(d.length===0||!d.includes(i))){l("ws_rejected_origin",{ip:f,origin:i}),M(n,403,"Origin not allowed");return}const k=ot(f);if(k>=h.security.maxConnectionsPerIp){l("ws_rejected_per_ip_cap",{ip:f,liveFromIp:k}),M(n,429,"Too many connections");return}const p=W(e)||t.searchParams.get("token")||"",w=p?y.validateToken(p):null;L.handleUpgrade(e,n,r,a=>{L.emit("connection",a,e,decodeURIComponent(o[1]),f,w)})}),L.on("connection",(e,n,r,t,u)=>{const s={ws:e,ip:t,authenticated:!1,authTimeout:null};E.set(e,s),l("ws_connected",{ip:t});let o=null,f=null;function i(a,c){s.authenticated=!0,s.authTimeout&&(clearTimeout(s.authTimeout),s.authTimeout=null),l("auth_success",{ip:t,method:a,tokenName:c}),R(e,{type:"auth.ok",setupMode:!h.passwordHash}),k()}function d(a,c={}){R(e,{type:"auth.failed",message:a,...c})}function k(){o||(o=We(r,{send:a=>R(e,a),onMessage:a=>{f=a},onClose:a=>{p=a}},{terminalBufferConfig:U,getSessionPaneTarget:q,capturePaneTail:ee,capturePaneHistoryChunk:te,toCrlf:ne,handleClientMessage:X,acquireControlClient:Z,tmuxSocketPath:Le()??void 0,onPtyExit:(a,c)=>A(e,1e3,"pty exited"),onSpawnError:(a,c)=>A(e,1011,"pty spawn failed")}))}let p=null;async function w(a){let c;try{c=JSON.parse(a)}catch{return!1}if(c.type==="auth.token"&&typeof c.token=="string"){const m=y.validateToken(c.token);if(!m){l("token_auth_failed",{ip:t});const v=S.recordFailure(t);return v.permanentLock&&(y.revokeAll(),l("permanent_lock",{ip:t,failures:v.failures})),d("Invalid or expired token",{permanentLock:v.permanentLock,retryAfterMs:v.retryAfterMs}),!0}return y.touch(m.tokenHash),i("token",m.name),!0}if(c.type==="auth"&&typeof c.password=="string"){const m=S.check(t);if(!m.allowed)return l("rate_limited",{ip:t,retryAfterMs:m.retryAfterMs,permanentLock:m.permanentLock}),d("Too many attempts",{permanentLock:m.permanentLock,retryAfterMs:m.retryAfterMs}),!0;if(!h.passwordHash){if(!h.security.allowRemoteSetup&&!yt(t))return l("setup_rejected_remote",{ip:t}),d("First-run setup must be performed from localhost"),!0;if(j)return d("Password setup in progress"),!0;const x=be(c.password);if(x)return d(x),!0;j=!0;try{h.passwordHash=await ve(c.password),Ze(h),l("password_set",{ip:t})}finally{j=!1}const{stored:F,plaintext:pe}=y.createAccessToken("setup",h.security.tokenTtlDays);return R(e,{type:"auth.ok",setupMode:!0,token:pe}),l("token_created",{ip:t,name:F.name,tokenId:F.id}),l("auth_success",{ip:t,method:"password",tokenName:F.name}),k(),!0}if(!h.passwordHash)return d("Server not configured"),!0;if(!await Ce(c.password,h.passwordHash)){const x=S.recordFailure(t);return l("auth_failed",{ip:t,method:"password",failures:x.failures,permanentLock:x.permanentLock}),x.permanentLock&&(y.revokeAll(),l("permanent_lock",{ip:t,failures:x.failures})),d("Incorrect password",{permanentLock:x.permanentLock,retryAfterMs:x.retryAfterMs}),!0}S.recordSuccess(t);const _=`ws-${t}`,{plaintext:fe}=y.createAccessToken(_,h.security.tokenTtlDays);return i("password",_),R(e,{type:"auth.ok",setupMode:!1,token:fe}),!0}return!1}u?(y.touch(u.tokenHash),i("token",u.name)):(R(e,{type:"auth.required",setupMode:!h.passwordHash}),s.authTimeout=setTimeout(()=>{s.authenticated||(l("auth_timeout",{ip:t}),d("Authentication timeout"),A(e,4e3,"Auth timeout"))},h.security.authTimeoutMs)),e.on("message",async a=>{const c=typeof a=="string"?a:a.toString("utf-8");if(!s.authenticated){if(c.length>1e6){R(e,{type:"auth.failed",message:"Message too large"});return}await w(c)||d("Authentication required");return}if(f)try{f(c)}catch{}}),e.on("close",()=>{s.authTimeout&&clearTimeout(s.authTimeout),E.delete(e),l("ws_disconnected",{ip:t}),p?.(),o&&(o.dispose(),o=null)}),e.on("error",()=>{s.authTimeout&&clearTimeout(s.authTimeout),E.delete(e),p?.(),o&&(o.dispose(),o=null)})});function mt(e,n,r,t,u){const s=B(e),o=new URL(e.url||"/",`http://${e.headers.host}`),f=W(e)||o.searchParams.get("token")||"";if(!(f?y.validateToken(f):null)){l("ws_rejected_remote_auth",{ip:s,agentId:t}),M(n,401,"Unauthorized");return}const d=ie(t);if(!d){l("ws_rejected_remote_offline",{ip:s,agentId:t}),M(n,503,"Agent offline");return}if(d.relayCount>=Xe){M(n,429,"Too many connections to agent");return}const k=e.headers.origin,p=h.security.allowedOrigins;if(k&&!(k===`http://${e.headers.host}`||k===`https://${e.headers.host}`)&&(p.length===0||!p.includes(k))){M(n,403,"Origin not allowed");return}const w=pt();L.handleUpgrade(e,n,r,a=>{if(d.registerRelay(w,u,a),g.bumpRelayConns(t,1),!d.attach(w,u)){a.send(JSON.stringify({type:"data",data:`\r
4
+ `),e.destroy()}ut.on("upgrade",(e,n,r)=>{const t=new URL(e.url||"/",`http://${e.headers.host}`),u=t.pathname;if(u==="/agent/ws"){pt(e,n,r);return}const s=u.match(/^\/ws\/a\/([^/]+)\/(.+)$/);if(s){dt(e,n,r,decodeURIComponent(s[1]),decodeURIComponent(s[2]));return}const o=u.match(/^\/ws\/(.+)$/);if(!o){n.destroy();return}const f=B(e),i=e.headers.origin,d=h.security.allowedOrigins;if(i&&!(i===`http://${e.headers.host}`||i===`https://${e.headers.host}`)&&(d.length===0||!d.includes(i))){l("ws_rejected_origin",{ip:f,origin:i}),M(n,403,"Origin not allowed");return}const k=nt(f);if(k>=h.security.maxConnectionsPerIp){l("ws_rejected_per_ip_cap",{ip:f,liveFromIp:k}),M(n,429,"Too many connections");return}const p=W(e)||t.searchParams.get("token")||"",w=p?y.validateToken(p):null;L.handleUpgrade(e,n,r,a=>{L.emit("connection",a,e,decodeURIComponent(o[1]),f,w)})}),L.on("connection",(e,n,r,t,u)=>{const s={ws:e,ip:t,authenticated:!1,authTimeout:null};E.set(e,s),l("ws_connected",{ip:t});let o=null,f=null;function i(a,c){s.authenticated=!0,s.authTimeout&&(clearTimeout(s.authTimeout),s.authTimeout=null),l("auth_success",{ip:t,method:a,tokenName:c}),R(e,{type:"auth.ok",setupMode:!h.passwordHash}),k()}function d(a,c={}){R(e,{type:"auth.failed",message:a,...c})}function k(){o||(o=Be(r,{send:a=>R(e,a),onMessage:a=>{f=a},onClose:a=>{p=a}},{terminalBufferConfig:U,getSessionPaneTarget:q,capturePaneTail:ee,capturePaneHistoryChunk:te,toCrlf:ne,handleClientMessage:X,acquireControlClient:Z,tmuxSocketPath:Pe()??void 0,onPtyExit:(a,c)=>A(e,1e3,"pty exited"),onSpawnError:(a,c)=>A(e,1011,"pty spawn failed")}))}let p=null;async function w(a){let c;try{c=JSON.parse(a)}catch{return!1}if(c.type==="auth.token"&&typeof c.token=="string"){const m=y.validateToken(c.token);if(!m){l("token_auth_failed",{ip:t});const v=S.recordFailure(t);return v.permanentLock&&(y.revokeAll(),l("permanent_lock",{ip:t,failures:v.failures})),d("Invalid or expired token",{permanentLock:v.permanentLock,retryAfterMs:v.retryAfterMs}),!0}return y.touch(m.tokenHash),i("token",m.name),!0}if(c.type==="auth"&&typeof c.password=="string"){const m=S.check(t);if(!m.allowed)return l("rate_limited",{ip:t,retryAfterMs:m.retryAfterMs,permanentLock:m.permanentLock}),d("Too many attempts",{permanentLock:m.permanentLock,retryAfterMs:m.retryAfterMs}),!0;if(!h.passwordHash){if(!h.security.allowRemoteSetup&&!gt(t))return l("setup_rejected_remote",{ip:t}),d("First-run setup must be performed from localhost"),!0;if(j)return d("Password setup in progress"),!0;const x=Ce(c.password);if(x)return d(x),!0;j=!0;try{h.passwordHash=await Ae(c.password),Qe(h),l("password_set",{ip:t})}finally{j=!1}const{stored:F,plaintext:fe}=y.createAccessToken("setup",h.security.tokenTtlDays);return R(e,{type:"auth.ok",setupMode:!0,token:fe}),l("token_created",{ip:t,name:F.name,tokenId:F.id}),l("auth_success",{ip:t,method:"password",tokenName:F.name}),k(),!0}if(!h.passwordHash)return d("Server not configured"),!0;if(!await ve(c.password,h.passwordHash)){const x=S.recordFailure(t);return l("auth_failed",{ip:t,method:"password",failures:x.failures,permanentLock:x.permanentLock}),x.permanentLock&&(y.revokeAll(),l("permanent_lock",{ip:t,failures:x.failures})),d("Incorrect password",{permanentLock:x.permanentLock,retryAfterMs:x.retryAfterMs}),!0}S.recordSuccess(t);const _=`ws-${t}`,{plaintext:me}=y.createAccessToken(_,h.security.tokenTtlDays);return i("password",_),R(e,{type:"auth.ok",setupMode:!1,token:me}),!0}return!1}u?(y.touch(u.tokenHash),i("token",u.name)):(R(e,{type:"auth.required",setupMode:!h.passwordHash}),s.authTimeout=setTimeout(()=>{s.authenticated||(l("auth_timeout",{ip:t}),d("Authentication timeout"),A(e,4e3,"Auth timeout"))},h.security.authTimeoutMs)),e.on("message",async a=>{const c=typeof a=="string"?a:a.toString("utf-8");if(!s.authenticated){if(c.length>1e6){R(e,{type:"auth.failed",message:"Message too large"});return}await w(c)||d("Authentication required");return}if(f)try{f(c)}catch{}}),e.on("close",()=>{s.authTimeout&&clearTimeout(s.authTimeout),E.delete(e),l("ws_disconnected",{ip:t}),p?.(),o&&(o.dispose(),o=null)}),e.on("error",()=>{s.authTimeout&&clearTimeout(s.authTimeout),E.delete(e),p?.(),o&&(o.dispose(),o=null)})});function dt(e,n,r,t,u){const s=B(e),o=new URL(e.url||"/",`http://${e.headers.host}`),f=W(e)||o.searchParams.get("token")||"";if(!(f?y.validateToken(f):null)){l("ws_rejected_remote_auth",{ip:s,agentId:t}),M(n,401,"Unauthorized");return}const d=ae(t);if(!d){l("ws_rejected_remote_offline",{ip:s,agentId:t}),M(n,503,"Agent offline");return}if(d.relayCount>=Ve){M(n,429,"Too many connections to agent");return}const k=e.headers.origin,p=h.security.allowedOrigins;if(k&&!(k===`http://${e.headers.host}`||k===`https://${e.headers.host}`)&&(p.length===0||!p.includes(k))){M(n,403,"Origin not allowed");return}const w=ft();L.handleUpgrade(e,n,r,a=>{if(d.registerRelay(w,u,a),g.bumpRelayConns(t,1),!d.attach(w,u)){a.send(JSON.stringify({type:"data",data:`\r
5
5
  \x1B[31magent offline\x1B[0m\r
6
- `})),a.close(1011,"agent offline"),d.relayClosed(w),g.bumpRelayConns(t,-1);return}a.on("message",m=>{const v=typeof m=="string"?m:m.toString("utf-8");let _;try{_=JSON.parse(v)}catch{return}(_.type==="input"||_.type==="resize"||_.type==="load_history")&&d.sendToAgent(w,_)});const c=()=>{d.relayClosed(w),g.bumpRelayConns(t,-1)};a.on("close",c),a.on("error",c)})}let ft=1;function pt(){return ft++}function ht(e,n,r){const t=B(e),u=e.headers.origin;if(u){l("agent_rejected_origin",{ip:t,origin:u}),M(n,403,"Origin not allowed");return}L.handleUpgrade(e,n,r,s=>{let o=null,f=setTimeout(()=>{l("agent_hello_timeout",{ip:t}),A(s,4e3,"hello timeout")},1e4);s.on("message",(i,d)=>{if(o){g.touch(o.agentId),d?o.handleData(Array.isArray(i)?Buffer.concat(i):Buffer.from(i)):o.handleMessage(typeof i=="string"?i:Buffer.from(i).toString("utf-8"));return}const k=typeof i=="string"?i:i.toString("utf-8");let p;try{p=JSON.parse(k)}catch{A(s,1008,"bad hello");return}if(p.type!=="hello"||typeof p.token!="string"){A(s,1008,"expected hello");return}if(!S.check(t).allowed){l("agent_rate_limited",{ip:t}),A(s,429,"Too many attempts");return}const a=Ke(p.token);if(!a){S.recordFailure(t),l("agent_bad_token",{ip:t}),oe(s,{type:"hello_err",reason:"invalid token"}),A(s,4003,"invalid token");return}const c=typeof p.name=="string"&&p.name.trim()?p.name.trim().slice(0,64):"agent",m=a.id;if(!g.register(m,c,s,a.tokenHash)){l("agent_cap_reached",{ip:t}),oe(s,{type:"hello_err",reason:"too many agents"}),A(s,4004,"too many agents");return}clearTimeout(f),S.recordSuccess(t),l("agent_connected",{ip:t,agentId:m,name:c}),o=new Ve(s,m,{onSessions:_=>g.setSessions(m,_),onClose:_=>{T.get(m)===_&&T.delete(m)}}),T.set(m,o),g.touch(m),s.send(JSON.stringify({type:"hello_ok",agentId:m})),o.sendSessionsReq()}),s.on("close",()=>{o&&(o.dispose(),T.delete(o.agentId),g.unregister(o.agentId),l("agent_disconnected",{ip:t,agentId:o.agentId})),clearTimeout(f)}),s.on("error",()=>{o&&(o.dispose(),T.delete(o.agentId),g.unregister(o.agentId)),clearTimeout(f)})})}const gt=parseInt(process.env.TMUX_WEB_AGENT_OFFLINE_MS||"45000",10);setInterval(()=>{const e=g.pruneOffline(gt);for(const r of e){const t=g.peek(r);t&&t.ws.readyState===D.CLOSED&&(g.unregister(r),T.get(r)?.dispose(),T.delete(r))}const n=new Set(Qe().map(r=>r.tokenHash));for(const[r,t]of T){const u=g.peek(r);if(!(!u||n.has(u.tokenHash))){l("agent_token_revoked",{agentId:r}),t.dispose(),T.delete(r),g.unregister(r);try{u.ws.close(4003,"token revoked")}catch{}}}y.purgeExpired()},10*1e3).unref();function de(){H.cleanup(),le(),Ge(),Je();for(const e of T.values())e.dispose();T.clear();for(const[e,n]of E){try{e.close(1001,"Server shutting down")}catch{}n.authTimeout&&clearTimeout(n.authTimeout)}E.clear();for(const e of re)try{e.kill()}catch{}re.clear();for(const e of se)try{e.kill("SIGTERM")}catch{}S.dispose(),process.exit(0)}process.on("SIGINT",de),process.on("SIGTERM",de);function yt(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function me(e,n){let r="xterm";(n==="ghostty"||n==="xterm")&&(r=n);const t=process.env.TMUX_WEB_TERMINAL_RENDERER?.trim().toLowerCase();(t==="ghostty"||t==="xterm")&&(r=t);for(const u of e)u==="--ghostty"&&(r="ghostty"),u==="--xterm"&&(r="xterm");return r}async function kt(e){const{createAgentToken:n,listAgentTokens:r,removeAgentToken:t}=await import("./lib/agent-tokens.js"),[u,...s]=e;switch(u){case"add":{const o=s.find(d=>!d.startsWith("-"))||"agent",{id:f,token:i}=n(o);console.log("\u2713 agent token created"),console.log(` id: ${f}`),console.log(` name: ${o}`),console.log(` token: ${i}`),console.log(`
6
+ `})),a.close(1011,"agent offline"),d.relayClosed(w),g.bumpRelayConns(t,-1);return}a.on("message",m=>{const v=typeof m=="string"?m:m.toString("utf-8");let _;try{_=JSON.parse(v)}catch{return}(_.type==="input"||_.type==="resize"||_.type==="load_history")&&d.sendToAgent(w,_)});const c=()=>{d.relayClosed(w),g.bumpRelayConns(t,-1)};a.on("close",c),a.on("error",c)})}let mt=1;function ft(){return mt++}function pt(e,n,r){const t=B(e),u=e.headers.origin;if(u){l("agent_rejected_origin",{ip:t,origin:u}),M(n,403,"Origin not allowed");return}L.handleUpgrade(e,n,r,s=>{let o=null,f=setTimeout(()=>{l("agent_hello_timeout",{ip:t}),A(s,4e3,"hello timeout")},1e4);s.on("message",(i,d)=>{if(o){g.touch(o.agentId),d?o.handleData(Array.isArray(i)?Buffer.concat(i):Buffer.from(i)):o.handleMessage(typeof i=="string"?i:Buffer.from(i).toString("utf-8"));return}const k=typeof i=="string"?i:i.toString("utf-8");let p;try{p=JSON.parse(k)}catch{A(s,1008,"bad hello");return}if(p.type!=="hello"||typeof p.token!="string"){A(s,1008,"expected hello");return}if(!S.check(t).allowed){l("agent_rate_limited",{ip:t}),A(s,429,"Too many attempts");return}const a=Xe(p.token);if(!a){S.recordFailure(t),l("agent_bad_token",{ip:t}),oe(s,{type:"hello_err",reason:"invalid token"}),A(s,4003,"invalid token");return}const c=typeof p.name=="string"&&p.name.trim()?p.name.trim().slice(0,64):"agent",m=a.id;if(!g.register(m,c,s,a.tokenHash)){l("agent_cap_reached",{ip:t}),oe(s,{type:"hello_err",reason:"too many agents"}),A(s,4004,"too many agents");return}clearTimeout(f),S.recordSuccess(t),l("agent_connected",{ip:t,agentId:m,name:c}),o=new ze(s,m,{onSessions:_=>g.setSessions(m,_),onClose:_=>{T.get(m)===_&&T.delete(m)}}),T.set(m,o),g.touch(m),s.send(JSON.stringify({type:"hello_ok",agentId:m})),o.sendSessionsReq()}),s.on("close",()=>{o&&(o.dispose(),T.delete(o.agentId),g.unregister(o.agentId),l("agent_disconnected",{ip:t,agentId:o.agentId})),clearTimeout(f)}),s.on("error",()=>{o&&(o.dispose(),T.delete(o.agentId),g.unregister(o.agentId)),clearTimeout(f)})})}const ht=parseInt(process.env.TMUX_WEB_AGENT_OFFLINE_MS||"45000",10);setInterval(()=>{const e=g.pruneOffline(ht);for(const r of e){const t=g.peek(r);t&&t.ws.readyState===D.CLOSED&&(g.unregister(r),T.get(r)?.dispose(),T.delete(r))}const n=new Set(Ke().map(r=>r.tokenHash));for(const[r,t]of T){const u=g.peek(r);if(!(!u||n.has(u.tokenHash))){l("agent_token_revoked",{agentId:r}),t.dispose(),T.delete(r),g.unregister(r);try{u.ws.close(4003,"token revoked")}catch{}}}y.purgeExpired()},10*1e3).unref();function ue(){H.cleanup(),ce(),Je(),We();for(const e of T.values())e.dispose();T.clear();for(const[e,n]of E){try{e.close(1001,"Server shutting down")}catch{}n.authTimeout&&clearTimeout(n.authTimeout)}E.clear();for(const e of re)try{e.kill()}catch{}re.clear();for(const e of se)try{e.kill("SIGTERM")}catch{}S.dispose(),process.exit(0)}process.on("SIGINT",ue),process.on("SIGTERM",ue);function gt(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function de(e,n){let r="xterm";(n==="ghostty"||n==="xterm")&&(r=n);const t=process.env.TMUX_WEB_TERMINAL_RENDERER?.trim().toLowerCase();(t==="ghostty"||t==="xterm")&&(r=t);for(const u of e)u==="--ghostty"&&(r="ghostty"),u==="--xterm"&&(r="xterm");return r}async function yt(e){const{createAgentToken:n,listAgentTokens:r,removeAgentToken:t}=await import("./lib/agent-tokens.js"),[u,...s]=e;switch(u){case"add":{const o=s.find(d=>!d.startsWith("-"))||"agent",{id:f,token:i}=n(o);console.log("\u2713 agent token created"),console.log(` id: ${f}`),console.log(` name: ${o}`),console.log(` token: ${i}`),console.log(`
7
7
  Run on the remote machine:`),console.log(` tmux-web agent --hub wss://YOUR_HUB --token ${i} --name "${o}"`);return}case"list":{const o=r();if(!o.length){console.log("No agent tokens. Create one with: tmux-web agent-token add --name <name>");return}console.log("Agent tokens:");for(const f of o)console.log(` ${f.id} ${f.name} (created ${new Date(f.createdAt).toISOString()})`);return}case"remove":{const o=s.find(i=>!i.startsWith("-"));o||(console.error("usage: tmux-web agent-token remove <id>"),process.exit(1)),t(o)||(console.error(`no token with id ${o}`),process.exit(1)),console.log(`\u2713 token ${o} removed`);return}default:console.error("usage: tmux-web agent-token <add|list|remove>"),process.exit(1)}}