tmux-weblink 1.5.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,8 +37,17 @@ TMUX_WEB_TERMINAL_RENDERER=ghostty tmux-weblink
37
37
 
38
38
  # Optional: tail-first buffer loading (see docs/architecture.md)
39
39
  TMUX_WEB_INITIAL_LINES=1000 TMUX_WEB_HISTORY_CHUNK=500 tmux-weblink
40
+
41
+ # Federation: join this machine to a hub (no ports opened)
42
+ tmux-weblink agent --hub wss://hub.example.com --token <agent-token> --name laptop
40
43
  ```
41
44
 
45
+ > **Every machine uses the same start command.** To join a hub, open
46
+ > `http://localhost:21000/settings/federation` on the machine you want to
47
+ > attach, paste the hub URL + token, and press **Save & Connect** — the agent
48
+ > client runs in-process next to the normal server. The hub's own
49
+ > `/settings/federation` page creates/revokes tokens.
50
+
42
51
  Then open `http://localhost:21000` in your browser. You'll see a list of active tmux sessions — click one to attach.
43
52
 
44
53
  ## Documentation
@@ -48,6 +57,7 @@ Then open `http://localhost:21000` in your browser. You'll see a list of active
48
57
  - [Scheduler](docs/scheduler.md) — delayed `send-keys` tasks
49
58
  - **Windows drawer** — switch tmux windows from the terminal header (mobile-friendly tab picker)
50
59
  - [Extensions](docs/extensions.md) — install, configure, and build sidebar plugins
60
+ - [Federation / Agents](docs/agents.md) — join NAT'd machines to a public hub
51
61
  - [Architecture](docs/architecture.md) — how the server, terminal, and extensions connect
52
62
 
53
63
  ## Prerequisites
package/dist/agent.js ADDED
@@ -0,0 +1,2 @@
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}from"./sessions.js";import{loadSecurityConfig as g}from"./lib/security-config.js";import{TokenStore as _}from"./lib/auth.js";import{RateLimiter as w}from"./lib/rateLimiter.js";import{db as n}from"./lib/db.js";import{loadExtensions as b,spawnExtensionBackend as k}from"./lib/ext-loader.js";import{SchedulerService as A}from"./lib/scheduler.js";import{handleClientMessage as M}from"./lib/ws-message.js";import{loadDotEnv as N}from"./lib/load-env.js";import{readSettings as y}from"./lib/settings.js";import{readActiveTheme as S}from"./lib/theme-store.js";import{buildApp as x}from"./lib/build-app.js";import{acquireControlClient as B}from"./lib/tmux-control.js";import{getSessionPaneTarget as C,capturePaneTail as U,capturePaneHistoryChunk as W,toCrlf as G}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as D}from"./lib/terminal-config.js";import{startAgentClient as H}from"./lib/agent-client.js";N();const O=l(import.meta.url),{version:R}=O("../package.json");function P(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:X,token:q,name:I}=P(process.argv.slice(2)),c=D(),K=g(),j=new _,F=new w;await n.read(),n.data.sessionAccess??=[],n.data.pinnedViews??=[],n.data.watchedPanes??=[],n.data.triggeredTasks??=[],n.data.quickCommands??=[];const i=await y(),L=await S(),$=i.commandbar===!0,p=z(i.scheduleHistoryDays),f=T.join(process.cwd(),"extensions"),d=await b(f);for(const e of d)e.start&&k(e.dir,e);const m=new A({db:n,historyRetentionMs:p*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await m.restoreFromDb();const v=x({mode:"agent",securityConfig:K,tokenStore:j,rateLimiter:F,scheduler:m,settings:i,commandbarEnabled:$,terminalRenderer:i.terminalRenderer==="ghostty"?"ghostty":"xterm",scheduleHistoryDays:p,extsDir:f,extensions:d,terminalBufferConfig:c,getAgentId:()=>null,state:{activeTheme:L,settingUpPassword:!1}}),V=H({hub:X,token:q,name:I,version:R,app:v,listSessions:E,terminalBufferConfig:c,getSessionPaneTarget:C,capturePaneTail:U,capturePaneHistoryChunk:W,toCrlf:G,handleClientMessage:M,acquireControlClient:B});function u(){m.cleanup(),V.stop(),process.exit(0)}process.on("SIGINT",u),process.on("SIGTERM",u);function z(e){return typeof e!="number"||!Number.isFinite(e)?7:Math.min(365,Math.max(1,Math.round(e)))}
@@ -1,35 +1,36 @@
1
- var At="https://esm.sh/ghostty-web@0.4.0",Yt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function oe(t){return t.replace(Yt,"")}var le;function Pt(){return le||(document.querySelector("link[data-tmux-web-xterm-css]")?le=Promise.resolve():(le=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),le))}var Ke=class ut{terminal;fitAddon;container;constructor(e,n,i){this.container=e,this.terminal=n,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,i){let[{Terminal:o},{FitAddon:l}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Pt()]),d=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:i});return new ut(e,d,new l)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(n=>this.terminal.write(e,n))}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||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,n){let i=this.terminal.buffer.active,[o,l]=e<=n?[e,n]:[n,e],d=i.viewportY,r=[];for(let u=o;u<=l;u++){let f=i.getLine(d+u);r.push(f?f.translateToString(!0):"")}return r.join(`
2
- `)}selectRows(e,n){let i=this.terminal.buffer.active,[o,l]=e<=n?[e,n]:[n,e];this.terminal.selectLines(i.viewportY+o,i.viewportY+l)}hasSelectionSupport(){return!0}},zt=class pt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,n){this.container=e,this.terminal=n,this.terminal.open(e)}static async create(e,n,i){let o=await import(At);await o.init();let l=new o.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:i});return new pt(e,l)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(n=>this.terminal.write(e,n))}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 n=Math.floor(e.width/this.charW),i=Math.floor(e.height/this.charH);if(n<10||i<5)return!1;if(n!==this.colsValue||i!==this.rowsValue){this.colsValue=n,this.rowsValue=i,this.terminal.resize(n,i);for(let o of this.resizeCallbacks)o({cols:n,rows:i})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(n=>e(n))}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,n){try{let i=this.terminal.buffer?.active;if(!i||typeof i.getLine!="function")return"";let[o,l]=e<=n?[e,n]:[n,e],d=typeof i.viewportY=="number"?i.viewportY:0,r=[];for(let u=o;u<=l;u++){let f=i.getLine(d+u);r.push(typeof f?.translateToString=="function"?f.translateToString(!0):"")}return r.join(`
3
- `)}catch{return""}}selectRows(e,n){try{if(typeof this.terminal?.selectLines!="function")return;let i=this.terminal.buffer?.active,o=typeof i?.viewportY=="number"?i.viewportY:0,[l,d]=e<=n?[e,n]:[n,e];this.terminal.selectLines(o+l,o+d)}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let i=n.offsetWidth/this.colsValue,o=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=i,this.charH=o)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function qe(t){let e=t.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(e.width,n.width),height:Math.max(0,Math.min(e.height,n.height-Math.max(0,e.top)))}:e}function jt(t,e,n){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let i;if(n.renderer==="ghostty")try{i=await zt.create(t,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),i=await Ke.create(t,n.scrollback,n.theme)}else i=await Ke.create(t,n.scrollback,n.theme);let o,l=0,d,r=null,u=0,f="connecting",C=0,ne=!1,ie=[],$="",A=!1,z=1e3,ft=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),mt="/api/session/"+encodeURIComponent(e)+"/upload",ht=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function vt(){return ie.join("")+$}async function yt(a,s=0){let c=a?i.viewportY():0,b=a?i.baseY():0,x=oe(vt());if(i.reset(),!x){a||i.scrollToBottom();return}await i.write(x),a?i.scrollToLine(b+c+s):i.scrollToBottom()}function j(a){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(a))}function gt(a){let s;try{s=JSON.parse(a)}catch{f==="live"&&($+=a,i.write(oe(a)));return}if(s.type==="auth.required"){let c=localStorage.getItem("tmux-web-token");c?j({type:"auth.token",token:c}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),z=1e3,N(),j({type:"resize",cols:i.cols,rows:i.rows}),w();return}if(s.type==="auth.failed"){if(s.permanentLock||s.retryAfterMs&&s.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(s.message);return}return}if(s.type==="snapshot"&&typeof s.data=="string"){let c=oe(s.data);ie=[c],$="",C=typeof s.lines=="number"?s.lines:n.terminal.initialLines,f="live",i.reset(),i.write(c).then(()=>i.scrollToBottom());return}if(s.type==="window_changed"&&typeof s.activeIndex=="number"){let c=new URL(location.href);c.searchParams.get("window")!==String(s.activeIndex)&&(c.searchParams.set("window",String(s.activeIndex)),history.replaceState(history.state,"",c)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:s.windows}));return}if(s.type==="history"&&typeof s.data=="string"){ne=!1,s.lines>0&&s.data&&(ie.unshift(s.data),C+=s.lines,yt(!0,s.lines));return}if(s.type==="data"&&typeof s.data=="string"){let c=oe(s.data);if(f==="connecting"){f="live",$=c,i.write(c);return}f==="live"&&($+=c,i.write(c))}}function Se(){let a=ft,s=localStorage.getItem("tmux-web-token");s&&(a+=(a.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),o=new WebSocket(a),o.onopen=()=>{f="connecting",C=0,ne=!1,ie=[],$="",i.reset(),z=1e3},o.onmessage=c=>{typeof c.data=="string"&&gt(c.data)},o.onclose=()=>{A||(f="connecting",setTimeout(()=>{z=Math.min(z*2,1e4),A||Se()},z))},o.onerror=()=>o?.close()}function ae(a){j({type:"input",data:a})}function N(){return i.fit()}function be(){t.classList.remove("terminal-pending")}function w(){l&&cancelAnimationFrame(l),d&&clearTimeout(d),N(),l=requestAnimationFrame(()=>{l=0,N()&&be()}),d=setTimeout(()=>{N()&&be()},120)}function wt(){w(),setTimeout(w,50),setTimeout(w,150),setTimeout(w,300)}function He(a){return typeof a=="string"&&a.startsWith("image/")}function De(a){return a?.files?.length?a.files[0]??null:null}function bt(a){if(!a?.items)return null;for(let s=0;s<a.items.length;s++){let c=a.items[s];if(c?.kind==="file"&&He(c.type))return c.getAsFile()}return null}async function Lt(a){let s=new FormData,c=a instanceof File&&a.name?a.name:"upload";s.append("file",a,c);let b=localStorage.getItem("tmux-web-token"),x={};b&&(x.Authorization="Bearer "+b);let xe=await fetch(mt,{method:"POST",body:s,headers:x});if(!xe.ok){let Fe="upload failed";try{let Ve=await xe.json();Ve.error&&(Fe=Ve.error)}catch{}throw new Error(Fe)}let We=await xe.json();if(!We.path)throw new Error("no path in response");return We.path}async function Le(a){try{let s=await Lt(a);ae(s)}catch{}}i.onData(a=>ae(a)),i.onResize(({cols:a,rows:s})=>j({type:"resize",cols:a,rows:s})),i.onScroll(()=>{f!=="live"||ne||!i.isNearScrollbackTop()||(ne=!0,j({type:"load_history",before:C}))});let W=!1,F=!1,E=null,S=null,V=document.createElement("div");V.className="terminal-copy-banner",V.innerHTML='<strong>Copy mode</strong><span class="hint">drag to select \xB7 Esc exits</span>',V.hidden=!0,t.appendChild(V);let Y=document.createElement("div");Y.className="terminal-copy-banner terminal-copy-toast",Y.textContent="Copied",Y.hidden=!0,t.appendChild(Y);function Re(a){let s=t.getBoundingClientRect(),c=s.height>0&&i.rows>0?s.height/i.rows:18;return Math.max(0,Math.min(i.rows-1,Math.floor((a-s.top)/c)))}async function $e(a){if(!a)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(a);return}}catch{}let s=document.createElement("textarea");s.value=a,s.style.position="fixed",s.style.opacity="0",s.style.pointerEvents="none",document.body.appendChild(s),s.focus(),s.select();try{document.execCommand("copy")}catch{}s.remove()}let K;function Ae(){Y.hidden=!1,K&&clearTimeout(K),K=setTimeout(()=>{Y.hidden=!0},1e3)}function q(a){W=a,V.hidden=!a,t.classList.toggle("terminal-copy-mode-active",a),a&&i.focus()}function Ye(){q(!W)}function Et(){return E===null||S===null?null:E<=S?[E,S]:[S,E]}function kt(a){A||a.button!==0||F||(W||a.shiftKey)&&(a.preventDefault(),a.stopPropagation(),F=!0,E=Re(a.clientY),S=E,i.selectRows(E,E),document.addEventListener("mousemove",Ee,!0),document.addEventListener("mouseup",ke,!0))}function Ee(a){A||!F||E===null||(a.preventDefault(),a.stopPropagation(),S=Re(a.clientY),i.selectRows(E,S))}function ke(a){if(A||!F||E===null)return;a.preventDefault(),a.stopPropagation();let s=Et();document.removeEventListener("mousemove",Ee,!0),document.removeEventListener("mouseup",ke,!0),F=!1,q(!1),s&&($e(i.getRowsText(s[0],s[1])).then(Ae),i.focus())}t.addEventListener("mousedown",kt,!0),i.attachCustomKeyEventHandler(a=>{if(a.type!=="keydown")return!0;if(W){if(a.key==="Escape")return a.preventDefault(),q(!1),!1;if(a.key==="Enter"||(a.ctrlKey||a.metaKey)&&a.code==="KeyC")return a.preventDefault(),$e(i.getScreenText()).then(Ae),q(!1),!1}return a.altKey&&a.code==="KeyC"&&!a.ctrlKey&&!a.metaKey?(a.preventDefault(),Ye(),!1):(a.ctrlKey||a.metaKey)&&a.code==="KeyV"?(a.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let c of s)for(let b of c.types)if(He(b)){let x=await c.getType(b);await Le(x);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&i.pasteText(Pe(s))}catch{}})(),!1):!0});let se=0;function Pe(a){return a.split(`\r
1
+ var Nt="https://esm.sh/ghostty-web@0.4.0",Wt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ce(t){return t.replace(Wt,"")}var de;function jt(){return de||(document.querySelector("link[data-tmux-web-xterm-css]")?de=Promise.resolve():(de=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),de))}var qe=class ft{terminal;fitAddon;container;constructor(e,n,a){this.container=e,this.terminal=n,this.fitAddon=a,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,a){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),jt()]),c=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:a});return new ft(e,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(n=>this.terminal.write(e,n))}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=Oe(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,n){let a=this.terminal.buffer.active,[o,r]=e<=n?[e,n]:[n,e],c=a.viewportY,l=[];for(let u=o;u<=r;u++){let f=a.getLine(c+u);l.push(f?f.translateToString(!0):"")}return l.join(`
2
+ `)}selectRows(e,n){let a=this.terminal.buffer.active,[o,r]=e<=n?[e,n]:[n,e];this.terminal.selectLines(a.viewportY+o,a.viewportY+r)}hasSelectionSupport(){return!0}},Vt=class mt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,n){this.container=e,this.terminal=n,this.terminal.open(e)}static async create(e,n,a){let o=await import(Nt);await o.init();let r=new o.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:a});return new mt(e,r)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(n=>this.terminal.write(e,n))}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=Oe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let n=Math.floor(e.width/this.charW),a=Math.floor(e.height/this.charH);if(n<10||a<5)return!1;if(n!==this.colsValue||a!==this.rowsValue){this.colsValue=n,this.rowsValue=a,this.terminal.resize(n,a);for(let o of this.resizeCallbacks)o({cols:n,rows:a})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(n=>e(n))}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,n){try{let a=this.terminal.buffer?.active;if(!a||typeof a.getLine!="function")return"";let[o,r]=e<=n?[e,n]:[n,e],c=typeof a.viewportY=="number"?a.viewportY:0,l=[];for(let u=o;u<=r;u++){let f=a.getLine(c+u);l.push(typeof f?.translateToString=="function"?f.translateToString(!0):"")}return l.join(`
3
+ `)}catch{return""}}selectRows(e,n){try{if(typeof this.terminal?.selectLines!="function")return;let a=this.terminal.buffer?.active,o=typeof a?.viewportY=="number"?a.viewportY:0,[r,c]=e<=n?[e,n]:[n,e];this.terminal.selectLines(o+r,o+c)}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let a=n.offsetWidth/this.colsValue,o=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=a,this.charH=o)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function Oe(t){let e=t.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(e.width,n.width),height:Math.max(0,Math.min(e.height,n.height-Math.max(0,e.top)))}:e}function Ft(t,e,n){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let a;if(n.renderer==="ghostty")try{a=await Vt.create(t,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),a=await qe.create(t,n.scrollback,n.theme)}else a=await qe.create(t,n.scrollback,n.theme);let o,r=0,c,l=null,u=0,f="connecting",B=0,se=!1,oe=[],z="",Y=!1,V=1e3,ht=location.protocol==="https:"?"wss:":"ws:",vt=n.wsBase||"/ws",yt=ht+"//"+location.host+vt+"/"+encodeURIComponent(e),gt="/api/session/"+encodeURIComponent(e)+"/upload",wt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function bt(){return oe.join("")+z}async function Lt(i,s=0){let d=i?a.viewportY():0,L=i?a.baseY():0,T=ce(bt());if(a.reset(),!T){i||a.scrollToBottom();return}await a.write(T),i?a.scrollToLine(L+d+s):a.scrollToBottom()}function F(i){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(i))}function Et(i){let s;try{s=JSON.parse(i)}catch{f==="live"&&(z+=i,a.write(ce(i)));return}if(s.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?F({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),V=1e3,K(),F({type:"resize",cols:a.cols,rows:a.rows}),w();return}if(s.type==="auth.failed"){if(s.permanentLock||s.retryAfterMs&&s.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(s.message);return}return}if(s.type==="snapshot"&&typeof s.data=="string"){let d=ce(s.data);oe=[d],z="",B=typeof s.lines=="number"?s.lines:n.terminal.initialLines,f="live",a.reset(),a.write(d).then(()=>a.scrollToBottom());return}if(s.type==="window_changed"&&typeof s.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(s.activeIndex)&&(d.searchParams.set("window",String(s.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:s.windows}));return}if(s.type==="history"&&typeof s.data=="string"){se=!1,s.lines>0&&s.data&&(oe.unshift(s.data),B+=s.lines,Lt(!0,s.lines));return}if(s.type==="data"&&typeof s.data=="string"){let d=ce(s.data);if(f==="connecting"){f="live",z=d,a.write(d);return}f==="live"&&(z+=d,a.write(d))}}function De(){let i=yt,s=localStorage.getItem("tmux-web-token");s&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),o=new WebSocket(i),o.onopen=()=>{f="connecting",B=0,se=!1,oe=[],z="",a.reset(),V=1e3},o.onmessage=d=>{typeof d.data=="string"&&Et(d.data)},o.onclose=()=>{Y||(f="connecting",setTimeout(()=>{V=Math.min(V*2,1e4),Y||De()},V))},o.onerror=()=>o?.close()}function le(i){F({type:"input",data:i})}function K(){return a.fit()}function Ee(){t.classList.remove("terminal-pending")}function w(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Ee()}),c=setTimeout(()=>{K()&&Ee()},120)}function kt(){w(),setTimeout(w,50),setTimeout(w,150),setTimeout(w,300)}function Re(i){return typeof i=="string"&&i.startsWith("image/")}function $e(i){return i?.files?.length?i.files[0]??null:null}function xt(i){if(!i?.items)return null;for(let s=0;s<i.items.length;s++){let d=i.items[s];if(d?.kind==="file"&&Re(d.type))return d.getAsFile()}return null}async function Tt(i){let s=new FormData,d=i instanceof File&&i.name?i.name:"upload";s.append("file",i,d);let L=localStorage.getItem("tmux-web-token"),T={};L&&(T.Authorization="Bearer "+L);let Ce=await fetch(gt,{method:"POST",body:s,headers:T});if(!Ce.ok){let Ke="upload failed";try{let Ue=await Ce.json();Ue.error&&(Ke=Ue.error)}catch{}throw new Error(Ke)}let Fe=await Ce.json();if(!Fe.path)throw new Error("no path in response");return Fe.path}async function ke(i){try{let s=await Tt(i);le(s)}catch{}}a.onData(i=>le(i)),a.onResize(({cols:i,rows:s})=>F({type:"resize",cols:i,rows:s})),a.onScroll(()=>{f!=="live"||se||!a.isNearScrollbackTop()||(se=!0,F({type:"load_history",before:B}))});let U=!1,q=!1,k=null,D=null,O=document.createElement("div");O.className="terminal-copy-banner",O.innerHTML='<strong>Copy mode</strong><span class="hint">drag to select \xB7 Esc exits</span>',O.hidden=!0,t.appendChild(O);let P=document.createElement("div");P.className="terminal-copy-banner terminal-copy-toast",P.textContent="Copied",P.hidden=!0,t.appendChild(P);function Ae(i){let s=t.getBoundingClientRect(),d=s.height>0&&a.rows>0?s.height/a.rows:18;return Math.max(0,Math.min(a.rows-1,Math.floor((i-s.top)/d)))}async function ze(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let s=document.createElement("textarea");s.value=i,s.style.position="fixed",s.style.opacity="0",s.style.pointerEvents="none",document.body.appendChild(s),s.focus(),s.select();try{document.execCommand("copy")}catch{}s.remove()}let _;function Ye(){P.hidden=!1,_&&clearTimeout(_),_=setTimeout(()=>{P.hidden=!0},1e3)}function X(i){U=i,O.hidden=!i,t.classList.toggle("terminal-copy-mode-active",i),i&&a.focus()}function Pe(){X(!U)}function Ct(){return k===null||D===null?null:k<=D?[k,D]:[D,k]}function Mt(i){Y||i.button!==0||q||(U||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),q=!0,k=Ae(i.clientY),D=k,a.selectRows(k,k),document.addEventListener("mousemove",xe,!0),document.addEventListener("mouseup",Te,!0))}function xe(i){Y||!q||k===null||(i.preventDefault(),i.stopPropagation(),D=Ae(i.clientY),a.selectRows(k,D))}function Te(i){if(Y||!q||k===null)return;i.preventDefault(),i.stopPropagation();let s=Ct();document.removeEventListener("mousemove",xe,!0),document.removeEventListener("mouseup",Te,!0),q=!1,X(!1),s&&(ze(a.getRowsText(s[0],s[1])).then(Ye),a.focus())}t.addEventListener("mousedown",Mt,!0),a.attachCustomKeyEventHandler(i=>{if(i.type!=="keydown")return!0;if(U){if(i.key==="Escape")return i.preventDefault(),X(!1),!1;if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC")return i.preventDefault(),ze(a.getScreenText()).then(Ye),X(!1),!1}return i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey?(i.preventDefault(),Pe(),!1):(i.ctrlKey||i.metaKey)&&i.code==="KeyV"?(i.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let d of s)for(let L of d.types)if(Re(L)){let T=await d.getType(L);await ke(T);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&a.pasteText(Ne(s))}catch{}})(),!1):!0});let re=0;function Ne(i){return i.split(`\r
4
4
  `).join(`
5
5
  `).split(`
6
- `).join("\r")}async function xt(a){let s=bt(a.clipboardData);if(s){a.preventDefault(),a.stopPropagation();let x=Date.now();if(x-se<50)return;se=x,await Le(s);return}let c=a.clipboardData?.getData("text/plain");if(!c)return;a.preventDefault(),a.stopPropagation();let b=Date.now();b-se<50||(se=b,i.pasteText(Pe(c)))}let O=0;function Tt(a){a.preventDefault(),O++,De(a.dataTransfer)&&t.classList.add("terminal-drag-over")}function Ct(a){a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="copy")}function Mt(a){a.preventDefault(),O--,O<=0&&(O=0,t.classList.remove("terminal-drag-over"))}async function Bt(a){a.preventDefault(),O=0,t.classList.remove("terminal-drag-over");let s=De(a.dataTransfer);s&&await Le(s)}function ze(a){!ht||a.key!=="Escape"||W||a.defaultPrevented||a.repeat||a.metaKey||a.ctrlKey||a.altKey||i.isFocused()&&(a.preventDefault(),a.stopPropagation(),ae("\x1B"))}t.addEventListener("paste",xt),t.addEventListener("dragenter",Tt),t.addEventListener("dragover",Ct),t.addEventListener("dragleave",Mt),t.addEventListener("drop",Bt),document.addEventListener("keydown",ze,!0);function It(a){if(a.touches.length!==1){r=null;return}let s=a.touches[0];s&&(r={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,scrolling:!1},a.stopPropagation())}function St(a){if(!r||a.touches.length!==1)return;let s=a.touches[0];if(!s)return;let c=s.clientY-r.startY,b=s.clientX-r.startX;if(!r.scrolling){if(Math.abs(c)<8||Math.abs(c)<Math.abs(b))return;r.scrolling=!0,u=Date.now()+500}a.preventDefault(),a.stopPropagation(),$t(-(s.clientY-r.lastY),s.clientX,s.clientY),r.lastX=s.clientX,r.lastY=s.clientY}function Ht(a){if(!r)return;let s=r.scrolling;r=null,a.stopPropagation(),s?(u=Date.now()+500,a.preventDefault()):i.focus()}function Dt(a){r=null,a.stopPropagation()}function Rt(a){a.pointerType==="touch"&&Date.now()<u&&(a.preventDefault(),a.stopPropagation())}function $t(a,s,c){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:a,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:s,clientY:c,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",It,{passive:!0,capture:!0}),t.addEventListener("touchmove",St,{passive:!1,capture:!0}),t.addEventListener("touchend",Ht,{passive:!1,capture:!0}),t.addEventListener("touchcancel",Dt,{passive:!0,capture:!0}),t.addEventListener("pointerup",Rt,!0);function je(){w()}window.addEventListener("resize",je),window.visualViewport?.addEventListener("resize",w),window.visualViewport?.addEventListener("scroll",w);let Ne=new ResizeObserver(()=>w());return Ne.observe(t),document.fonts?.ready.then(()=>{w(),setTimeout(w,50),setTimeout(w,200)}),t.addEventListener("focusin",wt,!0),requestAnimationFrame(()=>{N()&&be(),Se(),w()}),{destroy(){if(A=!0,q(!1),K&&clearTimeout(K),document.removeEventListener("mousemove",Ee,!0),document.removeEventListener("mouseup",ke,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",je),Ne.disconnect(),document.removeEventListener("keydown",ze,!0)},focus(){i.focus()},sendInput(a){ae(a)},toggleCopyMode(){Ye()}}})()}var U=window.__TMUX_WEB_SHELL__,m=null,y=null,Oe="sessions",B="",re="",ce="",Ue="",h=document.getElementById("sidebar-content"),H=document.getElementById("main-placeholder"),L=document.getElementById("terminal-container"),X=document.getElementById("file-editor"),Nt=document.getElementById("fe-path"),k=document.getElementById("fe-status"),_=document.getElementById("fe-content"),D=document.getElementById("fe-save"),Wt=document.getElementById("fe-delete"),Ft=document.getElementById("fe-back"),Xe=document.getElementById("fe-new-name"),Vt=document.getElementById("fe-new-btn"),Te=document.getElementById("settings-backdrop"),_e=document.getElementById("settings-popover"),Je=document.querySelector(".sidebar");function Kt(){return window.matchMedia("(max-width: 640px)").matches}function Ce(){Kt()&&Je.classList.add("collapsed")}function Ze(){Je.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Ze(),Ge("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Ze(),Ge("files")});function Ge(t){Oe=t,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?J():Qt()}async function J(){h.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let n of e){let i=document.createElement("div");i.className="session-item"+(n.name===y?" active":""),i.setAttribute("data-session",n.name),i.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${n.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>
7
- <span>${p(n.name)}</span>
8
- <button class="session-edit-btn" data-session="${p(n.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>
9
- `,i.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||de(n.name)}),h.appendChild(i);let o=i.querySelector(".session-edit-btn");o&&o.addEventListener("click",l=>{l.stopPropagation(),sn(n.name,o)})}}catch{h.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function p(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function de(t){if(!(y===t&&m)){X.style.display="none",H.style.display="none",L.style.display="",L.classList.add("terminal-pending"),Ce(),m&&(ge(),m.destroy(),m=null),y=t;for(let e of h.querySelectorAll(".session-item"))e.classList.toggle("active",e.getAttribute("data-session")===t);try{m=await jt(L,t,{terminal:U.terminal,scrollback:U.scrollback,theme:U.theme,renderer:U.renderer}),rt()}catch(e){console.error("[shell] terminal init failed:",e);let n=e&&typeof e=="object"&&"message"in e?e.message:String(e);L.textContent="Failed to open terminal for "+t+": "+n,L.classList.remove("terminal-pending")}}}var ue=[],v=null,Qe=!1;function qt(t){if(!v||!v.files.length)return null;let e=v.repoRoot;if(!e||!t.startsWith(e))return null;let n=t.slice(e.length+1);for(let i of v.files)if(i.path===n)return i.status;return null}async function Ot(t){Qe=!0;try{let e=await fetch("/api/git/status?path="+encodeURIComponent(t));e.ok?v=await e.json():v=null}catch{v=null}finally{Qe=!1}}function Ut(){if(!v||!v.branch)return"";let{branch:t,linesAdded:e,linesRemoved:n}=v,i=`<div class="sidebar-git"><div class="git-branch-badge">
6
+ `).join("\r")}async function Bt(i){let s=xt(i.clipboardData);if(s){i.preventDefault(),i.stopPropagation();let T=Date.now();if(T-re<50)return;re=T,await ke(s);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let L=Date.now();L-re<50||(re=L,a.pasteText(Ne(d)))}let J=0;function It(i){i.preventDefault(),J++,$e(i.dataTransfer)&&t.classList.add("terminal-drag-over")}function St(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function Ht(i){i.preventDefault(),J--,J<=0&&(J=0,t.classList.remove("terminal-drag-over"))}async function Dt(i){i.preventDefault(),J=0,t.classList.remove("terminal-drag-over");let s=$e(i.dataTransfer);s&&await ke(s)}function We(i){!wt||i.key!=="Escape"||U||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||a.isFocused()&&(i.preventDefault(),i.stopPropagation(),le("\x1B"))}t.addEventListener("paste",Bt),t.addEventListener("dragenter",It),t.addEventListener("dragover",St),t.addEventListener("dragleave",Ht),t.addEventListener("drop",Dt),document.addEventListener("keydown",We,!0);function Rt(i){if(i.touches.length!==1){l=null;return}let s=i.touches[0];s&&(l={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,scrolling:!1},i.stopPropagation())}function $t(i){if(!l||i.touches.length!==1)return;let s=i.touches[0];if(!s)return;let d=s.clientY-l.startY,L=s.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(L))return;l.scrolling=!0,u=Date.now()+500}i.preventDefault(),i.stopPropagation(),Pt(-(s.clientY-l.lastY),s.clientX,s.clientY),l.lastX=s.clientX,l.lastY=s.clientY}function At(i){if(!l)return;let s=l.scrolling;l=null,i.stopPropagation(),s?(u=Date.now()+500,i.preventDefault()):a.focus()}function zt(i){l=null,i.stopPropagation()}function Yt(i){i.pointerType==="touch"&&Date.now()<u&&(i.preventDefault(),i.stopPropagation())}function Pt(i,s,d){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:i,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:s,clientY:d,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",Rt,{passive:!0,capture:!0}),t.addEventListener("touchmove",$t,{passive:!1,capture:!0}),t.addEventListener("touchend",At,{passive:!1,capture:!0}),t.addEventListener("touchcancel",zt,{passive:!0,capture:!0}),t.addEventListener("pointerup",Yt,!0);function je(){w()}window.addEventListener("resize",je),window.visualViewport?.addEventListener("resize",w),window.visualViewport?.addEventListener("scroll",w);let Ve=new ResizeObserver(()=>w());return Ve.observe(t),document.fonts?.ready.then(()=>{w(),setTimeout(w,50),setTimeout(w,200)}),t.addEventListener("focusin",kt,!0),requestAnimationFrame(()=>{K()&&Ee(),De(),w()}),{destroy(){if(Y=!0,X(!1),_&&clearTimeout(_),document.removeEventListener("mousemove",xe,!0),document.removeEventListener("mouseup",Te,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",je),Ve.disconnect(),document.removeEventListener("keydown",We,!0)},focus(){a.focus()},sendInput(i){le(i)},toggleCopyMode(){Pe()}}})()}var N=window.__TMUX_WEB_SHELL__,h=null,y=null,C=null,_e="sessions",S="",ue="",pe="",Xe="";function b(t){return C?"/a/"+encodeURIComponent(C)+t:t}var m=document.getElementById("sidebar-content"),R=document.getElementById("main-placeholder"),E=document.getElementById("terminal-container"),Z=document.getElementById("file-editor"),Kt=document.getElementById("fe-path"),x=document.getElementById("fe-status"),G=document.getElementById("fe-content"),$=document.getElementById("fe-save"),Ut=document.getElementById("fe-delete"),qt=document.getElementById("fe-back"),Je=document.getElementById("fe-new-name"),Ot=document.getElementById("fe-new-btn"),Me=document.getElementById("settings-backdrop"),Ze=document.getElementById("settings-popover"),Ge=document.querySelector(".sidebar");function _t(){return window.matchMedia("(max-width: 640px)").matches}function Be(){_t()&&Ge.classList.add("collapsed")}function Qe(){Ge.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Qe(),et("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Qe(),et("files")});function et(t){_e=t,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?W():an()}async function W(){m.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=await(await fetch("/api/sidebar/sessions")).json(),n=e.recent||[];for(let o of n){let r=document.createElement("div");r.className="session-item"+(o.name===y&&!C?" active":""),r.setAttribute("data-session",o.name),r.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${o.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>
7
+ <span>${p(o.name)}</span>
8
+ <button class="session-edit-btn" data-session="${p(o.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>
9
+ `,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||Q(o.name)}),m.appendChild(r);let c=r.querySelector(".session-edit-btn");c&&c.addEventListener("click",l=>{l.stopPropagation(),cn(o.name,c)})}let a=e.agents||[];for(let o of a){if(!o.sessions||o.sessions.length===0)continue;let r=document.createElement("div");r.className="sidebar-section-label"+(o.online?"":" agent-offline-label"),r.textContent=o.agentName+(o.online?"":" (offline)"),m.appendChild(r);for(let c of o.sessions){let l=document.createElement("div");l.className="session-item"+(c.name===y&&C===o.agentId?" active":"")+(o.online?"":" session-offline"),l.setAttribute("data-session",c.name),l.setAttribute("data-agent",o.agentId),l.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>
10
+ <span>${p(c.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&l.addEventListener("click",u=>{u.target.closest(".session-edit-btn")||Q(c.name,o.agentId)}),m.appendChild(l)}}}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function p(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function Q(t,e){if(!(y===t&&h&&C===(e||null))){Z.style.display="none",R.style.display="none",E.style.display="",E.classList.add("terminal-pending"),Be(),h&&(be(),h.destroy(),h=null),y=t,C=e||null,window.__tmuxWebScopeAgent=C;for(let n of m.querySelectorAll(".session-item"))n.classList.toggle("active",n.getAttribute("data-session")===t&&(n.getAttribute("data-agent")||null)===C);try{h=await Ft(E,t,{terminal:N.terminal,scrollback:N.scrollback,theme:N.theme,renderer:N.renderer,wsBase:C?"/ws/a/"+encodeURIComponent(C):N.wsBase||"/ws"}),dt()}catch(n){console.error("[shell] terminal init failed:",n);let a=n&&typeof n=="object"&&"message"in n?n.message:String(n);E.textContent="Failed to open terminal for "+t+": "+a,E.classList.remove("terminal-pending")}}}var fe=[],v=null,tt=!1;function Xt(t){if(!v||!v.files.length)return null;let e=v.repoRoot;if(!e||!t.startsWith(e))return null;let n=t.slice(e.length+1);for(let a of v.files)if(a.path===n)return a.status;return null}async function Jt(t){tt=!0;try{let e=await fetch(b("/api/git/status?path="+encodeURIComponent(t)));e.ok?v=await e.json():v=null}catch{v=null}finally{tt=!1}}function Zt(){if(!v||!v.branch)return"";let{branch:t,linesAdded:e,linesRemoved:n}=v,a=`<div class="sidebar-git"><div class="git-branch-badge">
10
11
  <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>
11
- ${p(t)}</div>`;return(e>0||n>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${n}</span></div>`),i+"</div>"}h.addEventListener("click",t=>{t.target.closest(".sidebar-git")&&P&&v&&(t.stopPropagation(),at())});var Z=document.getElementById("header-git"),et=document.getElementById("git-diff-view"),tt=document.getElementById("gd-path"),pe=document.getElementById("gd-status"),fe=document.getElementById("gd-content"),Xt=document.getElementById("gd-back"),P=null;function G(t){if(!t||!t.branch){Z.style.display="none",P=null;return}P=t.repoRoot;let{branch:e,linesAdded:n,linesRemoved:i}=t,o=`<span class="branch">${p(e)}</span>`;(n>0||i>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${i}</span>`),Z.innerHTML=o,Z.style.display="block"}Z.addEventListener("click",t=>{P&&v&&(t.stopPropagation(),at())});var g=document.getElementById("git-popover"),me=document.getElementById("git-popover-backdrop");function Me(){let t=Z.getBoundingClientRect();g.style.top=t.bottom+4+"px",g.style.left=Math.max(8,Math.min(t.left,window.innerWidth-400))+"px"}function nt(){let t=P,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(ve(),t){Oe="files",document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),I(t)}})}var it="";function at(){if(!v)return;let t=v.files||[];if(!t.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
12
- <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${t.length} file${t.length>1?"s":""} changed</div>`;for(let n=0;n<t.length;n++){let i=t[n],o=i.additions||0,l=i.deletions||0,d="";switch(i.status){case"M":d='<span class="git-file-status M">M</span>';break;case"A":d='<span class="git-file-status A">A</span>';break;case"D":d='<span class="git-file-status D">D</span>';break;case"?":d='<span class="git-file-status ?">?</span>';break;case"R":d='<span class="git-file-status R">R</span>';break}e+=`<div class="git-popover-item" data-idx="${n}">
13
- ${d}<span class="file-path">${p(i.path)}</span>
12
+ ${p(t)}</div>`;return(e>0||n>0)&&(a+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${n}</span></div>`),a+"</div>"}m.addEventListener("click",t=>{t.target.closest(".sidebar-git")&&j&&v&&(t.stopPropagation(),ot())});var ee=document.getElementById("header-git"),nt=document.getElementById("git-diff-view"),it=document.getElementById("gd-path"),me=document.getElementById("gd-status"),he=document.getElementById("gd-content"),Gt=document.getElementById("gd-back"),j=null;function te(t){if(!t||!t.branch){ee.style.display="none",j=null;return}j=t.repoRoot;let{branch:e,linesAdded:n,linesRemoved:a}=t,o=`<span class="branch">${p(e)}</span>`;(n>0||a>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${a}</span>`),ee.innerHTML=o,ee.style.display="block"}ee.addEventListener("click",t=>{j&&v&&(t.stopPropagation(),ot())});var g=document.getElementById("git-popover"),ve=document.getElementById("git-popover-backdrop");function Ie(){let t=ee.getBoundingClientRect();g.style.top=t.bottom+4+"px",g.style.left=Math.max(8,Math.min(t.left,window.innerWidth-400))+"px"}function at(){let t=j,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(ge(),t){_e="files",document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),H(t)}})}var st="";function ot(){if(!v)return;let t=v.files||[];if(!t.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
13
+ <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${t.length} file${t.length>1?"s":""} changed</div>`;for(let n=0;n<t.length;n++){let a=t[n],o=a.additions||0,r=a.deletions||0,c="";switch(a.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="${n}">
14
+ ${c}<span class="file-path">${p(a.path)}</span>
14
15
  <span class="file-add">${o>0?"+"+o:""}</span>
15
- <span class="file-del">${l>0?"-"+l:""}</span>
16
- </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',it=e,g.innerHTML=e,g.querySelectorAll(".git-popover-item").forEach(n=>{let i=parseInt(n.dataset.idx||"0",10),o=t[i];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>ot(o.path)))})}Me(),g.classList.add("open"),me.classList.add("open"),nt()}function _t(){return window.innerWidth>=1024}function st(t,e){let n=(t+(e&&t?`
16
+ <span class="file-del">${r>0?"-"+r:""}</span>
17
+ </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',st=e,g.innerHTML=e,g.querySelectorAll(".git-popover-item").forEach(n=>{let a=parseInt(n.dataset.idx||"0",10),o=t[a];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>rt(o.path)))})}Ie(),g.classList.add("open"),ve.classList.add("open"),at()}function Qt(){return window.innerWidth>=1024}function lt(t,e){let n=(t+(e&&t?`
17
18
  `:"")+e).split(`
18
- `),i="",o=0,l=0;for(let d of n){let r=d;if(r.startsWith("@@")){let u=r.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,l=parseInt(u[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${p(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">${p(r)}</span></div>`);continue}if(r.startsWith("+"))l++,i+=`<div class="diff-line add"><span class="ln-no">${l}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${p(r)}</span></div>`;else{if(!r&&!o&&!l)continue;o++,l++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${l}</span><span class="ln-body">${p(r)}</span></div>`}}return{html:i,lineCount:n.length}}var he=null;async function ot(t){let e=P;if(e)if(ve(),_t()){L.style.display="none",m&&(ge(),m.destroy(),m=null,he=y,y=null),G(null),X.style.display="none",H.style.display="none",et.style.display="flex",tt.textContent=t,pe.textContent="Loading\u2026",fe.innerHTML="";try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t));if(!n.ok)throw new Error(await n.text());let i=await n.json();Zt(t,i.diff,i.stagedDiff)}catch(n){let i=n instanceof Error?n.message:String(n);pe.textContent="Error",fe.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${p(i)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
19
+ `),a="",o=0,r=0;for(let c of n){let l=c;if(l.startsWith("@@")){let u=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),a+=`<div class="diff-line hunk"><span class="ln-body">${p(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(a+=`<div class="diff-line header"><span class="ln-body">${p(l)}</span></div>`);continue}if(l.startsWith("+"))r++,a+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${p(l)}</span></div>`;else if(l.startsWith("-"))o++,a+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${p(l)}</span></div>`;else if(l.startsWith("\\"))a+=`<div class="diff-line"><span class="ln-body">${p(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,a+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${p(l)}</span></div>`}}return{html:a,lineCount:n.length}}var ye=null;async function rt(t){let e=j;if(e)if(ge(),Qt()){E.style.display="none",h&&(be(),h.destroy(),h=null,ye=y,y=null),te(null),Z.style.display="none",R.style.display="none",nt.style.display="flex",it.textContent=t,me.textContent="Loading\u2026",he.innerHTML="";try{let n=await fetch(b("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t)));if(!n.ok)throw new Error(await n.text());let a=await n.json();tn(t,a.diff,a.stagedDiff)}catch(n){let a=n instanceof Error?n.message:String(n);me.textContent="Error",he.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${p(a)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
19
20
  <button class="diff-back">\u2190 Back</button>
20
21
  <span class="diff-filename">${p(t)}</span>
21
22
  <span class="diff-loading">Loading\u2026</span>
22
- </div></div>`,Me(),g.classList.add("open"),me.classList.add("open");try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t));if(!n.ok)throw new Error(await n.text());let i=await n.json();Jt(t,i.diff,i.stagedDiff)}catch(n){let i=n instanceof Error?n.message:String(n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
+ </div></div>`,Ie(),g.classList.add("open"),ve.classList.add("open");try{let n=await fetch(b("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t)));if(!n.ok)throw new Error(await n.text());let a=await n.json();en(t,a.diff,a.stagedDiff)}catch(n){let a=n instanceof Error?n.message:String(n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
24
  <button class="diff-back">\u2190 Back</button>
24
25
  <span class="diff-filename">${p(t)}</span>
25
- </div><div class="diff-empty">Error loading diff: ${p(i)}</div></div>`}}}function Jt(t,e,n){if(!e&&!n){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
26
+ </div><div class="diff-empty">Error loading diff: ${p(a)}</div></div>`}}}function en(t,e,n){if(!e&&!n){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
26
27
  <button class="diff-back">\u2190 Back</button>
27
28
  <span class="diff-filename">${p(t)}</span>
28
- </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:i,lineCount:o}=st(e,n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
29
+ </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:a,lineCount:o}=lt(e,n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
29
30
  <button class="diff-back">\u2190 Back</button>
30
31
  <span class="diff-filename">${p(t)}</span>
31
32
  <span class="diff-filesize">${o} lines</span>
32
- </div><div class="diff-content">${i}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=it,Me(),v&&g.querySelectorAll(".git-popover-item").forEach(l=>{let d=parseInt(l.dataset.idx||"0",10),r=v.files[d];!r||r.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>ot(r.path)))}),nt()})}function Zt(t,e,n){if(!e&&!n){pe.textContent="No diff",fe.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:o}=st(e,n);tt.textContent=t,pe.textContent=o+" lines",fe.innerHTML=i}function Gt(){et.style.display="none",he?(L.style.display="",L.classList.add("terminal-pending"),de(he),he=null):y?(L.style.display="",L.classList.add("terminal-pending"),rt()):H.style.display="flex"}Xt.addEventListener("click",Gt);function ve(){g.classList.remove("open"),me.classList.remove("open")}me.addEventListener("click",ve),document.addEventListener("keydown",t=>{t.key==="Escape"&&(ve(),we())});async function lt(t){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();if(e.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(n.ok){let i=await n.json();v=i,G(i);return}}}catch{}v=null,G(null)}var ye=null;function rt(){ge(),y&&(lt(y),ye=setInterval(()=>{y&&lt(y)},3e4))}function ge(){ye&&(clearInterval(ye),ye=null)}function Qt(){if(ue=U.fsRoots||[],ue.length===0){if(y){en(y);return}h.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}h.innerHTML="";for(let t of ue){let e=document.createElement("div");e.className="file-tree-item",e.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${p(t)}`,e.addEventListener("click",()=>I(t)),h.appendChild(e)}}async function en(t){h.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();e.path?I(e.path):h.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{h.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function I(t){B=t,h.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=Ot(t);try{let n=await(await fetch("/api/fs/list?path="+encodeURIComponent(t))).json();await e,h.innerHTML="",G(v),h.innerHTML+=Ut();let i=tn(t);if(i&&!ue.some(l=>t===l)){let l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>I(i)),h.appendChild(l)}if(n.dirs)for(let l of n.dirs){let d=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> ${p(d)}`,r.addEventListener("click",()=>I(l)),h.appendChild(r)}else h.innerHTML="";let o=n.files||[];for(let l of o){let d=l.split("/").pop()||l.split("\\").pop(),r=qt(l),u="",f="";if(r)switch(r){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let C=document.createElement("div");C.className="file-tree-item file"+(u?" "+u:""),C.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${p(d)}`,C.addEventListener("click",()=>nn(l)),h.appendChild(C)}}catch{h.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function tn(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function nn(t){re=t,L.style.display="none",m&&(ge(),m.destroy(),m=null,y=null),G(null),Ce(),H.style.display="none",X.style.display="flex",Nt.textContent=t,k.textContent="Loading...",D.style.display="none";try{let e=await fetch("/api/file?path="+encodeURIComponent(t));if(!e.ok){let i=await e.json();k.textContent=i.error||"Failed to load",_.value="";return}let n=await e.json();_.value=n.content,ce=n.content,Ue=n.content,k.textContent=n.size+" bytes",D.style.display=""}catch{k.textContent="Failed to load",_.value=""}}_.addEventListener("input",()=>{ce=_.value}),D.addEventListener("click",async()=>{D.textContent="Saving...",D.disabled=!0;try{let t=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:re,content:ce})});if(!t.ok){let e=await t.json();k.textContent=e.error||"Save failed";return}Ue=ce,k.textContent="Saved"}catch{k.textContent="Save failed"}finally{D.disabled=!1,D.textContent="Save"}}),Wt.addEventListener("click",async()=>{if(confirm("Delete "+re+"?"))try{let t=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:re})});if(!t.ok){let e=await t.json();k.textContent=e.error||"Delete failed";return}X.style.display="none",H.style.display="flex",B&&I(B)}catch{k.textContent="Delete failed"}}),Ft.addEventListener("click",()=>{X.style.display="none",H.style.display="flex",B&&I(B)}),Vt.addEventListener("click",async()=>{let t=Xe.value.trim();if(!t)return;let e=B+"/"+t;try{let n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!n.ok){let i=await n.json();k.textContent=i.error||"Create failed";return}Xe.value="",B&&I(B)}catch{k.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=_e.classList.toggle("open");Te.classList.toggle("open",t)}),Te.addEventListener("click",()=>{_e.classList.remove("open"),Te.classList.remove("open")});var R="",Q=document.createElement("div");Q.className="session-popover-backdrop",document.body.appendChild(Q);var M=document.createElement("div");M.id="session-popover",M.className="session-popover",M.style.display="none",M.innerHTML=`<div class="sp-field">
33
+ </div><div class="diff-content">${a}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=st,Ie(),v&&g.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),l=v.files[c];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>rt(l.path)))}),at()})}function tn(t,e,n){if(!e&&!n){me.textContent="No diff",he.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:a,lineCount:o}=lt(e,n);it.textContent=t,me.textContent=o+" lines",he.innerHTML=a}function nn(){nt.style.display="none",ye?(E.style.display="",E.classList.add("terminal-pending"),Q(ye),ye=null):y?(E.style.display="",E.classList.add("terminal-pending"),dt()):R.style.display="flex"}Gt.addEventListener("click",nn);function ge(){g.classList.remove("open"),ve.classList.remove("open")}ve.addEventListener("click",ge),document.addEventListener("keydown",t=>{t.key==="Escape"&&(ge(),Le())});async function ct(t){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();if(e.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(n.ok){let a=await n.json();v=a,te(a);return}}}catch{}v=null,te(null)}var we=null;function dt(){be(),y&&(ct(y),we=setInterval(()=>{y&&ct(y)},3e4))}function be(){we&&(clearInterval(we),we=null)}function an(){if(fe=N.fsRoots||[],fe.length===0){if(y){sn(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 t of fe){let e=document.createElement("div");e.className="file-tree-item",e.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${p(t)}`,e.addEventListener("click",()=>H(t)),m.appendChild(e)}}async function sn(t){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch(b("/api/fs/session-path?session="+encodeURIComponent(t)))).json();e.path?H(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 H(t){S=t,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=Jt(t);try{let n=await(await fetch(b("/api/fs/list?path="+encodeURIComponent(t)))).json();await e,m.innerHTML="",te(v),m.innerHTML+=Zt();let a=on(t);if(a&&!fe.some(r=>t===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>H(a)),m.appendChild(r)}if(n.dirs)for(let r of n.dirs){let c=r.split("/").pop()||r.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${p(c)}`,l.addEventListener("click",()=>H(r)),m.appendChild(l)}else m.innerHTML="";let o=n.files||[];for(let r of o){let c=r.split("/").pop()||r.split("\\").pop(),l=Xt(r),u="",f="";if(l)switch(l){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let B=document.createElement("div");B.className="file-tree-item file"+(u?" "+u:""),B.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${p(c)}`,B.addEventListener("click",()=>ln(r)),m.appendChild(B)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function on(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function ln(t){ue=t,E.style.display="none",h&&(be(),h.destroy(),h=null,y=null),te(null),Be(),R.style.display="none",Z.style.display="flex",Kt.textContent=t,x.textContent="Loading...",$.style.display="none";try{let e=await fetch(b("/api/file?path="+encodeURIComponent(t)));if(!e.ok){let a=await e.json();x.textContent=a.error||"Failed to load",G.value="";return}let n=await e.json();G.value=n.content,pe=n.content,Xe=n.content,x.textContent=n.size+" bytes",$.style.display=""}catch{x.textContent="Failed to load",G.value=""}}G.addEventListener("input",()=>{pe=G.value}),$.addEventListener("click",async()=>{$.textContent="Saving...",$.disabled=!0;try{let t=await fetch(b("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:ue,content:pe})});if(!t.ok){let e=await t.json();x.textContent=e.error||"Save failed";return}Xe=pe,x.textContent="Saved"}catch{x.textContent="Save failed"}finally{$.disabled=!1,$.textContent="Save"}}),Ut.addEventListener("click",async()=>{if(confirm("Delete "+ue+"?"))try{let t=await fetch(b("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:ue})});if(!t.ok){let e=await t.json();x.textContent=e.error||"Delete failed";return}Z.style.display="none",R.style.display="flex",S&&H(S)}catch{x.textContent="Delete failed"}}),qt.addEventListener("click",()=>{Z.style.display="none",R.style.display="flex",S&&H(S)}),Ot.addEventListener("click",async()=>{let t=Je.value.trim();if(!t)return;let e=S+"/"+t;try{let n=await fetch(b("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!n.ok){let a=await n.json();x.textContent=a.error||"Create failed";return}Je.value="",S&&H(S)}catch{x.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=Ze.classList.toggle("open");Me.classList.toggle("open",t)}),Me.addEventListener("click",()=>{Ze.classList.remove("open"),Me.classList.remove("open")});var A="",ne=document.createElement("div");ne.className="session-popover-backdrop",document.body.appendChild(ne);var I=document.createElement("div");I.id="session-popover",I.className="session-popover",I.style.display="none",I.innerHTML=`<div class="sp-field">
33
34
  <label>Rename session</label>
34
35
  <input type="text" id="sp-name" />
35
36
  </div>
@@ -38,5 +39,5 @@ var At="https://esm.sh/ghostty-web@0.4.0",Yt=/\x1b\](?:10|11|110|111|104)(?:;[^\
38
39
  <button class="btn" id="sp-cancel">Cancel</button>
39
40
  </div>
40
41
  <hr class="sp-divider" />
41
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(M);function an(t){let e=t.getBoundingClientRect();M.style.top=e.bottom+4+"px",M.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function sn(t,e){R=t;let n=document.getElementById("sp-name");n.value=t,Q.classList.add("open"),M.style.display="block",an(e),setTimeout(()=>n.focus(),50)}function ee(){Q.classList.remove("open"),M.style.display="none"}Q.addEventListener("click",ee),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===R){ee();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:R,newName:t})})).ok)return;ee(),J(),y===R&&de(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+R+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:R})})).ok)return;ee(),y===R&&(m&&(m.destroy(),m=null),y=null,L.style.display="none",H.style.display="flex"),J()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",ee);var on={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!m)return;let n=on[e.dataset.key];n&&(m.sendInput(n),m.focus())});var T=document.getElementById("mk-input"),ct=document.getElementById("mk-send");function dt(){if(!T||!m)return;let t=T.value;t&&(m.sendInput(t+"\r"),T.value="",T.rows=1,m.focus())}if(T&&(T.addEventListener("input",()=>{T.rows=1;let t=T.value.split(`
42
- `).length;T.rows=Math.min(t,4)}),T.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),dt())})),ct&&ct.addEventListener("click",dt),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Be=document.getElementById("process-panel"),te=document.getElementById("proc-list"),ln=document.getElementById("proc-close"),Ie=document.getElementById("proc-backdrop"),rn=document.getElementById("header-ram");function cn(){te.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(t=>t.json()).then(t=>{if(!Array.isArray(t)||!t.length){te.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let n of t){let i=n.rss,o=i<1048576?(i/1024).toFixed(0)+"K":(i/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+p(n.command)+'">'+p(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}te.innerHTML=e;for(let n of te.querySelectorAll(".proc-row")){let i=n.querySelector(".proc-kill");i&&i.addEventListener("click",o=>{let l=o.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(d=>d.json()).then(d=>{d.ok?l.style.opacity="0.3":alert("Failed: "+(d.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{te.innerHTML='<div class="proc-empty">Failed to load</div>'})}function dn(){Be.classList.add("open"),Ie.classList.add("open"),cn()}function we(){Be.classList.remove("open"),Ie.classList.remove("open")}rn.addEventListener("click",t=>{t.stopPropagation(),Be.classList.contains("open")?we():dn()}),ln.addEventListener("click",we),Ie.addEventListener("click",we),window.__openSession=de,window.__refreshSidebar=J,J(),Ce();
42
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(I);function rn(t){let e=t.getBoundingClientRect();I.style.top=e.bottom+4+"px",I.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function cn(t,e){A=t;let n=document.getElementById("sp-name");n.value=t,ne.classList.add("open"),I.style.display="block",rn(e),setTimeout(()=>n.focus(),50)}function ie(){ne.classList.remove("open"),I.style.display="none"}ne.addEventListener("click",ie),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===A){ie();return}try{if(!(await fetch(b("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:A,newName:t})})).ok)return;ie(),W(),y===A&&Q(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+A+'"?'))try{if(!(await fetch(b("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:A})})).ok)return;ie(),y===A&&(h&&(h.destroy(),h=null),y=null,E.style.display="none",R.style.display="flex"),W()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",ie);var dn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!h)return;let n=dn[e.dataset.key];n&&(h.sendInput(n),h.focus())});var M=document.getElementById("mk-input"),ut=document.getElementById("mk-send");function pt(){if(!M||!h)return;let t=M.value;t&&(h.sendInput(t+"\r"),M.value="",M.rows=1,h.focus())}if(M&&(M.addEventListener("input",()=>{M.rows=1;let t=M.value.split(`
43
+ `).length;M.rows=Math.min(t,4)}),M.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),pt())})),ut&&ut.addEventListener("click",pt),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Se=document.getElementById("process-panel"),ae=document.getElementById("proc-list"),un=document.getElementById("proc-close"),He=document.getElementById("proc-backdrop"),pn=document.getElementById("header-ram");function fn(){ae.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(b("/api/system/processes")).then(t=>t.json()).then(t=>{if(!Array.isArray(t)||!t.length){ae.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let n of t){let a=n.rss,o=a<1048576?(a/1024).toFixed(0)+"K":(a/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+p(n.command)+'">'+p(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}ae.innerHTML=e;for(let n of ae.querySelectorAll(".proc-row")){let a=n.querySelector(".proc-kill");a&&a.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(b("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?r.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ae.innerHTML='<div class="proc-empty">Failed to load</div>'})}function mn(){Se.classList.add("open"),He.classList.add("open"),fn()}function Le(){Se.classList.remove("open"),He.classList.remove("open")}pn.addEventListener("click",t=>{t.stopPropagation(),Se.classList.contains("open")?Le():mn()}),un.addEventListener("click",Le),He.addEventListener("click",Le),window.__openSession=Q,window.__refreshSidebar=W,W(),Be(),setInterval(()=>{document.visibilityState==="visible"&&W()},1e4);
@@ -1,30 +1,31 @@
1
- import{initTerminal as be}from"./terminal-core.js";const I=window.__TMUX_WEB_SHELL__;let r=null,u=null,ee="sessions",L="",P="",j="",te="";const p=document.getElementById("sidebar-content"),E=document.getElementById("main-placeholder"),v=document.getElementById("terminal-container"),M=document.getElementById("file-editor"),Le=document.getElementById("fe-path"),g=document.getElementById("fe-status"),T=document.getElementById("fe-content"),k=document.getElementById("fe-save"),we=document.getElementById("fe-delete"),Ee=document.getElementById("fe-back"),ne=document.getElementById("fe-new-name"),ke=document.getElementById("fe-new-btn"),K=document.getElementById("settings-backdrop"),se=document.getElementById("settings-popover"),ie=document.querySelector(".sidebar");function Be(){return window.matchMedia("(max-width: 640px)").matches}function X(){Be()&&ie.classList.add("collapsed")}function oe(){ie.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{oe(),ae("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{oe(),ae("files")});function ae(e){ee=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));const t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?S():Re()}async function S(){p.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const s=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(const i of s){const a=document.createElement("div");a.className="session-item"+(i.name===u?" active":""),a.setAttribute("data-session",i.name),a.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
- <span>${c(i.name)}</span>
3
- <button class="session-edit-btn" data-session="${c(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
- `,a.addEventListener("click",d=>{d.target.closest(".session-edit-btn")||A(i.name)}),p.appendChild(a);const o=a.querySelector(".session-edit-btn");o&&o.addEventListener("click",d=>{d.stopPropagation(),je(i.name,o)})}}catch{p.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 c(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function A(e){if(!(u===e&&r)){M.style.display="none",E.style.display="none",v.style.display="",v.classList.add("terminal-pending"),X(),r&&(_(),r.destroy(),r=null),u=e;for(const t of p.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e);try{r=await be(v,e,{terminal:I.terminal,scrollback:I.scrollback,theme:I.theme,renderer:I.renderer}),ye()}catch(t){console.error("[shell] terminal init failed:",t);const n=t&&typeof t=="object"&&"message"in t?t.message:String(t);v.textContent="Failed to open terminal for "+e+": "+n,v.classList.remove("terminal-pending")}}}let W=[],f=null,le=!1;function xe(e){if(!f||!f.files.length)return null;const t=f.repoRoot;if(!t||!e.startsWith(t))return null;const n=e.slice(t.length+1);for(const s of f.files)if(s.path===n)return s.status;return null}async function Ce(e){le=!0;try{const t=await fetch("/api/git/status?path="+encodeURIComponent(e));t.ok?f=await t.json():f=null}catch{f=null}finally{le=!1}}function Ie(){if(!f||!f.branch)return"";const{branch:e,linesAdded:t,linesRemoved:n}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
1
+ import{initTerminal as we}from"./terminal-core.js";const M=window.__TMUX_WEB_SHELL__;let p=null,u=null,b=null,ne="sessions",E="",z="",W="",se="";function g(e){return b?"/a/"+encodeURIComponent(b)+e:e}const r=document.getElementById("sidebar-content"),B=document.getElementById("main-placeholder"),y=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ee=document.getElementById("fe-path"),h=document.getElementById("fe-status"),$=document.getElementById("fe-content"),C=document.getElementById("fe-save"),ke=document.getElementById("fe-delete"),Be=document.getElementById("fe-back"),ie=document.getElementById("fe-new-name"),Ce=document.getElementById("fe-new-btn"),Y=document.getElementById("settings-backdrop"),oe=document.getElementById("settings-popover"),ae=document.querySelector(".sidebar");function Ie(){return window.matchMedia("(max-width: 640px)").matches}function Z(){Ie()&&ae.classList.add("collapsed")}function le(){ae.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{le(),ce("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{le(),ce("files")});function ce(e){ne=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"?S():De()}async function S(){r.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=t.recent||[];for(const i of s){const o=document.createElement("div");o.className="session-item"+(i.name===u&&!b?" active":""),o.setAttribute("data-session",i.name),o.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
+ <span>${d(i.name)}</span>
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
+ `,o.addEventListener("click",a=>{a.target.closest(".session-edit-btn")||R(i.name)}),r.appendChild(o);const c=o.querySelector(".session-edit-btn");c&&c.addEventListener("click",a=>{a.stopPropagation(),ze(i.name,c)})}const l=t.agents||[];for(const i of l){if(!i.sessions||i.sessions.length===0)continue;const o=document.createElement("div");o.className="sidebar-section-label"+(i.online?"":" agent-offline-label"),o.textContent=i.agentName+(i.online?"":" (offline)"),r.appendChild(o);for(const c of i.sessions){const a=document.createElement("div");a.className="session-item"+(c.name===u&&b===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",v=>{v.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}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 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&&b===(n||null))){H.style.display="none",B.style.display="none",y.style.display="",y.classList.add("terminal-pending"),Z(),p&&(K(),p.destroy(),p=null),u=e,b=n||null,window.__tmuxWebScopeAgent=b;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===b);try{p=await we(y,e,{terminal:M.terminal,scrollback:M.scrollback,theme:M.theme,renderer:M.renderer,wsBase:b?"/ws/a/"+encodeURIComponent(b):M.wsBase||"/ws"}),he()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);y.textContent="Failed to open terminal for "+e+": "+s,y.classList.remove("terminal-pending")}}}let G=[],f=null,de=!1;function xe(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 Me(e){de=!0;try{const n=await fetch(g("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{de=!1}}function Se(){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
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>
6
- ${c(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}p.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&C&&f&&(e.stopPropagation(),fe())});const H=document.getElementById("header-git"),ce=document.getElementById("git-diff-view"),de=document.getElementById("gd-path"),G=document.getElementById("gd-status"),z=document.getElementById("gd-content"),Me=document.getElementById("gd-back");let C=null;function $(e){if(!e||!e.branch){H.style.display="none",C=null;return}C=e.repoRoot;const{branch:t,linesAdded:n,linesRemoved:s}=e;let i=`<span class="branch">${c(t)}</span>`;(n>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),H.innerHTML=i,H.style.display="block"}H.addEventListener("click",e=>{C&&f&&(e.stopPropagation(),fe())});const m=document.getElementById("git-popover"),O=document.getElementById("git-popover-backdrop");function Y(){const e=H.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function re(){const e=C,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(U(),e){ee="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const n=document.getElementById("mode-files");n&&n.classList.add("active"),w(e)}})}let pe="";function fe(){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
- <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){const s=e[n],i=s.additions||0,a=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}t+=`<div class="git-popover-item" data-idx="${n}">
8
- ${o}<span class="file-path">${c(s.path)}</span>
9
- <span class="file-add">${i>0?"+"+i:""}</span>
10
- <span class="file-del">${a>0?"-"+a:""}</span>
11
- </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',pe=t,m.innerHTML=t,m.querySelectorAll(".git-popover-item").forEach(n=>{const s=parseInt(n.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>me(i.path)))})}Y(),m.classList.add("open"),O.classList.add("open"),re()}function Te(){return window.innerWidth>=1024}function ue(e,t){const s=(e+(t&&e?`
12
- `:"")+t).split(`
13
- `);let i="",a=0,o=0;for(const d of s){const l=d;if(l.startsWith("@@")){const y=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);y&&(a=parseInt(y[1],10)-1,o=parseInt(y[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${c(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${c(l)}</span></div>`);continue}if(l.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${c(l)}</span></div>`;else if(l.startsWith("-"))a++,i+=`<div class="diff-line del"><span class="ln-no">${a}</span><span class="ln-body">${c(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${c(l)}</span></div>`;else{if(!l&&!a&&!o)continue;a++,o++,i+=`<div class="diff-line"><span class="ln-no">${a}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${c(l)}</span></div>`}}return{html:i,lineCount:s.length}}let q=null;async function me(e){const t=C;if(t)if(U(),Te()){v.style.display="none",r&&(_(),r.destroy(),r=null,q=u,u=null),$(null),M.style.display="none",E.style.display="none",ce.style.display="flex",de.textContent=e,G.textContent="Loading\u2026",z.innerHTML="";try{const n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());const s=await n.json();He(e,s.diff,s.stagedDiff)}catch(n){const s=n instanceof Error?n.message:String(n);G.textContent="Error",z.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${c(s)}</div>`}}else{m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
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(),me())});const N=document.getElementById("header-git"),re=document.getElementById("git-diff-view"),pe=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Te=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 l=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),N.innerHTML=l,N.style.display="block"}N.addEventListener("click",e=>{T&&f&&(e.stopPropagation(),me())});const m=document.getElementById("git-popover"),V=document.getElementById("git-popover-backdrop");function Q(){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 fe(){const e=T,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(_(),e){ne="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 ue="";function me(){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
+ <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],l=s.additions||0,i=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
+ ${o}<span class="file-path">${d(s.path)}</span>
10
+ <span class="file-add">${l>0?"+"+l:""}</span>
11
+ <span class="file-del">${i>0?"-"+i:""}</span>
12
+ </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ue=n,m.innerHTML=n,m.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),l=e[s];!l||l.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>ge(l.path)))})}Q(),m.classList.add("open"),V.classList.add("open"),fe()}function He(){return window.innerWidth>=1024}function ve(e,n){const s=(e+(n&&e?`
13
+ `:"")+n).split(`
14
+ `);let l="",i=0,o=0;for(const c of s){const a=c;if(a.startsWith("@@")){const v=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);v&&(i=parseInt(v[1],10)-1,o=parseInt(v[2],10)-1),l+=`<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")&&(l+=`<div class="diff-line header"><span class="ln-body">${d(a)}</span></div>`);continue}if(a.startsWith("+"))o++,l+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("-"))i++,l+=`<div class="diff-line del"><span class="ln-no">${i}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("\\"))l+=`<div class="diff-line"><span class="ln-body">${d(a)}</span></div>`;else{if(!a&&!i&&!o)continue;i++,o++,l+=`<div class="diff-line"><span class="ln-no">${i}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(a)}</span></div>`}}return{html:l,lineCount:s.length}}let q=null;async function ge(e){const n=T;if(n)if(_(),He()){y.style.display="none",p&&(K(),p.destroy(),p=null,q=u,u=null),D(null),H.style.display="none",B.style.display="none",re.style.display="flex",pe.textContent=e,O.textContent="Loading\u2026",U.innerHTML="";try{const t=await fetch(g("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();Re(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">
14
15
  <button class="diff-back">\u2190 Back</button>
15
- <span class="diff-filename">${c(e)}</span>
16
+ <span class="diff-filename">${d(e)}</span>
16
17
  <span class="diff-loading">Loading\u2026</span>
17
- </div></div>`,Y(),m.classList.add("open"),O.classList.add("open");try{const n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());const s=await n.json();Se(e,s.diff,s.stagedDiff)}catch(n){const s=n instanceof Error?n.message:String(n);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
18
+ </div></div>`,Q(),m.classList.add("open"),V.classList.add("open");try{const t=await fetch(g("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();$e(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
19
  <button class="diff-back">\u2190 Back</button>
19
- <span class="diff-filename">${c(e)}</span>
20
- </div><div class="diff-empty">Error loading diff: ${c(s)}</div></div>`}}}function Se(e,t,n){if(!t&&!n){m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
20
+ <span class="diff-filename">${d(e)}</span>
21
+ </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function $e(e,n,t){if(!n&&!t){m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
21
22
  <button class="diff-back">\u2190 Back</button>
22
- <span class="diff-filename">${c(e)}</span>
23
- </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=ue(t,n);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
+ <span class="diff-filename">${d(e)}</span>
24
+ </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:l}=ve(n,t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
25
  <button class="diff-back">\u2190 Back</button>
25
- <span class="diff-filename">${c(e)}</span>
26
- <span class="diff-filesize">${i} lines</span>
27
- </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=pe,Y(),f&&m.querySelectorAll(".git-popover-item").forEach(a=>{const o=parseInt(a.dataset.idx||"0",10),d=f.files[o];!d||d.status==="D"||(a.style.cursor="pointer",a.addEventListener("click",()=>me(d.path)))}),re()})}function He(e,t,n){if(!t&&!n){G.textContent="No diff",z.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}=ue(t,n);de.textContent=e,G.textContent=i+" lines",z.innerHTML=s}function $e(){ce.style.display="none",q?(v.style.display="",v.classList.add("terminal-pending"),A(q),q=null):u?(v.style.display="",v.classList.add("terminal-pending"),ye()):E.style.display="flex"}Me.addEventListener("click",$e);function U(){m.classList.remove("open"),O.classList.remove("open")}O.addEventListener("click",U),document.addEventListener("keydown",e=>{e.key==="Escape"&&(U(),J())});async function ve(e){try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(n.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(n.path));if(s.ok){const i=await s.json();f=i,$(i);return}}}catch{}f=null,$(null)}let V=null;function ye(){_(),u&&(ve(u),V=setInterval(()=>{u&&ve(u)},3e4))}function _(){V&&(clearInterval(V),V=null)}function Re(){if(W=I.fsRoots||[],W.length===0){if(u){De(u);return}p.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}p.innerHTML="";for(const e of W){const t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${c(e)}`,t.addEventListener("click",()=>w(e)),p.appendChild(t)}}async function De(e){p.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();n.path?w(n.path):p.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{p.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function w(e){L=e,p.innerHTML='<div class="file-tree-empty">Loading...</div>';const t=Ce(e);try{const s=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();await t,p.innerHTML="",$(f),p.innerHTML+=Ie();const i=Ne(e);if(i&&!W.some(d=>e===d)){const d=document.createElement("div");d.className="file-tree-item",d.innerHTML="..",d.addEventListener("click",()=>w(i)),p.appendChild(d)}if(s.dirs)for(const o of s.dirs){const d=o.split("/").pop()||o.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.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> ${c(d)}`,l.addEventListener("click",()=>w(o)),p.appendChild(l)}else p.innerHTML="";const a=s.files||[];for(const o of a){const d=o.split("/").pop()||o.split("\\").pop(),l=xe(o);let y="",x="";if(l)switch(l){case"M":y="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":y="git-add",x='<span class="git-file-status A">A</span>';break;case"D":y="git-del",x='<span class="git-file-status D">D</span>';break;case"?":y="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":y="git-mod",x='<span class="git-file-status R">R</span>';break}const F=document.createElement("div");F.className="file-tree-item file"+(y?" "+y:""),F.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${c(d)}`,F.addEventListener("click",()=>Fe(o)),p.appendChild(F)}}catch{p.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Ne(e){const t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Fe(e){P=e,v.style.display="none",r&&(_(),r.destroy(),r=null,u=null),$(null),X(),E.style.display="none",M.style.display="flex",Le.textContent=e,g.textContent="Loading...",k.style.display="none";try{const t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){const s=await t.json();g.textContent=s.error||"Failed to load",T.value="";return}const n=await t.json();T.value=n.content,j=n.content,te=n.content,g.textContent=n.size+" bytes",k.style.display=""}catch{g.textContent="Failed to load",T.value=""}}T.addEventListener("input",()=>{j=T.value}),k.addEventListener("click",async()=>{k.textContent="Saving...",k.disabled=!0;try{const e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:P,content:j})});if(!e.ok){const t=await e.json();g.textContent=t.error||"Save failed";return}te=j,g.textContent="Saved"}catch{g.textContent="Save failed"}finally{k.disabled=!1,k.textContent="Save"}}),we.addEventListener("click",async()=>{if(confirm("Delete "+P+"?"))try{const e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:P})});if(!e.ok){const t=await e.json();g.textContent=t.error||"Delete failed";return}M.style.display="none",E.style.display="flex",L&&w(L)}catch{g.textContent="Delete failed"}}),Ee.addEventListener("click",()=>{M.style.display="none",E.style.display="flex",L&&w(L)}),ke.addEventListener("click",async()=>{const e=ne.value.trim();if(!e)return;const t=L+"/"+e;try{const n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){const s=await n.json();g.textContent=s.error||"Create failed";return}ne.value="",L&&w(L)}catch{g.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=se.classList.toggle("open");K.classList.toggle("open",e)}),K.addEventListener("click",()=>{se.classList.remove("open"),K.classList.remove("open")});let B="";const R=document.createElement("div");R.className="session-popover-backdrop",document.body.appendChild(R);const b=document.createElement("div");b.id="session-popover",b.className="session-popover",b.style.display="none",b.innerHTML=`<div class="sp-field">
26
+ <span class="diff-filename">${d(e)}</span>
27
+ <span class="diff-filesize">${l} lines</span>
28
+ </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ue,Q(),f&&m.querySelectorAll(".git-popover-item").forEach(i=>{const o=parseInt(i.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(i.style.cursor="pointer",i.addEventListener("click",()=>ge(c.path)))}),fe()})}function Re(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:l}=ve(n,t);pe.textContent=e,O.textContent=l+" lines",U.innerHTML=s}function Ne(){re.style.display="none",q?(y.style.display="",y.classList.add("terminal-pending"),R(q),q=null):u?(y.style.display="",y.classList.add("terminal-pending"),he()):B.style.display="flex"}Te.addEventListener("click",Ne);function _(){m.classList.remove("open"),V.classList.remove("open")}V.addEventListener("click",_),document.addEventListener("keydown",e=>{e.key==="Escape"&&(_(),X())});async function ye(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 l=await s.json();f=l,D(l);return}}}catch{}f=null,D(null)}let J=null;function he(){K(),u&&(ye(u),J=setInterval(()=>{u&&ye(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function De(){if(G=M.fsRoots||[],G.length===0){if(u){Ae(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 G){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 Ae(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(g("/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=Me(e);try{const s=await(await fetch(g("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",D(f),r.innerHTML+=Se();const l=Fe(e);if(l&&!G.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>k(l)),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 i=s.files||[];for(const o of i){const c=o.split("/").pop()||o.split("\\").pop(),a=xe(o);let v="",x="";if(a)switch(a){case"M":v="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":v="git-add",x='<span class="git-file-status A">A</span>';break;case"D":v="git-del",x='<span class="git-file-status D">D</span>';break;case"?":v="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":v="git-mod",x='<span class="git-file-status R">R</span>';break}const j=document.createElement("div");j.className="file-tree-item file"+(v?" "+v:""),j.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${d(c)}`,j.addEventListener("click",()=>Pe(o)),r.appendChild(j)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Fe(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function Pe(e){z=e,y.style.display="none",p&&(K(),p.destroy(),p=null,u=null),D(null),Z(),B.style.display="none",H.style.display="flex",Ee.textContent=e,h.textContent="Loading...",C.style.display="none";try{const n=await fetch(g("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();h.textContent=s.error||"Failed to load",$.value="";return}const t=await n.json();$.value=t.content,W=t.content,se=t.content,h.textContent=t.size+" bytes",C.style.display=""}catch{h.textContent="Failed to load",$.value=""}}$.addEventListener("input",()=>{W=$.value}),C.addEventListener("click",async()=>{C.textContent="Saving...",C.disabled=!0;try{const e=await fetch(g("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:z,content:W})});if(!e.ok){const n=await e.json();h.textContent=n.error||"Save failed";return}se=W,h.textContent="Saved"}catch{h.textContent="Save failed"}finally{C.disabled=!1,C.textContent="Save"}}),ke.addEventListener("click",async()=>{if(confirm("Delete "+z+"?"))try{const e=await fetch(g("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:z})});if(!e.ok){const n=await e.json();h.textContent=n.error||"Delete failed";return}H.style.display="none",B.style.display="flex",E&&k(E)}catch{h.textContent="Delete failed"}}),Be.addEventListener("click",()=>{H.style.display="none",B.style.display="flex",E&&k(E)}),Ce.addEventListener("click",async()=>{const e=ie.value.trim();if(!e)return;const n=E+"/"+e;try{const t=await fetch(g("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();h.textContent=s.error||"Create failed";return}ie.value="",E&&k(E)}catch{h.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=oe.classList.toggle("open");Y.classList.toggle("open",e)}),Y.addEventListener("click",()=>{oe.classList.remove("open"),Y.classList.remove("open")});let I="";const A=document.createElement("div");A.className="session-popover-backdrop",document.body.appendChild(A);const w=document.createElement("div");w.id="session-popover",w.className="session-popover",w.style.display="none",w.innerHTML=`<div class="sp-field">
28
29
  <label>Rename session</label>
29
30
  <input type="text" id="sp-name" />
30
31
  </div>
@@ -33,5 +34,5 @@ import{initTerminal as be}from"./terminal-core.js";const I=window.__TMUX_WEB_SHE
33
34
  <button class="btn" id="sp-cancel">Cancel</button>
34
35
  </div>
35
36
  <hr class="sp-divider" />
36
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(b);function Pe(e){const t=e.getBoundingClientRect();b.style.top=t.bottom+4+"px",b.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function je(e,t){B=e;const n=document.getElementById("sp-name");n.value=e,R.classList.add("open"),b.style.display="block",Pe(t),setTimeout(()=>n.focus(),50)}function D(){R.classList.remove("open"),b.style.display="none"}R.addEventListener("click",D),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===B){D();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:B,newName:e})})).ok)return;D(),S(),u===B&&A(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+B+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B})})).ok)return;D(),u===B&&(r&&(r.destroy(),r=null),u=null,v.style.display="none",E.style.display="flex"),S()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",D);const Ae={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 t=e.target.closest("button[data-key]");if(!t||!r)return;const n=Ae[t.dataset.key];n&&(r.sendInput(n),r.focus())});const h=document.getElementById("mk-input"),ge=document.getElementById("mk-send");function he(){if(!h||!r)return;const e=h.value;e&&(r.sendInput(e+"\r"),h.value="",h.rows=1,r.focus())}if(h&&(h.addEventListener("input",()=>{h.rows=1;const e=h.value.split(`
37
- `).length;h.rows=Math.min(e,4)}),h.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),he())})),ge&&ge.addEventListener("click",he),window.visualViewport){const e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const Z=document.getElementById("process-panel"),N=document.getElementById("proc-list"),We=document.getElementById("proc-close"),Q=document.getElementById("proc-backdrop"),Ge=document.getElementById("header-ram");function ze(){N.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){N.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(const n of e){const s=n.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+c(n.command)+'">'+c(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}N.innerHTML=t;for(const n of N.querySelectorAll(".proc-row")){const s=n.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const a=i.target.closest(".proc-row");!a||!confirm("Kill PID "+a.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(a.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?a.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{N.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Oe(){Z.classList.add("open"),Q.classList.add("open"),ze()}function J(){Z.classList.remove("open"),Q.classList.remove("open")}Ge.addEventListener("click",e=>{e.stopPropagation(),Z.classList.contains("open")?J():Oe()}),We.addEventListener("click",J),Q.addEventListener("click",J),window.__openSession=A,window.__refreshSidebar=S,S(),X();
37
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(w);function je(e){const n=e.getBoundingClientRect();w.style.top=n.bottom+4+"px",w.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function ze(e,n){I=e;const t=document.getElementById("sp-name");t.value=e,A.classList.add("open"),w.style.display="block",je(n),setTimeout(()=>t.focus(),50)}function F(){A.classList.remove("open"),w.style.display="none"}A.addEventListener("click",F),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===I){F();return}try{if(!(await fetch(g("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:I,newName:e})})).ok)return;F(),S(),u===I&&R(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+I+'"?'))try{if(!(await fetch(g("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:I})})).ok)return;F(),u===I&&(p&&(p.destroy(),p=null),u=null,y.style.display="none",B.style.display="flex"),S()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",F);const We={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=We[n.dataset.key];t&&(p.sendInput(t),p.focus())});const L=document.getElementById("mk-input"),be=document.getElementById("mk-send");function Le(){if(!L||!p)return;const e=L.value;e&&(p.sendInput(e+"\r"),L.value="",L.rows=1,p.focus())}if(L&&(L.addEventListener("input",()=>{L.rows=1;const e=L.value.split(`
38
+ `).length;L.rows=Math.min(e,4)}),L.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Le())})),be&&be.addEventListener("click",Le),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 ee=document.getElementById("process-panel"),P=document.getElementById("proc-list"),Ge=document.getElementById("proc-close"),te=document.getElementById("proc-backdrop"),Oe=document.getElementById("header-ram");function Ue(){P.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(g("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){P.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,l=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">'+l+'</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>'}P.innerHTML=n;for(const t of P.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",l=>{const i=l.target.closest(".proc-row");!i||!confirm("Kill PID "+i.dataset.pid+"?")||fetch(g("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(i.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?i.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{P.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Ve(){ee.classList.add("open"),te.classList.add("open"),Ue()}function X(){ee.classList.remove("open"),te.classList.remove("open")}Oe.addEventListener("click",e=>{e.stopPropagation(),ee.classList.contains("open")?X():Ve()}),Ge.addEventListener("click",X),te.addEventListener("click",X),window.__openSession=R,window.__refreshSidebar=S,S(),Z(),setInterval(()=>{document.visibilityState==="visible"&&S()},1e4);