tmux-weblink 2.2.25 → 2.2.26

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
@@ -2,24 +2,172 @@
2
2
 
3
3
  **English** | [中文](README.zh-CN.md)
4
4
 
5
- Access your tmux sessions from the browser. A lightweight web server that lists running tmux sessions and lets you attach through a full terminal in your browser — with built-in notes and a sidebar.
5
+ Access your tmux sessions from the browser. A lightweight web server that lists running tmux sessions and lets you attach through a full terminal in your browser — with built-in notes, a file browser, and a touch-friendly UI for both mobile and desktop.
6
6
 
7
- ## Install
7
+ ## Features
8
+
9
+ - **Full terminal**: xterm.js rendering, 256 colors, copy/paste, paste images straight into CLI tools
10
+ - **Touch optimized**: inertial scrollback, tap-to-page left half, drag-select auto-copy, virtual-key toolbar
11
+ - **PWA**: "Add to Home Screen" on phones, "Install" in desktop browsers → standalone window app
12
+ - **Sidebar grouping**: sessions auto-clustered by working directory (folder-name headers, collapsible)
13
+ - **New Session directory-tree picker**: expand down to any directory, including hidden ones
14
+ - **Notes**: per-session Markdown scratchpad with auto-save
15
+ - **Files**: browse/edit local files and view git status
16
+ - **Machines federation**: join NAT'd machines to a public hub (no open ports needed), multi-machine terminal matrix
17
+ - **Security**: scrypt-hashed password auth, login rate limiting, audit log
18
+
19
+ ---
20
+
21
+ # Usage Guide (step by step)
22
+
23
+ ## 1. Install and start
8
24
 
9
25
  ```bash
10
26
  npm install -g tmux-weblink
27
+
28
+ # Start (default port 21000)
29
+ tmux-weblink
11
30
  ```
12
31
 
13
- Or run directly with npx:
32
+ Open **http://localhost:21000** in your browser.
14
33
 
15
- ```bash
16
- npx tmux-weblink
17
- ```
34
+ ## 2. First run: set a password and log in
35
+
36
+ The first visit shows a **"Set your password"** page (min 8 characters). The password is stored as a scrypt hash on the machine; from then on you just type it to log in.
37
+
38
+ > Alternative: run `tmux-weblink setup` from the command line to initialize.
39
+
40
+ ## 3. Layout tour
41
+
42
+ - **Sidebar (Sessions mode, default)**:
43
+ - **`+ New Session`** button on top — create a new tmux session
44
+ - Session list **grouped by working directory** (folder-name headers, click to collapse). Sessions from the same project stack together.
45
+ - Each session has a colored dot: **green = idle, amber = working** (automatic activity probing, no config)
46
+ - Sessions killed inside tmux move to a greyed-out **"失效会话 / dead sessions"** group (see §8)
47
+ - **Three mode buttons at the bottom**: `Sessions` / `Files` / `Settings`
48
+ - **Main area**: placeholder until you open a session, then the full terminal
49
+
50
+ ## 4. Opening a session & day-to-day operations
51
+
52
+ ### Open a session
53
+
54
+ **Click any session** in the sidebar to attach (a real `tmux attach`, not an emulation). The sidebar collapses to give the terminal the full width.
55
+
56
+ - To switch sessions, click the **tmux-weblink** logo in the top-left to re-open the sidebar, then click another session
57
+ - Attachments are real: windows/panes you open and what other terminals (e.g. a native tmux client) see stay fully in sync
58
+
59
+ ### Copy & paste
60
+
61
+ | Action | How |
62
+ | --- | --- |
63
+ | Copy terminal text | **Drag-select** and release — auto-copies (desktop & mobile); or hold `Shift` while dragging |
64
+ | Paste into terminal | With the terminal focused, just press `Ctrl+V` (`Cmd+V` on Mac) — the page handles it: text is typed in, images are uploaded (grant clipboard permission the first time) |
65
+ | Paste an **image** (≥2.2.24) | Copy an image (screenshot / clipboard image), press `Ctrl+V` in the terminal → the image uploads to `~/.tmux-web/uploads/` and its **file path is typed into the terminal**. The terminal is text-only, so pasting an image = pasting its path — then `cat` or `vim` it |
66
+
67
+ > In an agent session (Machines), pasted images land on the **agent's machine**, so the path is valid there.
68
+
69
+ ### Touch screens (phones / tablets)
70
+
71
+ The terminal is split in two zones — the **left 40% is a paging zone** (split top/bottom by where your finger lands):
72
+
73
+ - **Left 40%** (split top/bottom by where your finger lands):
74
+ - tap/swipe **top half** = page up (PgUp, vim-friendly)
75
+ - tap/swipe **bottom half** = page down (PgDn)
76
+ - A translucent ↑/↓ hint appears; no soft keyboard pops up — tap the terminal body when you want to type
77
+ - **Right 60%**: drag up/down to **scroll terminal history** with inertia, like a normal mobile page
78
+
79
+ ### Virtual-key toolbar (mobile)
80
+
81
+ Tap the input bar at the bottom for a toolbar with `ESC` / `Tab` / `Shift+Tab` / arrow keys — the keys soft keyboards lack.
82
+
83
+ ### Switch tmux windows from the header
84
+
85
+ Multi-window sessions show a **window drawer** at the terminal header (a mobile-friendly tab picker). Tap to switch tmux windows — no need to remember `Ctrl+B n` keybindings.
86
+
87
+ ### Quick Commands
88
+
89
+ Open **`/quick-commands`**: save frequent commands as cards (title + command + optional target session) and run them with one click. Great for "restart the service" or "run the tests".
90
+
91
+ ## 5. Creating a session (New Session)
18
92
 
19
- ## Usage
93
+ Click **`+ New Session`** in the sidebar:
94
+
95
+ 1. **Session name**: e.g. `myproject`
96
+ 2. **Start directory** — either:
97
+ - type a path and pick from the live **autocomplete** (typing `~/.cl` suggests `~/.claude`), or
98
+ - click the folder button (📁) to open a **directory tree**; expand down to your target — it auto-expands to the current session's directory, and **hidden directories (`.claude`, `.config`, …) are visible** (≥2.2.25, sorted after regular ones)
99
+ 3. Click **Create** — the session starts and you're attached
100
+
101
+ > Leave the directory empty to start in `$HOME` and `cd` later — the tree is a convenience, not a requirement.
102
+
103
+ ## 6. Notes & Files
104
+
105
+ ### Notes (per-session scratchpad)
106
+
107
+ Click the **notepad icon** in the session header to open that session's Markdown pad; it **auto-saves as you type** into `~/.tmux-web/db.json`. Re-open the session later and the note is still there. For a bigger surface / export use `/notes/<session>` or the global note at `/notes/__global__`.
108
+
109
+ ### Files (browse/edit + git)
110
+
111
+ Switch to **Files** mode at the bottom:
112
+
113
+ - **Default** (no config): with a session open, Files browses that session's **working directory**
114
+ - **Configured** (`TMUX_WEB_FS_ROOTS=/path1:/path2`): shows multiple root entries
115
+ - Click folders to descend, click a file to open it in the editor, **Save** to write; `..` goes up
116
+ - Git repos show the **branch name** and per-file change markers; git diffs are viewable from the header/sidebar
117
+ - New file: type a name in the bottom field and hit **New File**
118
+
119
+ Security: paths must resolve inside a configured root (single file ≤1 MiB by default); traversal like `../../../etc/passwd` is rejected.
120
+
121
+ ## 7. Machines (bring NAT'd machines in)
122
+
123
+ One **hub** (publicly reachable) + any number of machines stuck behind NAT. Every machine runs the same package and the same command — only the role differs:
124
+
125
+ 1. **On the hub**: open `http://<hub>:21000/settings/machines`, create an agent token and copy it (one-shot; it disappears once you leave the page)
126
+ 2. **On the remote machine** (e.g. your home/office LAN box): start the server, then open *its own* `http://localhost:21000/settings/machines`, paste hub URL + token, hit **Save & Connect**
127
+ 3. **Back on the hub's browser page**: a group named after the machine appears in the sidebar — click into its sessions exactly like local ones. Input, touch, and image paste all travel through an encrypted tunnel
128
+
129
+ Notes:
130
+
131
+ - The agent client runs **in-process** next to the normal server; the remote machine opens no ports
132
+ - **When the hub is upgraded, upgrade the remote agents too** (agent-side logic evolves with releases)
133
+ - Hard-refresh the browser after front-end changes (see §9)
134
+
135
+ ## 8. Sessions killed in tmux (dead sessions / tombstones)
136
+
137
+ When you `kill-session` a session in tmux, the list doesn't throw it away — it moves to the greyed-out **dead sessions** group, keeping its place and path.
138
+
139
+ Click it to open an action panel:
140
+
141
+ - **Rebuild (same name & path)** — relaunch the session in its original working directory (the common case)
142
+ - **Rename (record only)** — change the record, not tmux
143
+ - **Delete the dead record** — forget it entirely
144
+
145
+ ## 9. Installing as an app
146
+
147
+ - **iOS**: Safari share → **Add to Home Screen** (fullscreen standalone)
148
+ - **Android / Chrome**: menu → **Add to Home Screen** / **Install app**
149
+ - **Desktop browsers** (Linux/Windows/macOS): click the **Install** icon in the Chrome/Edge/Chromium address bar for a standalone window
150
+ - Note: plain-HTTP LAN IPs won't show the install prompt (browsers require a secure context); `localhost` or HTTPS both work
151
+
152
+ ## 10. Troubleshooting
153
+
154
+ | Symptom | Fix |
155
+ | --- | --- |
156
+ | Page doesn't show new features after an upgrade | **Hard-refresh**: `Ctrl+Shift+R` (Mac `Cmd+Shift+R`). Front-end JS is served at page load; already-open pages don't update themselves |
157
+ | Agent sessions still behave old after hub upgrade | Upgrade the **remote agent** package to the new version too |
158
+ | Pasting an image does nothing | Need ≥2.2.24; make sure `Ctrl+V` in the terminal and grant clipboard permission once |
159
+ | Hidden dirs like `.claude` missing in the tree | Need ≥2.2.25 (hidden entries sort at the end of listings) |
160
+ | Port taken | `PORT=8080 tmux-weblink` |
161
+ | Forgot your password | Re-run `tmux-weblink setup` on the machine |
162
+ | Running under systemd | The unit **must** set `KillMode=process` (the default control-group mode would kill the tmux server on restart — see [docs/index.md](docs/index.md)) |
163
+ | Server won't run natively on Windows | Run the server inside **WSL2**; the Windows browser works fine as a client (point it at WSL2 or a remote hub) |
164
+
165
+ ---
166
+
167
+ # Command reference
20
168
 
21
169
  ```bash
22
- # Interactive setup (agents page)
170
+ # Interactive setup (set password, etc.)
23
171
  tmux-weblink setup
24
172
 
25
173
  # Start on default port 21000
@@ -28,9 +176,6 @@ tmux-weblink
28
176
  # Custom port
29
177
  PORT=8080 tmux-weblink
30
178
 
31
- # Use the default xterm.js renderer
32
- tmux-weblink
33
-
34
179
  # Optional: tail-first buffer loading (see docs/architecture.md)
35
180
  TMUX_WEB_INITIAL_LINES=1000 TMUX_WEB_HISTORY_CHUNK=500 tmux-weblink
36
181
 
@@ -38,37 +183,51 @@ TMUX_WEB_INITIAL_LINES=1000 TMUX_WEB_HISTORY_CHUNK=500 tmux-weblink
38
183
  tmux-weblink agent --hub wss://hub.example.com --token <agent-token> --name laptop
39
184
  ```
40
185
 
41
- > **Every machine uses the same start command.** To join a hub, open
42
- > `http://localhost:21000/settings/machines` on the machine you want to
43
- > attach, paste the hub URL + token, and press **Save & Connect** the agent
44
- > client runs in-process next to the normal server. The hub's own
45
- > `/settings/machines` page creates/revokes tokens.
186
+ > **Every machine runs the same command.** To join a hub without the CLI, start the
187
+ > server and open its own `http://localhost:21000/settings/machines`, paste the hub
188
+ > URL + token, and press **Save & Connect**. The hub's `/settings/machines` page
189
+ > creates/revokes tokens.
46
190
 
47
- Then open `http://localhost:21000` in your browser. You'll see a list of active tmux sessions — click one to attach.
191
+ # Environment variables
48
192
 
49
- ## Documentation
193
+ | Variable | Default | Description |
194
+ | --- | --- | --- |
195
+ | `PORT` | `21000` | HTTP port |
196
+ | `TMUX_WEB_MODE` | `production` | `development` uses a dev data directory |
197
+ | `TMUX_WEB_INITIAL_LINES` | — | Read the last N lines first when opening a session |
198
+ | `TMUX_WEB_HISTORY_CHUNK` | — | History chunk size for batched loading |
199
+ | `TMUX_WEB_AGENT_HUB` / `TMUX_WEB_AGENT_TOKEN` / `TMUX_WEB_AGENT_NAME` | — | Agent connection parameters |
200
+ | `TMUX_WEB_FS_ROOTS` | — | File-browsing roots (colon-separated). Unset = Files browses the open session's working directory |
201
+ | `TMUX_WEB_MAX_IMAGE_UPLOAD_BYTES` | 10 MiB | Image-paste upload size cap |
50
202
 
51
- - [Documentation hub](docs/index.md)
52
- - [Notes](docs/notes.md) — per-session Markdown scratchpad
53
- - **Windows drawer** — switch tmux windows from the terminal header (mobile-friendly tab picker)
54
- - [Machines / Agents](docs/agents.md) — join NAT'd machines to a public hub
55
- - [Architecture](docs/architecture.md) — how the server, terminal, and sidebar connect
203
+ # More documentation
56
204
 
57
- ## Prerequisites
205
+ - [Documentation hub](docs/index.md) (incl. systemd service setup)
206
+ - [Notes](docs/notes.md)
207
+ - [Files API / security](docs/files.md)
208
+ - [Machines / Agents](docs/agents.md)
209
+ - [Architecture](docs/architecture.md)
210
+
211
+ # Prerequisites
58
212
 
59
213
  - **Node.js** >= 22
60
- - **tmux** installed and available in your PATH
61
- - Writable `~/.tmux-web/` and `~/.config/tmux-web/` (see [docs](docs/index.md) for dev-mode paths)
214
+ - **tmux** installed and in your PATH
215
+ - Writable `~/.tmux-web/` and `~/.config/tmux-web/` (dev-mode paths: see [docs](docs/index.md))
216
+
217
+ # Windows notes
218
+
219
+ - The server doesn't run natively well on Windows (node-pty lacks a linux-x64-style prebuild there; requires source compilation — **WSL2** is recommended)
220
+ - The browser client (PWA / web) works fine on Windows, against WSL2 locally or a remote hub
62
221
 
63
- ## Credits
222
+ # Credits
64
223
 
65
- This project is built on top of ideas and code from:
224
+ Built on ideas and code from:
66
225
 
67
226
  - [tmux-web](https://github.com/ashutoshpw/tmux-web) by [@ashutoshpw](https://github.com/ashutoshpw)
68
227
  - [persalink](https://github.com/brobata/persalink) by [@brobata](https://github.com/brobata)
69
228
 
70
- It is a continuation and re-packaging of those experiments, focused on making a single-person, browser-first tmux companion that is easy to install and run.
229
+ A continuation and re-packaging of those experiments, focused on a single-user, browser-first tmux companion that's easy to install and run.
71
230
 
72
- ## License
231
+ # License
73
232
 
74
233
  MIT
package/README.zh-CN.md CHANGED
@@ -2,37 +2,174 @@
2
2
 
3
3
  [English](README.md) | **中文**
4
4
 
5
- 在浏览器里访问你的 tmux 会话。一个轻量级 Web 服务器:列出正在运行的 tmux 会话,并让你通过浏览器里的完整终端接入——内置笔记与侧边栏,移动端与桌面端都能用。
5
+ 在浏览器里访问你的 tmux 会话。一个轻量级 Web 服务器:列出正在运行的 tmux 会话,并让你通过浏览器里的完整终端接入——内置笔记与文件浏览,移动端与桌面端都能用。
6
6
 
7
7
  ## 特性
8
8
 
9
9
  - **完整终端**:xterm.js 渲染,支持 256 色、复制粘贴、粘贴图片到 CLI 工具
10
- - **触摸优化**:移动端惯性滚动、Shift+拖选松手自动复制、虚拟键盘工具栏
10
+ - **触摸优化**:移动端惯性回看历史、左半屏点按翻页、拖选自动复制、虚拟键盘工具栏
11
11
  - **PWA**:手机"添加到主屏幕"、桌面浏览器"安装应用"即为独立窗口应用
12
12
  - **侧边栏会话分组**:按工作目录自动聚类(显示文件夹名),可折叠
13
- - **New Session 目录树选择器**:层层展开目录树,直接定位到当前会话所在路径
14
- - **笔记(Notes)**:每个会话独立的 Markdown 便签
15
- - **文件浏览/编辑(Files)**:浏览、编辑本地文件(默认仅本机,路径受限)
13
+ - **New Session 目录树选择器**:层层展开目录树,直接定位到当前会话所在路径(含隐藏目录)
14
+ - **笔记(Notes)**:每个会话独立的 Markdown 便签,自动保存
15
+ - **文件浏览/编辑(Files)**:浏览、编辑本地文件与 git 状态
16
16
  - **Machines 联邦**:把 NAT 后的机器加入公共 hub(hub 无需开放端口),多机终端矩阵
17
- - **窗口抽屉**:在终端头部切换 tmux 窗口(移动端友好的标签选择器)
18
- - **安全**:scrypt 哈希 token 认证、登录限流、审计日志
17
+ - **安全**:scrypt 哈希密码认证、登录限流、审计日志
19
18
 
20
- ## 安装
19
+ ---
20
+
21
+ # 使用教程(手把手)
22
+
23
+ ## 1. 安装与启动
21
24
 
22
25
  ```bash
23
26
  npm install -g tmux-weblink
27
+
28
+ # 启动(默认端口 21000)
29
+ tmux-weblink
24
30
  ```
25
31
 
26
- 或者直接用 npx 运行:
32
+ 打开浏览器访问 **http://localhost:21000**。
27
33
 
28
- ```bash
29
- npx tmux-weblink
30
- ```
34
+ ## 2. 第一次使用:设置密码并登录
35
+
36
+ 第一次访问会看到 **"Set your password"(设置密码)** 页面(要求至少 8 位)。设置后密码以 scrypt 哈希保存在本机,以后每次打开都输入同一个密码即可。
37
+
38
+ > 也可以用命令行初始化:`tmux-weblink setup`
39
+
40
+ ## 3. 界面一览
41
+
42
+ 登录后主界面分三块:
43
+
44
+ - **左侧边栏(Sessions 模式,默认)**:
45
+ - 顶部 **`+ New Session`** 按钮——创建新 tmux 会话
46
+ - 下方会话列表,**按工作目录自动分组**(每组一个文件夹标题,可点击折叠)。同目录的多个会话堆在一起,一眼看到"我在哪个项目里开了什么"
47
+ - 每个会话前面的小圆点显示活动状态:**绿色 = 空闲(idle),橘黄 = 正在干活(working)**(后台自动探测,无需配置)
48
+ - 若 session 在 tmux 里被杀掉,会出现在 **"失效会话"** 分组(灰显,见 §9)
49
+ - **底部三个模式按钮**:`Sessions`(会话)/ `Files`(文件)/ `Settings`(设置)
50
+ - **右侧主区**:点击会话前的占位提示;接入终端后显示完整终端
51
+
52
+ ## 4. 接入终端 & 每天的操作
53
+
54
+ ### 打开一个会话
55
+
56
+ 直接**点击侧边栏里的会话**即可接入(通过 `tmux attach` 接入真实会话,不是模拟)。接入后侧边栏自动收起,终端占满屏幕。
57
+
58
+ - 需要切换会话时,点左上角 **tmux-weblink** 标志展开侧边栏,再点另一个会话
59
+ - 会话是真实 attach:你在 tmux 里开的窗口/面板,以及**别的终端**(如 tmux 原始终端)看到的内容完全同步
60
+
61
+ ### 复制与粘贴
62
+
63
+ | 操作 | 方法 |
64
+ | --- | --- |
65
+ | 复制终端里的文本 | 用鼠标**拖选**,松手即自动复制(桌面 & 移动端通用);或先 `Shift` 再拖选 |
66
+ | 粘贴到终端 | 焦点在终端里直接 `Ctrl+V`(Mac 为 `Cmd+V`)——页面接管:文本 = 直接输入,图片 = 自动上传(首次需放行剪贴板权限) |
67
+ | 粘贴**图片**(≥2.2.24) | 复制一张图片(截图/剪贴板图片),在终端里 `Ctrl+V` 粘贴 → 图片自动上传到本机 `~/.tmux-web/uploads/`,并把**文件路径输入到终端**。终端是文本协议,粘贴图片 = 得到路径,可在 shell 里 `cat` / `vim` 查看 |
68
+
69
+ > 在 agent 会话(Machines)里粘贴图片,文件会传到 **agent 那台机器**上,路径对那台机器有效。
70
+
71
+ ### 触摸屏(手机 / 平板)
72
+
73
+ 终端区域分左右两区,**左手拇指方便操作的左 40% 是翻页区**:
74
+
75
+ - **左 40%**(按手指落点分上下两半)是翻页区:
76
+ - 点/滑 **上半区** = 上翻一页(PgUp,vim 友好)
77
+ - 点/滑 **下半区** = 下翻一页(PgDn)
78
+ - 会弹出半透明提示(↑/↓),不弹软键盘;要输入时点击终端主体即可
79
+ - **右 60%**:上下拖动**回看终端历史**(带惯性),像普通手机页面一样往回翻
80
+
81
+ ### 虚拟键盘工具栏(移动端)
82
+
83
+ 点击屏幕下方输入条弹出工具栏:`ESC` / `Tab` / `Shift+Tab` / 方向键等软键盘没有的键。
84
+
85
+ ### 在终端头部切换 tmux 窗口
86
+
87
+ 多窗口会话顶部有 **窗口抽屉**(移动端友好的标签选择器),点击即切换到对应 tmux 窗口,不用记 `Ctrl+B n` 之类的快捷键。
88
+
89
+ ### 快速命令 Quick Commands
90
+
91
+ 打开 **`/quick-commands`** 页面:把常用命令存成卡片(标题 + 命令 + 可指定在哪个 session 执行),点一下即执行。适合"重启服务"、"跑测试"这类天天要敲的命令。
92
+
93
+ ## 5. 创建新会话(New Session)
94
+
95
+ 侧边栏点 **`+ New Session`**,弹窗里:
96
+
97
+ 1. **Session name**:会话名(字母数字,如 `myproject`)
98
+ 2. **Start directory**:起始目录,两种填法:
99
+ - 直接输入路径,下面会**实时补全匹配**(输入 `~/.cl` 会补全出 `~/.claude`)
100
+ - 点目录树按钮(📁)弹出**目录树**,层层展开选择;树会自动展开到当前会话所在目录,**隐藏目录(`.claude`、`.config` 等)也可见**(≥2.2.25,排在普通目录后面)
101
+ 3. 点 **Create**:创建并自动接入
102
+
103
+ > 不会用目录树也没关系:留空 = 在 home 目录创建,之后在会话里 `cd` 即可。
31
104
 
32
- ## 使用
105
+ ## 6. 笔记 Notes & 文件 Files
106
+
107
+ ### Notes(每个会话一块便签)
108
+
109
+ 会话头部点**记事本图标**,打开该会话的 Markdown 便签,**边写边自动保存**到 `~/.tmux-web/db.json`。下次再打开这个会话,便签还在。想用大编辑区/导出,打开 `/notes/<session名>` 或全局便签 `/notes/__global__`。
110
+
111
+ ### Files(文件浏览/编辑 + git)
112
+
113
+ 点底部 **Files** 模式:
114
+
115
+ - **默认**(未配置时):打开会话后切到 Files,直接浏览**当前会话的工作目录**
116
+ - **配置后**(`TMUX_WEB_FS_ROOTS=/path1:/path2`):显示多个根目录入口
117
+ - 点文件夹逐层进入,点文件在右侧打开编辑器直接改,**Save** 保存;`..` 回上级
118
+ - 目录里有 git 仓库时显示**分支名**,文件旁标注改动状态;顶部/侧栏可看 **git diff**
119
+ - 新建文件:底部输入文件名 + **New File**
120
+
121
+ 安全限制:路径必须落在配置的根目录内(默认单文件 ≤1 MiB),`../../../etc/passwd` 这类越界会被拒绝。
122
+
123
+ ## 7. 多机使用 Machines(把 NAT 后的机器接进来)
124
+
125
+ 一台 **hub**(有公网 IP 或至少你能访问它)+ 任意台**被 NAT 挡住的机器**。所有机器装的是同一个包、跑的是同一条命令,只是角色不同:
126
+
127
+ 1. **hub 上**:打开 `http://<hub>:21000/settings/machines`,点创建 agent token,复制(一次性的,关页就看不到)
128
+ 2. **远端机器**(如家里/公司内网的电脑):安装好并启动后,打开**它本机**的 `http://localhost:21000/settings/machines`,粘贴 hub 地址 + token,点 **Save & Connect**
129
+ 3. 回到 **hub 的浏览器页面**:侧边栏出现该机器的分组(显示机器名),点里面的会话就像操作本机会话一样——输入、触摸、图片粘贴全部走加密隧道
130
+
131
+ 要点:
132
+
133
+ - agent 客户端与普通服务器**同进程**运行,远端机器不需要开放任何端口
134
+ - **hub 升级新版后,远端 agent 也要升级到对应版本**(agent 端逻辑随版本变化)
135
+ - 浏览器端改版后记得**硬刷新**(见 §10)
136
+
137
+ ## 8. 在 tmux 里关掉的会话(失效会话/墓碑)
138
+
139
+ 在 tmux 里 `kill-session` 或关闭了某个会话后,列表不会立刻把它扔掉,而是移到 **"失效会话"** 分组灰显(保留你记得住的位置)。
140
+
141
+ 点它弹出操作面板:
142
+
143
+ - **重建同名同路径 session**:在原工作目录把会话原样拉起来(最常用)
144
+ - **重命名(仅记录)**:只改记录不改 tmux
145
+ - **删除失效记录(墓碑消失)**:彻底忘掉它
146
+
147
+ ## 9. 手机 / 桌面安装成独立应用
148
+
149
+ - **iOS**:Safari 分享 → **添加到主屏幕**,全屏独立运行
150
+ - **Android / Chrome**:菜单 → **添加到主屏幕** / **安装应用**
151
+ - **桌面**(Linux/Windows/macOS 浏览器):Chrome/Edge/Chromium 地址栏右侧点**安装**图标,变成独立窗口应用
152
+ - 注意:内网 HTTP IP 不会出现安装提示(浏览器要求 secure context);本机 `localhost` 或 HTTPS 都可以
153
+
154
+ ## 10. 常见问题 Troubleshooting
155
+
156
+ | 现象 | 解决办法 |
157
+ | --- | --- |
158
+ | 页面功能没更新(缺新按钮/触摸手势不对) | **硬刷新**:`Ctrl+Shift+R`(Mac `Cmd+Shift+R`)。前端 JS 是服务端启动时下发的,服务端升级后**已打开的页面不会自动更新** |
159
+ | 升级后 agent 会话仍旧行为 | 升级 **hub** 后记得升级远端机器上的 agent 到新版本 |
160
+ | 粘贴图片没反应 | 需要 ≥2.2.24;确认终端里 `Ctrl+V`(浏览器剪贴板权限要放行一次) |
161
+ | 目录树里看不到 `.claude` 等隐藏目录 | 需要 ≥2.2.25(隐藏目录排在列表末尾) |
162
+ | 端口被占 | `PORT=8080 tmux-weblink` 换端口 |
163
+ | 忘了密码 | 本机重跑 `tmux-weblink setup` 重置 |
164
+ | 用 systemd 托管 | 配置文件必须带 `KillMode=process`(否则重启服务会连坐杀掉 tmux server,见 [docs/index.md](docs/index.md)) |
165
+ | Windows 上服务端起不来 | 服务端推荐在 **WSL2** 里运行;Windows 浏览器作为客户端完全正常(接 WSL2 或远程 hub) |
166
+
167
+ ---
168
+
169
+ # 命令速查
33
170
 
34
171
  ```bash
35
- # 交互式初始化(创建 token 等)
172
+ # 交互式初始化(设置密码等)
36
173
  tmux-weblink setup
37
174
 
38
175
  # 默认端口 21000 启动
@@ -48,35 +185,11 @@ TMUX_WEB_INITIAL_LINES=1000 TMUX_WEB_HISTORY_CHUNK=500 tmux-weblink
48
185
  tmux-weblink agent --hub wss://hub.example.com --token <agent-token> --name laptop
49
186
  ```
50
187
 
51
- > **每台机器都用同一条启动命令。** 加入 hub:在要接入的机器上打开
52
- > `http://localhost:21000/settings/machines`,粘贴 hub 地址 + token,点
53
- > **Save & Connect**——agent 客户端与普通服务器同进程运行。hub 自己的
54
- > `/settings/machines` 页面负责创建/吊销 token。
55
-
56
- 然后浏览器打开 `http://localhost:21000`,你会看到活跃的 tmux 会话列表——点击即可接入。
57
-
58
- ## 手机端 / PWA
59
-
60
- - **iOS**:Safari 分享 → "添加到主屏幕",全屏独立运行
61
- - **Android / Chrome**:菜单 → "添加到主屏幕" / "安装应用"
62
- - 支持软键盘自动适配、触摸惯性滚动、拖选复制
188
+ > **每台机器都用同一条启动命令。** 加入 hub 也可不开命令行:机器启动后到本机
189
+ > `http://localhost:21000/settings/machines` 粘贴 hub 地址 + token,点 **Save & Connect**。
190
+ > hub 自己的 `/settings/machines` 负责创建/吊销 token。
63
191
 
64
- ## 桌面端(Linux / Windows / macOS)
65
-
66
- 桌面浏览器(Chrome / Edge / Chromium)地址栏右侧点"安装"图标即可生成**独立窗口应用**:
67
-
68
- - 本机 `http://localhost:端口` 或 HTTPS 均可;内网 HTTP IP 不会出现安装提示(非 secure context)
69
- - Windows 上服务端原生运行受限(node-pty 需 WSL2),可用 WSL2 或接入远程 hub 使用
70
-
71
- ## 文档
72
-
73
- - [文档中心](docs/index.md)
74
- - [笔记](docs/notes.md) — 每个会话独立的 Markdown 便签
75
- - [文件](docs/files.md) — 浏览/编辑本地文件
76
- - [Machines / Agents](docs/agents.md) — 把 NAT 机器接入公共 hub
77
- - [架构](docs/architecture.md) — 服务器、终端、侧边栏如何协作
78
-
79
- ## 环境变量(常用)
192
+ # 环境变量(常用)
80
193
 
81
194
  | 变量 | 默认 | 说明 |
82
195
  | --- | --- | --- |
@@ -85,20 +198,29 @@ tmux-weblink agent --hub wss://hub.example.com --token <agent-token> --name lapt
85
198
  | `TMUX_WEB_INITIAL_LINES` | — | 打开会话时先读尾部 N 行 |
86
199
  | `TMUX_WEB_HISTORY_CHUNK` | — | 历史分批加载的块大小 |
87
200
  | `TMUX_WEB_AGENT_HUB` / `TMUX_WEB_AGENT_TOKEN` / `TMUX_WEB_AGENT_NAME` | — | agent 连接参数 |
88
- | `FS_ROOTS` | `~` | 文件浏览可访问的根目录(逗号分隔) |
201
+ | `TMUX_WEB_FS_ROOTS` | | 文件浏览根目录(冒号分隔)。不设时 Files 模式浏览当前会话的工作目录 |
202
+ | `TMUX_WEB_MAX_IMAGE_UPLOAD_BYTES` | 10 MiB | 图片粘贴上传大小上限 |
203
+
204
+ # 更多文档
205
+
206
+ - [文档中心](docs/index.md)(含 systemd 服务配置)
207
+ - [笔记](docs/notes.md)
208
+ - [文件 API / 安全](docs/files.md)
209
+ - [Machines / Agents](docs/agents.md)
210
+ - [架构](docs/architecture.md)
89
211
 
90
- ## 前提条件
212
+ # 前提条件
91
213
 
92
214
  - **Node.js** >= 22
93
215
  - **tmux** 已安装且在 PATH 中
94
216
  - 可写的 `~/.tmux-web/` 和 `~/.config/tmux-web/`(dev 模式路径见 [docs](docs/index.md))
95
217
 
96
- ## Windows 说明
218
+ # Windows 说明
97
219
 
98
220
  - 服务端在 Windows 上原生运行受限(node-pty 在 Windows 无 linux-x64 类 prebuild,需源码编译;推荐 **WSL2** 内运行服务)
99
221
  - 浏览器客户端(PWA / 网页)在 Windows 上完全正常,可接入本机 WSL2 或远程 hub 的会话
100
222
 
101
- ## 致谢
223
+ # 致谢
102
224
 
103
225
  本项目建立在以下项目的想法和代码之上:
104
226
 
@@ -107,6 +229,6 @@ tmux-weblink agent --hub wss://hub.example.com --token <agent-token> --name lapt
107
229
 
108
230
  它是这些实验的延续与重新打包,目标是做一个单用户、浏览器优先、易于安装运行的 tmux 伴侣。
109
231
 
110
- ## 许可证
232
+ # 许可证
111
233
 
112
234
  MIT
@@ -1,26 +1,26 @@
1
1
  var hn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Je(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function fe(e){return e.replace(hn,"")}var Ae;function vn(){return Ae||(document.querySelector("link[data-tmux-web-xterm-css]")?Ae=Promise.resolve():(Ae=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),Ae))}var gn=class _t{terminal;fitAddon;container;constructor(t,s,n){this.container=t,this.terminal=s,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,s,n){let[{Terminal:l},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),vn()]),c=new l({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:s,convertEol:!1,smoothScrollDuration:120,theme:n});return new _t(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(s=>this.terminal.write(t,s))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,s=!1){this.terminal.scrollToLine(Math.max(0,t),s)}scrollLines(t){let s=this.terminal.buffer?.active;this.terminal.scrollLines(t)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let t=yn(this.container);if(t.width<=0||t.height<=0)return!1;let s=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!s?.width||!s?.height)return this.fitAddon.fit(),!0;let n=s.width,l=s.height,r=this.terminal.element?.querySelector(".xterm-viewport"),c=r?Math.max(0,r.offsetWidth-r.clientWidth):0,i=Math.max(2,Math.floor((t.width-c-n/2)/n)),u=Math.max(2,Math.floor(t.height/l));return(i!==this.terminal.cols||u!==this.terminal.rows)&&this.terminal.resize(i,u),!0}focus(){this.terminal.focus()}pasteText(t){this.terminal.paste(t)}input(t){this.terminal.input(t,!1)}onData(t){this.terminal.onData(t),this.terminal.onBinary(t)}onResize(t){this.terminal.onResize(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return t?t===this.container||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,s){let n=this.terminal.buffer.active,[l,r]=t<=s?[t,s]:[s,t],c=n.viewportY,i=[];for(let u=l;u<=r;u++){let f=n.getLine(c+u);i.push(f?f.translateToString(!0):"")}return i.join(`
2
2
  `)}selectRows(t,s){let n=this.terminal.buffer.active,[l,r]=t<=s?[t,s]:[s,t];this.terminal.selectLines(n.viewportY+l,n.viewportY+r)}selectCells(t,s,n,l){let r=this.terminal.buffer.active,c=Math.min(s,l),i=Math.max(s,l);if(c===i){let u=Math.min(t,n),f=Math.max(t,n);this.terminal.select(u,r.viewportY+c,f-u+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+i)}getCellsText(t,s,n,l){let r=this.terminal.buffer.active,c=Math.min(s,l),i=Math.max(s,l),u=s<l?t:s>l?n:Math.min(t,n),f=s<l?n:s>l?t:Math.max(t,n),S=r.viewportY,L=[];for(let F=c;F<=i;F++){let M=r.getLine(S+F);if(!M){L.push("");continue}let b=F===c?u:0,q=F===i?f:this.terminal.cols-1,ae="";for(let Ce=b;Ce<=q;Ce++){let O=M.getCell(Ce);O&&(ae+=O.getChars())}L.push(ae.replace(/\s+$/,""))}return L.join(`
3
- `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function yn(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function wn(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let n=await gn.create(e,s.scrollback,s.theme),l,r=0,c,i=null,u=0,f=0,S=.2,L=10,F=.95,M=.04,b=null,q=null;function ae(a){b||(b=document.createElement("div"),b.style.cssText=["position:absolute","left:0","width:40%","padding:10px 0","text-align:center","font:600 14px/1.4 system-ui, -apple-system, sans-serif","color:rgba(255,255,255,0.95)","background:rgba(20,30,50,0.55)","border:1px solid rgba(255,255,255,0.25)","border-radius:10px","pointer-events:none","opacity:0","transition:opacity 0.15s ease","z-index:6"].join(";"),e.appendChild(b)),b.textContent=a?"\u2191 \u4E0A\u7FFB\u4E00\u9875 (PgUp)":"\u2193 \u4E0B\u7FFB\u4E00\u9875 (PgDn)",b.style.top=a?"10%":"auto",b.style.bottom=a?"auto":"10%",b.style.opacity="1",q&&clearTimeout(q),q=setTimeout(()=>{b&&(b.style.opacity="0")},900)}function Ce(){q&&clearTimeout(q),b&&(b.style.opacity="0")}function O(){u&&(cancelAnimationFrame(u),u=0),f=0}function Yt(a){if(O(),Math.abs(a)<S)return;f=Math.max(-L,Math.min(L,a));let o=()=>{if(j){u=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,v=p>0?Math.round(f*16/p):f>0?1:-1;v===0&&(v=f>0?1:-1);let A=n.viewportY()-v,V=n.baseY(),pe=Math.max(0,Math.min(A,V));if(n.scrollToLine(pe,!0),f*=F,pe!==A){u=0;return}if(Math.abs(f)<M){u=0;return}u=requestAnimationFrame(o)};u=requestAnimationFrame(o)}let ke=0,R="connecting",Se=0,ie=!1,Te=[],Z="",j=!1,oe=1e3,qt=location.protocol==="https:"?"wss:":"ws:",lt=s.wsBase||"/ws",Ot=qt+"//"+location.host+lt+"/"+encodeURIComponent(t),Wt=(lt.replace(/^\/ws/,"")||"")+"/api/session/"+encodeURIComponent(t)+"/upload",Kt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Vt(){return Te.join("")+Z}async function Xt(a,o=0){let d=a?n.viewportY():0,p=fe(Vt());if(n.reset(),!p){a||n.scrollToBottom();return}await n.write(p),a?n.scrollToLine(o+d):n.scrollToBottom()}function W(a){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(a))}function Jt(a){let o;try{o=JSON.parse(a)}catch{R==="live"&&(Z+=a,n.write(fe(a)));return}if(o.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?W({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(o.type==="attach_failed"&&typeof o.message=="string"){Be=!0,R="dead",n.reset(),n.write(`\r
3
+ `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function yn(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function wn(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let n=await gn.create(e,s.scrollback,s.theme),l,r=0,c,i=null,u=0,f=0,S=.2,L=10,F=.95,M=.04,b=null,q=null;function ae(a){b||(b=document.createElement("div"),b.style.cssText=["position:absolute","left:0","width:40%","padding:10px 0","text-align:center","font:600 14px/1.4 system-ui, -apple-system, sans-serif","color:rgba(255,255,255,0.95)","background:rgba(20,30,50,0.55)","border:1px solid rgba(255,255,255,0.25)","border-radius:10px","pointer-events:none","opacity:0","transition:opacity 0.15s ease","z-index:6"].join(";"),e.appendChild(b)),b.textContent=a?"\u2191 \u4E0A\u7FFB\u4E00\u9875 (PgUp)":"\u2193 \u4E0B\u7FFB\u4E00\u9875 (PgDn)",b.style.top=a?"10%":"auto",b.style.bottom=a?"auto":"10%",b.style.opacity="1",q&&clearTimeout(q),q=setTimeout(()=>{b&&(b.style.opacity="0")},900)}function Ce(){q&&clearTimeout(q),b&&(b.style.opacity="0")}function O(){u&&(cancelAnimationFrame(u),u=0),f=0}function Yt(a){if(O(),Math.abs(a)<S)return;f=Math.max(-L,Math.min(L,a));let o=()=>{if(j){u=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,v=p>0?Math.round(f*16/p):f>0?1:-1;v===0&&(v=f>0?1:-1);let A=n.viewportY()-v,V=n.baseY(),pe=Math.max(0,Math.min(A,V));if(n.scrollToLine(pe,!0),f*=F,pe!==A){u=0;return}if(Math.abs(f)<M){u=0;return}u=requestAnimationFrame(o)};u=requestAnimationFrame(o)}let ke=0,R="connecting",Se=0,ie=!1,Te=[],Z="",j=!1,oe=1e3,qt=location.protocol==="https:"?"wss:":"ws:",rt=s.wsBase||"/ws",Ot=qt+"//"+location.host+rt+"/"+encodeURIComponent(t),Wt=(rt.replace(/^\/ws/,"")||"")+"/api/session/"+encodeURIComponent(t)+"/upload",Kt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Vt(){return Te.join("")+Z}async function Xt(a,o=0){let d=a?n.viewportY():0,p=fe(Vt());if(n.reset(),!p){a||n.scrollToBottom();return}await n.write(p),a?n.scrollToLine(o+d):n.scrollToBottom()}function W(a){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(a))}function Jt(a){let o;try{o=JSON.parse(a)}catch{R==="live"&&(Z+=a,n.write(fe(a)));return}if(o.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?W({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(o.type==="attach_failed"&&typeof o.message=="string"){Be=!0,R="dead",n.reset(),n.write(`\r
4
4
  \x1B[31m`+fe(o.message)+`\x1B[0m\r
5
- `).then(()=>{Me();try{l?.close()}catch{}});return}if(o.type==="auth.ok"){o.token&&localStorage.setItem("tmux-web-token",o.token),oe=1e3,K(),W({type:"resize",cols:n.cols,rows:n.rows}),T();return}if(o.type==="auth.failed"){if(o.permanentLock||o.retryAfterMs&&o.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(o.message);return}return}if(o.type==="snapshot"&&typeof o.data=="string"){let d=Je(fe(o.data));Te=[d],Z="",Se=typeof o.lines=="number"?o.lines:s.terminal.initialLines,R="live",n.reset(),K(),W({type:"resize",cols:n.cols,rows:n.rows}),n.write(d).then(()=>n.scrollToBottom());return}if(o.type==="window_changed"&&typeof o.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(o.activeIndex)&&(d.searchParams.set("window",String(o.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:o.windows}));return}if(o.type==="history"&&typeof o.data=="string"){o.lines>0&&o.data?(Te.unshift(Je(o.data)),Se+=o.lines,Xt(!0,o.lines).finally(()=>{ie=!1})):ie=!1;return}if(o.type==="data"&&typeof o.data=="string"){let d=Je(fe(o.data));if(R==="connecting"){R="live",Z=d,n.write(d);return}R==="live"&&(Z+=d,n.write(d))}}let Be=!1;function rt(){if(Be)return;let a=Ot,o=localStorage.getItem("tmux-web-token");o&&(a+=(a.includes("?")?"&":"?")+"token="+encodeURIComponent(o)),a+=(a.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,l=new WebSocket(a),l.onopen=()=>{R="connecting",Se=0,ie=!1,Te=[],Z="",n.reset(),oe=1e3},l.onmessage=d=>{typeof d.data=="string"&&Jt(d.data)},l.onclose=()=>{j||(R="connecting",!Be&&setTimeout(()=>{oe=Math.min(oe*2,1e4),!j&&!Be&&rt()},oe))},l.onerror=()=>l?.close()}function Q(a){W({type:"input",data:a})}function K(){return n.fit()}function Me(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Me()}),c=setTimeout(()=>{K()&&Me()},120)}function Gt(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function ct(a){return typeof a=="string"&&a.startsWith("image/")}function dt(a){return a?.files?.length?a.files[0]??null:null}function Zt(a){if(!a?.items)return null;for(let o=0;o<a.items.length;o++){let d=a.items[o];if(d?.kind==="file"&&ct(d.type))return d.getAsFile()}return null}async function Qt(a){let o=new FormData,d=a instanceof File&&a.name?a.name:"upload";o.append("file",a,d);let p=localStorage.getItem("tmux-web-token"),v={};p&&(v.Authorization="Bearer "+p);let A=await fetch(Wt,{method:"POST",body:o,headers:v});if(!A.ok){let pe="upload failed";try{let gt=await A.json();gt.error&&(pe=gt.error)}catch{}throw new Error(pe)}let V=await A.json();if(!V.path)throw new Error("no path in response");return V.path}async function Ke(a){try{let o=await Qt(a);Q(o)}catch{}}n.onData(a=>Q(a)),n.onResize(({cols:a,rows:o})=>W({type:"resize",cols:a,rows:o})),n.onScroll(()=>{R!=="live"||ie||!n.isNearScrollbackTop()||(ie=!0,W({type:"load_history",before:Se}))});let le=!1,re=!1,E=null,x=null,ce=document.createElement("div");ce.className="terminal-copy-banner",ce.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',ce.hidden=!0,e.appendChild(ce);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,e.appendChild($);function ut(a,o){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),v=p.height>0&&n.rows>0?p.height/n.rows:18,A=p.width>0&&n.cols>0?p.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((o-p.top)/v))),col:Math.max(0,Math.min(n.cols-1,Math.floor((a-p.left)/A)))}}async function Ie(a){if(!a)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(a);return}}catch{}let o=document.createElement("textarea");o.value=a,o.style.position="fixed",o.style.opacity="0",o.style.pointerEvents="none",document.body.appendChild(o),o.focus(),o.select();try{document.execCommand("copy")}catch{}o.remove()}let de;function De(){$.hidden=!1,de&&clearTimeout(de),de=setTimeout(()=>{$.hidden=!0},1e3)}function ee(a){le=a,ce.hidden=!a,e.classList.toggle("terminal-copy-mode-active",a),a&&n.focus()}function pt(){ee(!le)}function en(a){j||a.button!==0||re||(le||a.shiftKey)&&(a.preventDefault(),a.stopPropagation(),re=!0,E=ut(a.clientX,a.clientY),x=E,n.selectCells(E.col,E.row,x.col,x.row),document.addEventListener("mousemove",Ve,!0),document.addEventListener("mouseup",Xe,!0))}function Ve(a){j||!re||!E||(a.preventDefault(),a.stopPropagation(),x=ut(a.clientX,a.clientY),n.selectCells(E.col,E.row,x.col,x.row))}function Xe(a){if(j||!re||!E||!x)return;a.preventDefault(),a.stopPropagation(),document.removeEventListener("mousemove",Ve,!0),document.removeEventListener("mouseup",Xe,!0),re=!1;let o=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),ee(!1),o&&($.textContent="\u5DF2\u590D\u5236",Ie(o).then(De)),n.focus()}e.addEventListener("mousedown",en,!0),document.addEventListener("keydown",tn,!0);function tn(a){if(!(j||!n.isFocused()||a.defaultPrevented||a.isComposing)){if(le){if(a.key==="Escape"){a.preventDefault(),a.stopPropagation(),E=null,x=null,n.clearSelection(),ee(!1);return}if(a.key==="Enter"||(a.ctrlKey||a.metaKey)&&a.code==="KeyC"){a.preventDefault(),a.stopPropagation(),Ie(n.getScreenText()).then(De),ee(!1);return}}if(a.altKey&&a.code==="KeyC"&&!a.ctrlKey&&!a.metaKey){if(a.preventDefault(),a.stopPropagation(),E&&x){let o=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),ee(!1),o&&($.textContent="Copied",Ie(o).then(De))}else{let o=n.getSelection();o?(n.clearSelection(),$.textContent="Copied",Ie(o).then(De)):pt()}return}if((a.ctrlKey||a.metaKey)&&a.code==="KeyV"){a.preventDefault(),a.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let o=await navigator.clipboard.read();for(let d of o)for(let p of d.types)if(ct(p)){let v=await d.getType(p);await Ke(v);return}}}catch{}try{let o=await navigator.clipboard?.readText();o&&n.pasteText(ft(o))}catch{}})();return}}}let Fe=0;function ft(a){return a.split(`\r
5
+ `).then(()=>{Me();try{l?.close()}catch{}});return}if(o.type==="auth.ok"){o.token&&localStorage.setItem("tmux-web-token",o.token),oe=1e3,K(),W({type:"resize",cols:n.cols,rows:n.rows}),T();return}if(o.type==="auth.failed"){if(o.permanentLock||o.retryAfterMs&&o.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(o.message);return}return}if(o.type==="snapshot"&&typeof o.data=="string"){let d=Je(fe(o.data));Te=[d],Z="",Se=typeof o.lines=="number"?o.lines:s.terminal.initialLines,R="live",n.reset(),K(),W({type:"resize",cols:n.cols,rows:n.rows}),n.write(d).then(()=>n.scrollToBottom());return}if(o.type==="window_changed"&&typeof o.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(o.activeIndex)&&(d.searchParams.set("window",String(o.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:o.windows}));return}if(o.type==="history"&&typeof o.data=="string"){o.lines>0&&o.data?(Te.unshift(Je(o.data)),Se+=o.lines,Xt(!0,o.lines).finally(()=>{ie=!1})):ie=!1;return}if(o.type==="data"&&typeof o.data=="string"){let d=Je(fe(o.data));if(R==="connecting"){R="live",Z=d,n.write(d);return}R==="live"&&(Z+=d,n.write(d))}}let Be=!1;function ct(){if(Be)return;let a=Ot,o=localStorage.getItem("tmux-web-token");o&&(a+=(a.includes("?")?"&":"?")+"token="+encodeURIComponent(o)),a+=(a.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,l=new WebSocket(a),l.onopen=()=>{R="connecting",Se=0,ie=!1,Te=[],Z="",n.reset(),oe=1e3},l.onmessage=d=>{typeof d.data=="string"&&Jt(d.data)},l.onclose=()=>{j||(R="connecting",!Be&&setTimeout(()=>{oe=Math.min(oe*2,1e4),!j&&!Be&&ct()},oe))},l.onerror=()=>l?.close()}function Q(a){W({type:"input",data:a})}function K(){return n.fit()}function Me(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Me()}),c=setTimeout(()=>{K()&&Me()},120)}function Gt(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function dt(a){return typeof a=="string"&&a.startsWith("image/")}function ut(a){return a?.files?.length?a.files[0]??null:null}function Zt(a){if(!a?.items)return null;for(let o=0;o<a.items.length;o++){let d=a.items[o];if(d?.kind==="file"&&dt(d.type))return d.getAsFile()}return null}async function Qt(a){let o=new FormData,d=a instanceof File&&a.name?a.name:"upload";o.append("file",a,d);let p=localStorage.getItem("tmux-web-token"),v={};p&&(v.Authorization="Bearer "+p);let A=await fetch(Wt,{method:"POST",body:o,headers:v});if(!A.ok){let pe="upload failed";try{let yt=await A.json();yt.error&&(pe=yt.error)}catch{}throw new Error(pe)}let V=await A.json();if(!V.path)throw new Error("no path in response");return V.path}async function Ke(a){try{let o=await Qt(a);Q(o)}catch{}}n.onData(a=>Q(a)),n.onResize(({cols:a,rows:o})=>W({type:"resize",cols:a,rows:o})),n.onScroll(()=>{R!=="live"||ie||!n.isNearScrollbackTop()||(ie=!0,W({type:"load_history",before:Se}))});let le=!1,re=!1,E=null,x=null,ce=document.createElement("div");ce.className="terminal-copy-banner",ce.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',ce.hidden=!0,e.appendChild(ce);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,e.appendChild($);function pt(a,o){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),v=p.height>0&&n.rows>0?p.height/n.rows:18,A=p.width>0&&n.cols>0?p.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((o-p.top)/v))),col:Math.max(0,Math.min(n.cols-1,Math.floor((a-p.left)/A)))}}async function Ie(a){if(!a)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(a);return}}catch{}let o=document.createElement("textarea");o.value=a,o.style.position="fixed",o.style.opacity="0",o.style.pointerEvents="none",document.body.appendChild(o),o.focus(),o.select();try{document.execCommand("copy")}catch{}o.remove()}let de;function De(){$.hidden=!1,de&&clearTimeout(de),de=setTimeout(()=>{$.hidden=!0},1e3)}function ee(a){le=a,ce.hidden=!a,e.classList.toggle("terminal-copy-mode-active",a),a&&n.focus()}function ft(){ee(!le)}function en(a){j||a.button!==0||re||(le||a.shiftKey)&&(a.preventDefault(),a.stopPropagation(),re=!0,E=pt(a.clientX,a.clientY),x=E,n.selectCells(E.col,E.row,x.col,x.row),document.addEventListener("mousemove",Ve,!0),document.addEventListener("mouseup",Xe,!0))}function Ve(a){j||!re||!E||(a.preventDefault(),a.stopPropagation(),x=pt(a.clientX,a.clientY),n.selectCells(E.col,E.row,x.col,x.row))}function Xe(a){if(j||!re||!E||!x)return;a.preventDefault(),a.stopPropagation(),document.removeEventListener("mousemove",Ve,!0),document.removeEventListener("mouseup",Xe,!0),re=!1;let o=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),ee(!1),o&&($.textContent="\u5DF2\u590D\u5236",Ie(o).then(De)),n.focus()}e.addEventListener("mousedown",en,!0),document.addEventListener("keydown",tn,!0);function tn(a){if(!(j||!n.isFocused()||a.defaultPrevented||a.isComposing)){if(le){if(a.key==="Escape"){a.preventDefault(),a.stopPropagation(),E=null,x=null,n.clearSelection(),ee(!1);return}if(a.key==="Enter"||(a.ctrlKey||a.metaKey)&&a.code==="KeyC"){a.preventDefault(),a.stopPropagation(),Ie(n.getScreenText()).then(De),ee(!1);return}}if(a.altKey&&a.code==="KeyC"&&!a.ctrlKey&&!a.metaKey){if(a.preventDefault(),a.stopPropagation(),E&&x){let o=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),ee(!1),o&&($.textContent="Copied",Ie(o).then(De))}else{let o=n.getSelection();o?(n.clearSelection(),$.textContent="Copied",Ie(o).then(De)):ft()}return}if((a.ctrlKey||a.metaKey)&&a.code==="KeyV"){a.preventDefault(),a.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let o=await navigator.clipboard.read();for(let d of o)for(let p of d.types)if(dt(p)){let v=await d.getType(p);await Ke(v);return}}}catch{}try{let o=await navigator.clipboard?.readText();o&&n.pasteText(mt(o))}catch{}})();return}}}let Fe=0;function mt(a){return a.split(`\r
6
6
  `).join(`
7
7
  `).split(`
8
- `).join("\r")}async function nn(a){let o=Zt(a.clipboardData);if(o){a.preventDefault(),a.stopPropagation();let v=Date.now();if(v-Fe<50)return;Fe=v,await Ke(o);return}let d=a.clipboardData?.getData("text/plain");if(!d)return;a.preventDefault(),a.stopPropagation();let p=Date.now();p-Fe<50||(Fe=p,n.pasteText(ft(d)))}let ue=0;function sn(a){a.preventDefault(),ue++,dt(a.dataTransfer)&&e.classList.add("terminal-drag-over")}function an(a){a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="copy")}function on(a){a.preventDefault(),ue--,ue<=0&&(ue=0,e.classList.remove("terminal-drag-over"))}async function ln(a){a.preventDefault(),ue=0,e.classList.remove("terminal-drag-over");let o=dt(a.dataTransfer);o&&await Ke(o)}function mt(a){!Kt||a.key!=="Escape"||le||a.defaultPrevented||a.repeat||a.metaKey||a.ctrlKey||a.altKey||n.isFocused()&&(a.preventDefault(),a.stopPropagation(),Q("\x1B"))}e.addEventListener("paste",nn),e.addEventListener("dragenter",sn),e.addEventListener("dragover",an),e.addEventListener("dragleave",on),e.addEventListener("drop",ln),document.addEventListener("keydown",mt,!0);function rn(a){if(a.touches.length!==1){i=null;return}let o=a.touches[0];if(!o)return;O();let d=e.getBoundingClientRect(),p=o.clientX-d.left<d.width*.4?"tui":"history",v=o.clientY-d.top<d.height*.5;i={startX:o.clientX,startY:o.clientY,lastX:o.clientX,lastY:o.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:p,tuiUp:v,sent:!1},a.stopPropagation()}function cn(a){if(!i||a.touches.length!==1)return;let o=a.touches[0];if(!o)return;let d=o.clientY-i.startY,p=o.clientX-i.startX;if(!i.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(p))return;i.scrolling=!0,ke=Date.now()+500}a.preventDefault(),a.stopPropagation();let v=performance.now(),A=o.clientY-i.lastY;if(i.zone==="tui"){i.sent||(i.sent=!0,Q(i.tuiUp?"\x1B[5~":"\x1B[6~"),ae(i.tuiUp)),i.lastX=o.clientX,i.lastY=o.clientY,i.lastT=v;return}let V=v-i.lastT;V>0&&(i.velocity=A/V),fn(-A),i.lastX=o.clientX,i.lastY=o.clientY,i.lastT=v}function dn(a){if(!i)return;let o=i.scrolling,d=i.zone,p=i.tuiUp,v=i.velocity;if(i=null,a.stopPropagation(),d==="tui"){o||(Q(p?"\x1B[5~":"\x1B[6~"),ae(p),ke=Date.now()+500,a.preventDefault());return}if(!o){n.focus();return}ke=Date.now()+500,a.preventDefault(),v&&Yt(v)}function un(a){i=null,O(),a.stopPropagation()}function pn(a){a.pointerType==="touch"&&Date.now()<ke&&(a.preventDefault(),a.stopPropagation())}function fn(a){let o=e.querySelector(".xterm-rows > div"),d=o?o.getBoundingClientRect().height:16,p=d>0?Math.round(a/d):a>0?1:-1;p===0&&(p=a>0?1:-1),n.scrollLines(p)}e.addEventListener("touchstart",rn,{passive:!0,capture:!0}),e.addEventListener("touchmove",cn,{passive:!1,capture:!0}),e.addEventListener("touchend",dn,{passive:!1,capture:!0}),e.addEventListener("touchcancel",un,{passive:!0,capture:!0}),e.addEventListener("pointerup",pn,!0);function ht(){T()}window.addEventListener("resize",ht),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let vt=new ResizeObserver(()=>T());vt.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",Gt,!0);async function mn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(a=>setTimeout(a,1e3))])}catch{}}return requestAnimationFrame(()=>{K()&&Me(),(async()=>(await mn(),K()&&W({type:"resize",cols:n.cols,rows:n.rows}),rt(),T()))()}),{destroy(){if(j=!0,O(),ee(!1),de&&clearTimeout(de),document.removeEventListener("mousemove",Ve,!0),document.removeEventListener("mouseup",Xe,!0),l){try{l.close(1e3,"session switch")}catch{}l=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",ht),vt.disconnect(),document.removeEventListener("keydown",mt,!0)},focus(){n.focus()},sendInput(a){Q(a)},toggleCopyMode(){pt()}}})()}var me=window.__TMUX_WEB_SHELL__,g=null,y=null,he=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,B=he;window.__tmuxWebScopeAgent=he;var Ge="sessions",U="",He="",Pe="",yt="";function C(e){return B?"/a/"+encodeURIComponent(B)+e:e}var m=document.getElementById("sidebar-content"),X=document.getElementById("main-placeholder"),I=document.getElementById("terminal-container"),ve=document.getElementById("file-editor"),bn=document.getElementById("fe-path"),H=document.getElementById("fe-status"),ge=document.getElementById("fe-content"),J=document.getElementById("fe-save"),En=document.getElementById("fe-delete"),Ln=document.getElementById("fe-back"),wt=document.getElementById("fe-new-name"),xn=document.getElementById("fe-new-btn"),Ze=document.getElementById("settings-backdrop"),bt=document.getElementById("settings-popover"),Et=document.querySelector(".sidebar");function Cn(){return window.matchMedia("(max-width: 640px)").matches}function Qe(){Cn()&&Et.classList.add("collapsed")}function Lt(){Et.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Lt(),xt("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Lt(),xt("files")});function xt(e){Ge=e,document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(et="",_()):$n()}var et="";async function _(){if(Ge!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===et)return;et=s,Mn(t)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{let t=window.__openNewSessionModal;typeof t=="function"?t():document.getElementById("new-session-modal")?.classList.add("open")}),ot()}var Ct="tmux-web-sidebar-group-collapsed";function kt(){try{return new Set(JSON.parse(localStorage.getItem(Ct)||"[]"))}catch{return new Set}}function kn(e){try{localStorage.setItem(Ct,JSON.stringify([...e]))}catch{}}function Sn(e,t,s){if(!e)return"\u2026";let n=s&&(e===s||e.startsWith(s+"/"))?"~"+e.slice(s.length):e,l=n.split("/").filter(Boolean);if(l.length===0)return e;for(let r=1;r<=l.length;r++){let c=l.slice(-r).join("/");if(!t.some(i=>!i||i===e?!1:(s&&(i===s||i.startsWith(s+"/"))?"~"+i.slice(s.length):i).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return n}function St(e,t,s,n=!0){let l=new Map;for(let i of e){let u=i.path||"";l.has(u)||l.set(u,[]),l.get(u).push(i)}let r=[...l.keys()],c=kt();for(let[i,u]of l){let f=Sn(i,r,t),S=c.has(i),L=document.createElement("div");L.className="session-group-header"+(S?" collapsed":""),L.title=i||"No working directory",L.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${h(f)}</span>`,L.addEventListener("click",()=>{let M=kt(),b=L.nextElementSibling;M.has(i)?(M.delete(i),L.classList.remove("collapsed"),b?.classList.remove("collapsed")):(M.add(i),L.classList.add("collapsed"),b?.classList.add("collapsed")),kn(M)});let F=document.createElement("div");F.className="session-group-body"+(S?" collapsed":"");for(let M of u)F.appendChild(s?Tn(M,s,n):Bn(M));m.appendChild(L),m.appendChild(F)}}function Tn(e,t,s){let n=document.createElement("div");return n.className="session-item"+(e.name===y&&B===t?" active":"")+(s?"":" session-offline"),n.setAttribute("data-session",e.name),n.setAttribute("data-agent",t),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
8
+ `).join("\r")}async function nn(a){let o=Zt(a.clipboardData);if(o){a.preventDefault(),a.stopPropagation();let v=Date.now();if(v-Fe<50)return;Fe=v,await Ke(o);return}let d=a.clipboardData?.getData("text/plain");if(!d)return;a.preventDefault(),a.stopPropagation();let p=Date.now();p-Fe<50||(Fe=p,n.pasteText(mt(d)))}let ue=0;function sn(a){a.preventDefault(),ue++,ut(a.dataTransfer)&&e.classList.add("terminal-drag-over")}function an(a){a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="copy")}function on(a){a.preventDefault(),ue--,ue<=0&&(ue=0,e.classList.remove("terminal-drag-over"))}async function ln(a){a.preventDefault(),ue=0,e.classList.remove("terminal-drag-over");let o=ut(a.dataTransfer);o&&await Ke(o)}function ht(a){!Kt||a.key!=="Escape"||le||a.defaultPrevented||a.repeat||a.metaKey||a.ctrlKey||a.altKey||n.isFocused()&&(a.preventDefault(),a.stopPropagation(),Q("\x1B"))}e.addEventListener("paste",nn),e.addEventListener("dragenter",sn),e.addEventListener("dragover",an),e.addEventListener("dragleave",on),e.addEventListener("drop",ln),document.addEventListener("keydown",ht,!0);function rn(a){if(a.touches.length!==1){i=null;return}let o=a.touches[0];if(!o)return;O();let d=e.getBoundingClientRect(),p=o.clientX-d.left<d.width*.4?"tui":"history",v=o.clientY-d.top<d.height*.5;i={startX:o.clientX,startY:o.clientY,lastX:o.clientX,lastY:o.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:p,tuiUp:v,sent:!1},a.stopPropagation()}function cn(a){if(!i||a.touches.length!==1)return;let o=a.touches[0];if(!o)return;let d=o.clientY-i.startY,p=o.clientX-i.startX;if(!i.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(p))return;i.scrolling=!0,ke=Date.now()+500}a.preventDefault(),a.stopPropagation();let v=performance.now(),A=o.clientY-i.lastY;if(i.zone==="tui"){i.sent||(i.sent=!0,Q(i.tuiUp?"\x1B[5~":"\x1B[6~"),ae(i.tuiUp)),i.lastX=o.clientX,i.lastY=o.clientY,i.lastT=v;return}let V=v-i.lastT;V>0&&(i.velocity=A/V),fn(-A),i.lastX=o.clientX,i.lastY=o.clientY,i.lastT=v}function dn(a){if(!i)return;let o=i.scrolling,d=i.zone,p=i.tuiUp,v=i.velocity;if(i=null,a.stopPropagation(),d==="tui"){o||(Q(p?"\x1B[5~":"\x1B[6~"),ae(p),ke=Date.now()+500,a.preventDefault());return}if(!o){n.focus();return}ke=Date.now()+500,a.preventDefault(),v&&Yt(v)}function un(a){i=null,O(),a.stopPropagation()}function pn(a){a.pointerType==="touch"&&Date.now()<ke&&(a.preventDefault(),a.stopPropagation())}function fn(a){let o=e.querySelector(".xterm-rows > div"),d=o?o.getBoundingClientRect().height:16,p=d>0?Math.round(a/d):a>0?1:-1;p===0&&(p=a>0?1:-1),n.scrollLines(p)}e.addEventListener("touchstart",rn,{passive:!0,capture:!0}),e.addEventListener("touchmove",cn,{passive:!1,capture:!0}),e.addEventListener("touchend",dn,{passive:!1,capture:!0}),e.addEventListener("touchcancel",un,{passive:!0,capture:!0}),e.addEventListener("pointerup",pn,!0);function vt(){T()}window.addEventListener("resize",vt),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let gt=new ResizeObserver(()=>T());gt.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",Gt,!0);async function mn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(a=>setTimeout(a,1e3))])}catch{}}return requestAnimationFrame(()=>{K()&&Me(),(async()=>(await mn(),K()&&W({type:"resize",cols:n.cols,rows:n.rows}),ct(),T()))()}),{destroy(){if(j=!0,O(),ee(!1),de&&clearTimeout(de),document.removeEventListener("mousemove",Ve,!0),document.removeEventListener("mouseup",Xe,!0),l){try{l.close(1e3,"session switch")}catch{}l=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",vt),gt.disconnect(),document.removeEventListener("keydown",ht,!0)},focus(){n.focus()},sendInput(a){Q(a)},toggleCopyMode(){ft()}}})()}var me=window.__TMUX_WEB_SHELL__,g=null,y=null,he=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,B=he;window.__tmuxWebScopeAgent=he;var Ge="sessions",U="",He="",Pe="",wt="";function C(e){return B?"/a/"+encodeURIComponent(B)+e:e}var m=document.getElementById("sidebar-content"),X=document.getElementById("main-placeholder"),I=document.getElementById("terminal-container"),ve=document.getElementById("file-editor"),bn=document.getElementById("fe-path"),H=document.getElementById("fe-status"),ge=document.getElementById("fe-content"),J=document.getElementById("fe-save"),En=document.getElementById("fe-delete"),Ln=document.getElementById("fe-back"),bt=document.getElementById("fe-new-name"),xn=document.getElementById("fe-new-btn"),Ze=document.getElementById("settings-backdrop"),Et=document.getElementById("settings-popover"),Lt=document.querySelector(".sidebar");function Cn(){return window.matchMedia("(max-width: 640px)").matches}function Qe(){Cn()&&Lt.classList.add("collapsed")}function xt(){Lt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{xt(),Ct("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{xt(),Ct("files")});function Ct(e){Ge=e,document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(et="",_()):$n()}var et="";async function _(){if(Ge!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===et)return;et=s,Mn(t)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{let t=window.__openNewSessionModal;typeof t=="function"?t():document.getElementById("new-session-modal")?.classList.add("open")}),lt()}var kt="tmux-web-sidebar-group-collapsed";function St(){try{return new Set(JSON.parse(localStorage.getItem(kt)||"[]"))}catch{return new Set}}function kn(e){try{localStorage.setItem(kt,JSON.stringify([...e]))}catch{}}function Sn(e,t,s){if(!e)return"\u2026";let n=s&&(e===s||e.startsWith(s+"/"))?"~"+e.slice(s.length):e,l=n.split("/").filter(Boolean);if(l.length===0)return e;for(let r=1;r<=l.length;r++){let c=l.slice(-r).join("/");if(!t.some(i=>!i||i===e?!1:(s&&(i===s||i.startsWith(s+"/"))?"~"+i.slice(s.length):i).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return n}function Tt(e,t,s,n=!0){let l=new Map;for(let i of e){let u=i.path||"";l.has(u)||l.set(u,[]),l.get(u).push(i)}let r=[...l.keys()],c=St();for(let[i,u]of l){let f=Sn(i,r,t),S=c.has(i),L=document.createElement("div");L.className="session-group-header"+(S?" collapsed":""),L.title=i||"No working directory",L.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${h(f)}</span>`,L.addEventListener("click",()=>{let M=St(),b=L.nextElementSibling;M.has(i)?(M.delete(i),L.classList.remove("collapsed"),b?.classList.remove("collapsed")):(M.add(i),L.classList.add("collapsed"),b?.classList.add("collapsed")),kn(M)});let F=document.createElement("div");F.className="session-group-body"+(S?" collapsed":"");for(let M of u)F.appendChild(s?Tn(M,s,n):Bn(M));m.appendChild(L),m.appendChild(F)}}function Tn(e,t,s){let n=document.createElement("div");return n.className="session-item"+(e.name===y&&B===t?" active":"")+(s?"":" session-offline"),n.setAttribute("data-session",e.name),n.setAttribute("data-agent",t),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
9
9
  <span>${h(e.name)}</span>${s?"":'<span class="meta">offline</span>'}`,s&&n.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||ye(e.name,t)}),n}function Bn(e){let t=document.createElement("div");t.className="session-item"+(e.name===y&&!B?" active":""),t.setAttribute("data-session",e.name),t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
10
10
  <span>${h(e.name)}</span>
11
11
  <button class="session-edit-btn" data-session="${h(e.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
12
- `,t.addEventListener("click",n=>{n.target.closest(".session-edit-btn")||ye(e.name)});let s=t.querySelector(".session-edit-btn");return s&&s.addEventListener("click",n=>{n.stopPropagation(),Rt(e.name,s)}),t}function Mn(e){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],s=document.createElement("div");s.className="sidebar-section-label"+(he?" clickable":""),s.textContent=he?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",he&&(s.title="Back to hub",s.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(s),St(t,e.home);let n=e.agents||[];for(let r of n){if(!r.sessions||r.sessions.length===0)continue;let c=r.agentId===B,i=document.createElement("div");i.className="sidebar-section-label machine-label"+(r.online?"":" agent-offline-label")+(!c&&r.online?" clickable":""),i.textContent=r.agentName+(r.online?"":" (offline)"),!c&&r.online&&(i.title="\u6253\u5F00 "+r.agentName,i.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(r.agentId)+"/"})),m.appendChild(i),St(r.sessions,e.home,r.agentId,r.online)}let l=e.tombstones||[];if(l.length>0){let r=document.createElement("div");r.className="sidebar-section-label tombstone-label",r.textContent="\u5931\u6548\u4F1A\u8BDD",m.appendChild(r);for(let c of l){let i=document.createElement("div");i.className="session-item session-tombstone",i.setAttribute("data-session",c.name),c.agentId&&i.setAttribute("data-agent",c.agentId);let u=c.agentId?c.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";i.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
12
+ `,t.addEventListener("click",n=>{n.target.closest(".session-edit-btn")||ye(e.name)});let s=t.querySelector(".session-edit-btn");return s&&s.addEventListener("click",n=>{n.stopPropagation(),nt(e.name,s)}),t}function Mn(e){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],s=document.createElement("div");s.className="sidebar-section-label"+(he?" clickable":""),s.textContent=he?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",he&&(s.title="Back to hub",s.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(s),Tt(t,e.home);let n=e.agents||[];for(let r of n){if(!r.sessions||r.sessions.length===0)continue;let c=r.agentId===B,i=document.createElement("div");i.className="sidebar-section-label machine-label"+(r.online?"":" agent-offline-label")+(!c&&r.online?" clickable":""),i.textContent=r.agentName+(r.online?"":" (offline)"),!c&&r.online&&(i.title="\u6253\u5F00 "+r.agentName,i.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(r.agentId)+"/"})),m.appendChild(i),Tt(r.sessions,e.home,r.agentId,r.online)}let l=e.tombstones||[];if(l.length>0){let r=document.createElement("div");r.className="sidebar-section-label tombstone-label",r.textContent="\u5931\u6548\u4F1A\u8BDD",m.appendChild(r);for(let c of l){let i=document.createElement("div");i.className="session-item session-tombstone",i.setAttribute("data-session",c.name),c.agentId&&i.setAttribute("data-agent",c.agentId);let u=c.agentId?c.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";i.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
13
13
  <span>${h(c.name)}</span><span class="meta">\u5931\u6548${u}</span>
14
- <button class="session-edit-btn" data-session="${h(c.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;let f=i.querySelector(".session-edit-btn");f&&f.addEventListener("click",S=>{S.stopPropagation(),Rt(c.name,f,{tombstone:!0,agentId:c.agentId})}),m.appendChild(i)}}}function h(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function ye(e,t){if(y===e&&g&&B===(t||null))return;ve.style.display="none",X.style.display="none",I.style.display="",I.classList.add("terminal-pending"),Qe(),g&&(Ye(),g.destroy(),g=null),y=e,B=t||null,window.__tmuxWebScopeAgent=B,window.__tmuxWebCurrentSession=y,Gn(e,t);let s=it(e,t);jt.set(s,Date.now()+Xn);let n=se.get(s);if(n&&n.state==="working"){n.state="idle",n.doneUntil=void 0,n.unread=!1;let l=m.querySelector(t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');l&&We(l,s,n)}for(let l of m.querySelectorAll(".session-item"))l.classList.toggle("active",l.getAttribute("data-session")===e&&(l.getAttribute("data-agent")||null)===B);try{g=await wn(I,e,{terminal:me.terminal,scrollback:me.scrollback,theme:me.theme,wsBase:B?"/ws/a/"+encodeURIComponent(B):me.wsBase||"/ws"}),Nt()}catch(l){console.error("[shell] terminal init failed:",l);let r=l&&typeof l=="object"&&"message"in l?l.message:String(l);I.textContent="Failed to open terminal for "+e+": "+r,I.classList.remove("terminal-pending")}}var Ne=[],w=null,Tt=!1;function In(e){if(!w||!w.files.length)return null;let t=w.repoRoot;if(!t||!e.startsWith(t))return null;let s=e.slice(t.length+1);for(let n of w.files)if(n.path===s)return n.status;return null}async function Dn(e){Tt=!0;try{let t=await fetch(C("/api/git/status?path="+encodeURIComponent(e)));t.ok?w=await t.json():w=null}catch{w=null}finally{Tt=!1}}function Fn(){if(!w||!w.branch)return"";let{branch:e,linesAdded:t,linesRemoved:s}=w,n=`<div class="sidebar-git"><div class="git-branch-badge">
14
+ <button class="session-edit-btn" data-session="${h(c.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`,i.title="\u5931\u6548\u4F1A\u8BDD\uFF1A\u70B9\u51FB\u7BA1\u7406\uFF08\u91CD\u5EFA/\u91CD\u547D\u540D/\u5220\u9664\uFF09",i.addEventListener("click",()=>{nt(c.name,i,{tombstone:!0,agentId:c.agentId})});let f=i.querySelector(".session-edit-btn");f&&f.addEventListener("click",S=>{S.stopPropagation(),nt(c.name,f,{tombstone:!0,agentId:c.agentId})}),m.appendChild(i)}}}function h(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function ye(e,t){if(y===e&&g&&B===(t||null))return;ve.style.display="none",X.style.display="none",I.style.display="",I.classList.add("terminal-pending"),Qe(),g&&(Ye(),g.destroy(),g=null),y=e,B=t||null,window.__tmuxWebScopeAgent=B,window.__tmuxWebCurrentSession=y,Gn(e,t);let s=ot(e,t);jt.set(s,Date.now()+Xn);let n=se.get(s);if(n&&n.state==="working"){n.state="idle",n.doneUntil=void 0,n.unread=!1;let l=m.querySelector(t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');l&&We(l,s,n)}for(let l of m.querySelectorAll(".session-item"))l.classList.toggle("active",l.getAttribute("data-session")===e&&(l.getAttribute("data-agent")||null)===B);try{g=await wn(I,e,{terminal:me.terminal,scrollback:me.scrollback,theme:me.theme,wsBase:B?"/ws/a/"+encodeURIComponent(B):me.wsBase||"/ws"}),Rt()}catch(l){console.error("[shell] terminal init failed:",l);let r=l&&typeof l=="object"&&"message"in l?l.message:String(l);I.textContent="Failed to open terminal for "+e+": "+r,I.classList.remove("terminal-pending")}}var Ne=[],w=null,Bt=!1;function In(e){if(!w||!w.files.length)return null;let t=w.repoRoot;if(!t||!e.startsWith(t))return null;let s=e.slice(t.length+1);for(let n of w.files)if(n.path===s)return n.status;return null}async function Dn(e){Bt=!0;try{let t=await fetch(C("/api/git/status?path="+encodeURIComponent(e)));t.ok?w=await t.json():w=null}catch{w=null}finally{Bt=!1}}function Fn(){if(!w||!w.branch)return"";let{branch:e,linesAdded:t,linesRemoved:s}=w,n=`<div class="sidebar-git"><div class="git-branch-badge">
15
15
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
16
- ${h(e)}</div>`;return(t>0||s>0)&&(n+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),n+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&te&&w&&(e.stopPropagation(),Ft())});var we=document.getElementById("header-git"),Bt=document.getElementById("git-diff-view"),Mt=document.getElementById("gd-path"),Re=document.getElementById("gd-status"),$e=document.getElementById("gd-content"),An=document.getElementById("gd-back"),te=null;function be(e){if(!e||!e.branch){we.style.display="none",te=null;return}te=e.repoRoot;let{branch:t,linesAdded:s,linesRemoved:n}=e,l=`<span class="branch">${h(t)}</span>`;(s>0||n>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${s}</span> <span class="diff-del">-${n}</span>`),we.innerHTML=l,we.style.display="block"}we.addEventListener("click",e=>{te&&w&&(e.stopPropagation(),Ft())});var k=document.getElementById("git-popover"),ze=document.getElementById("git-popover-backdrop");function tt(){let e=we.getBoundingClientRect();k.style.top=e.bottom+4+"px",k.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function It(){let e=te,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Ue(),e){Ge="files",document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let s=document.getElementById("mode-files");s&&s.classList.add("active"),Y(e)}})}var Dt="";function Ft(){if(!w)return;let e=w.files||[];if(!e.length)k.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
16
+ ${h(e)}</div>`;return(t>0||s>0)&&(n+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),n+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&te&&w&&(e.stopPropagation(),At())});var we=document.getElementById("header-git"),Mt=document.getElementById("git-diff-view"),It=document.getElementById("gd-path"),Re=document.getElementById("gd-status"),$e=document.getElementById("gd-content"),An=document.getElementById("gd-back"),te=null;function be(e){if(!e||!e.branch){we.style.display="none",te=null;return}te=e.repoRoot;let{branch:t,linesAdded:s,linesRemoved:n}=e,l=`<span class="branch">${h(t)}</span>`;(s>0||n>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${s}</span> <span class="diff-del">-${n}</span>`),we.innerHTML=l,we.style.display="block"}we.addEventListener("click",e=>{te&&w&&(e.stopPropagation(),At())});var k=document.getElementById("git-popover"),ze=document.getElementById("git-popover-backdrop");function tt(){let e=we.getBoundingClientRect();k.style.top=e.bottom+4+"px",k.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Dt(){let e=te,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Ue(),e){Ge="files",document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let s=document.getElementById("mode-files");s&&s.classList.add("active"),Y(e)}})}var Ft="";function At(){if(!w)return;let e=w.files||[];if(!e.length)k.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
17
17
  <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let s=0;s<e.length;s++){let n=e[s],l=n.additions||0,r=n.deletions||0,c="";switch(n.status){case"M":c='<span class="git-file-status M">M</span>';break;case"A":c='<span class="git-file-status A">A</span>';break;case"D":c='<span class="git-file-status D">D</span>';break;case"?":c='<span class="git-file-status ?">?</span>';break;case"R":c='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${s}">
18
18
  ${c}<span class="file-path">${h(n.path)}</span>
19
19
  <span class="file-add">${l>0?"+"+l:""}</span>
20
20
  <span class="file-del">${r>0?"-"+r:""}</span>
21
- </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Dt=t,k.innerHTML=t,k.querySelectorAll(".git-popover-item").forEach(s=>{let n=parseInt(s.dataset.idx||"0",10),l=e[n];!l||l.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>Ht(l.path)))})}tt(),k.classList.add("open"),ze.classList.add("open"),It()}function Hn(){return window.innerWidth>=1024}function At(e,t){let s=(e+(t&&e?`
21
+ </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Ft=t,k.innerHTML=t,k.querySelectorAll(".git-popover-item").forEach(s=>{let n=parseInt(s.dataset.idx||"0",10),l=e[n];!l||l.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>Pt(l.path)))})}tt(),k.classList.add("open"),ze.classList.add("open"),Dt()}function Hn(){return window.innerWidth>=1024}function Ht(e,t){let s=(e+(t&&e?`
22
22
  `:"")+t).split(`
23
- `),n="",l=0,r=0;for(let c of s){let i=c;if(i.startsWith("@@")){let u=i.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(l=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),n+=`<div class="diff-line hunk"><span class="ln-body">${h(i)}</span></div>`;continue}if(i.startsWith("diff --git")||i.startsWith("index ")||i.startsWith("--- ")||i.startsWith("+++ ")||i.startsWith("new file")||i.startsWith("deleted file")){i.startsWith("diff --git")&&(n+=`<div class="diff-line header"><span class="ln-body">${h(i)}</span></div>`);continue}if(i.startsWith("+"))r++,n+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${h(i)}</span></div>`;else if(i.startsWith("-"))l++,n+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${h(i)}</span></div>`;else if(i.startsWith("\\"))n+=`<div class="diff-line"><span class="ln-body">${h(i)}</span></div>`;else{if(!i&&!l&&!r)continue;l++,r++,n+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${h(i)}</span></div>`}}return{html:n,lineCount:s.length}}var je=null;async function Ht(e){let t=te;if(t)if(Ue(),Hn()){I.style.display="none",g&&(Ye(),g.destroy(),g=null,je=y,y=null),be(null),ve.style.display="none",X.style.display="none",Bt.style.display="flex",Mt.textContent=e,Re.textContent="Loading\u2026",$e.innerHTML="";try{let s=await fetch(C("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let n=await s.json();Nn(e,n.diff,n.stagedDiff)}catch(s){let n=s instanceof Error?s.message:String(s);Re.textContent="Error",$e.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${h(n)}</div>`}}else{k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
+ `),n="",l=0,r=0;for(let c of s){let i=c;if(i.startsWith("@@")){let u=i.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(l=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),n+=`<div class="diff-line hunk"><span class="ln-body">${h(i)}</span></div>`;continue}if(i.startsWith("diff --git")||i.startsWith("index ")||i.startsWith("--- ")||i.startsWith("+++ ")||i.startsWith("new file")||i.startsWith("deleted file")){i.startsWith("diff --git")&&(n+=`<div class="diff-line header"><span class="ln-body">${h(i)}</span></div>`);continue}if(i.startsWith("+"))r++,n+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${h(i)}</span></div>`;else if(i.startsWith("-"))l++,n+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${h(i)}</span></div>`;else if(i.startsWith("\\"))n+=`<div class="diff-line"><span class="ln-body">${h(i)}</span></div>`;else{if(!i&&!l&&!r)continue;l++,r++,n+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${h(i)}</span></div>`}}return{html:n,lineCount:s.length}}var je=null;async function Pt(e){let t=te;if(t)if(Ue(),Hn()){I.style.display="none",g&&(Ye(),g.destroy(),g=null,je=y,y=null),be(null),ve.style.display="none",X.style.display="none",Mt.style.display="flex",It.textContent=e,Re.textContent="Loading\u2026",$e.innerHTML="";try{let s=await fetch(C("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let n=await s.json();Nn(e,n.diff,n.stagedDiff)}catch(s){let n=s instanceof Error?s.message:String(s);Re.textContent="Error",$e.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${h(n)}</div>`}}else{k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
25
  <span class="diff-filename">${h(e)}</span>
26
26
  <span class="diff-loading">Loading\u2026</span>
@@ -30,11 +30,11 @@ var hn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
30
30
  </div><div class="diff-empty">Error loading diff: ${h(n)}</div></div>`}}}function Pn(e,t,s){if(!t&&!s){k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
31
31
  <button class="diff-back">\u2190 Back</button>
32
32
  <span class="diff-filename">${h(e)}</span>
33
- </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:n,lineCount:l}=At(t,s);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
33
+ </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:n,lineCount:l}=Ht(t,s);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
34
34
  <button class="diff-back">\u2190 Back</button>
35
35
  <span class="diff-filename">${h(e)}</span>
36
36
  <span class="diff-filesize">${l} lines</span>
37
- </div><div class="diff-content">${n}</div></div>`,k.querySelector(".diff-back")?.addEventListener("click",()=>{k.innerHTML=Dt,tt(),w&&k.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),i=w.files[c];!i||i.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>Ht(i.path)))}),It()})}function Nn(e,t,s){if(!t&&!s){Re.textContent="No diff",$e.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:n,lineCount:l}=At(t,s);Mt.textContent=e,Re.textContent=l+" lines",$e.innerHTML=n}function Rn(){Bt.style.display="none",je?(I.style.display="",I.classList.add("terminal-pending"),ye(je),je=null):y?(I.style.display="",I.classList.add("terminal-pending"),Nt()):X.style.display="flex"}An.addEventListener("click",Rn);function Ue(){k.classList.remove("open"),ze.classList.remove("open")}ze.addEventListener("click",Ue),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Ue(),Oe())});async function Pt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){let n=await s.json();w=n,be(n);return}}}catch{}w=null,be(null)}var _e=null;function Nt(){Ye(),y&&(Pt(y),_e=setInterval(()=>{y&&Pt(y)},3e4))}function Ye(){_e&&(clearInterval(_e),_e=null)}function $n(){if(Ne=me.fsRoots||[],Ne.length===0){if(y){zn(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 e of Ne){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(e)}`,t.addEventListener("click",()=>Y(e)),m.appendChild(t)}}async function zn(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(C("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?Y(t.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function Y(e){U=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Dn(e);try{let s=await(await fetch(C("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",be(w),m.innerHTML+=Fn();let n=jn(e);if(n&&!Ne.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>Y(n)),m.appendChild(r)}if(s.dirs)for(let r of s.dirs){let c=r.split("/").pop()||r.split("\\").pop(),i=document.createElement("div");i.className="file-tree-item",i.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(c)}`,i.addEventListener("click",()=>Y(r)),m.appendChild(i)}else m.innerHTML="";let l=s.files||[];for(let r of l){let c=r.split("/").pop()||r.split("\\").pop(),i=In(r),u="",f="";if(i)switch(i){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 S=document.createElement("div");S.className="file-tree-item file"+(u?" "+u:""),S.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${h(c)}`,S.addEventListener("click",()=>Un(r)),m.appendChild(S)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function jn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Un(e){He=e,I.style.display="none",g&&(Ye(),g.destroy(),g=null,y=null),be(null),Qe(),X.style.display="none",ve.style.display="flex",bn.textContent=e,H.textContent="Loading...",J.style.display="none";try{let t=await fetch(C("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let n=await t.json();H.textContent=n.error||"Failed to load",ge.value="";return}let s=await t.json();ge.value=s.content,Pe=s.content,yt=s.content,H.textContent=s.size+" bytes",J.style.display=""}catch{H.textContent="Failed to load",ge.value=""}}ge.addEventListener("input",()=>{Pe=ge.value}),J.addEventListener("click",async()=>{J.textContent="Saving...",J.disabled=!0;try{let e=await fetch(C("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He,content:Pe})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Save failed";return}yt=Pe,H.textContent="Saved"}catch{H.textContent="Save failed"}finally{J.disabled=!1,J.textContent="Save"}}),En.addEventListener("click",async()=>{if(confirm("Delete "+He+"?"))try{let e=await fetch(C("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Delete failed";return}ve.style.display="none",X.style.display="flex",U&&Y(U)}catch{H.textContent="Delete failed"}}),Ln.addEventListener("click",()=>{ve.style.display="none",X.style.display="flex",U&&Y(U)}),xn.addEventListener("click",async()=>{let e=wt.value.trim();if(!e)return;let t=U+"/"+e;try{let s=await fetch(C("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!s.ok){let n=await s.json();H.textContent=n.error||"Create failed";return}wt.value="",U&&Y(U)}catch{H.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=bt.classList.toggle("open");Ze.classList.toggle("open",e)}),Ze.addEventListener("click",()=>{bt.classList.remove("open"),Ze.classList.remove("open")});var D="",z=!1,qe,Ee=document.createElement("div");Ee.className="session-popover-backdrop",document.body.appendChild(Ee);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
37
+ </div><div class="diff-content">${n}</div></div>`,k.querySelector(".diff-back")?.addEventListener("click",()=>{k.innerHTML=Ft,tt(),w&&k.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),i=w.files[c];!i||i.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>Pt(i.path)))}),Dt()})}function Nn(e,t,s){if(!t&&!s){Re.textContent="No diff",$e.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:n,lineCount:l}=Ht(t,s);It.textContent=e,Re.textContent=l+" lines",$e.innerHTML=n}function Rn(){Mt.style.display="none",je?(I.style.display="",I.classList.add("terminal-pending"),ye(je),je=null):y?(I.style.display="",I.classList.add("terminal-pending"),Rt()):X.style.display="flex"}An.addEventListener("click",Rn);function Ue(){k.classList.remove("open"),ze.classList.remove("open")}ze.addEventListener("click",Ue),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Ue(),Oe())});async function Nt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){let n=await s.json();w=n,be(n);return}}}catch{}w=null,be(null)}var _e=null;function Rt(){Ye(),y&&(Nt(y),_e=setInterval(()=>{y&&Nt(y)},3e4))}function Ye(){_e&&(clearInterval(_e),_e=null)}function $n(){if(Ne=me.fsRoots||[],Ne.length===0){if(y){zn(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 e of Ne){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(e)}`,t.addEventListener("click",()=>Y(e)),m.appendChild(t)}}async function zn(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(C("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?Y(t.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function Y(e){U=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Dn(e);try{let s=await(await fetch(C("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",be(w),m.innerHTML+=Fn();let n=jn(e);if(n&&!Ne.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>Y(n)),m.appendChild(r)}if(s.dirs)for(let r of s.dirs){let c=r.split("/").pop()||r.split("\\").pop(),i=document.createElement("div");i.className="file-tree-item",i.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(c)}`,i.addEventListener("click",()=>Y(r)),m.appendChild(i)}else m.innerHTML="";let l=s.files||[];for(let r of l){let c=r.split("/").pop()||r.split("\\").pop(),i=In(r),u="",f="";if(i)switch(i){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 S=document.createElement("div");S.className="file-tree-item file"+(u?" "+u:""),S.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${h(c)}`,S.addEventListener("click",()=>Un(r)),m.appendChild(S)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function jn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Un(e){He=e,I.style.display="none",g&&(Ye(),g.destroy(),g=null,y=null),be(null),Qe(),X.style.display="none",ve.style.display="flex",bn.textContent=e,H.textContent="Loading...",J.style.display="none";try{let t=await fetch(C("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let n=await t.json();H.textContent=n.error||"Failed to load",ge.value="";return}let s=await t.json();ge.value=s.content,Pe=s.content,wt=s.content,H.textContent=s.size+" bytes",J.style.display=""}catch{H.textContent="Failed to load",ge.value=""}}ge.addEventListener("input",()=>{Pe=ge.value}),J.addEventListener("click",async()=>{J.textContent="Saving...",J.disabled=!0;try{let e=await fetch(C("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He,content:Pe})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Save failed";return}wt=Pe,H.textContent="Saved"}catch{H.textContent="Save failed"}finally{J.disabled=!1,J.textContent="Save"}}),En.addEventListener("click",async()=>{if(confirm("Delete "+He+"?"))try{let e=await fetch(C("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Delete failed";return}ve.style.display="none",X.style.display="flex",U&&Y(U)}catch{H.textContent="Delete failed"}}),Ln.addEventListener("click",()=>{ve.style.display="none",X.style.display="flex",U&&Y(U)}),xn.addEventListener("click",async()=>{let e=bt.value.trim();if(!e)return;let t=U+"/"+e;try{let s=await fetch(C("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!s.ok){let n=await s.json();H.textContent=n.error||"Create failed";return}bt.value="",U&&Y(U)}catch{H.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=Et.classList.toggle("open");Ze.classList.toggle("open",e)}),Ze.addEventListener("click",()=>{Et.classList.remove("open"),Ze.classList.remove("open")});var D="",z=!1,qe,Ee=document.createElement("div");Ee.className="session-popover-backdrop",document.body.appendChild(Ee);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
38
38
  <label>Rename session</label>
39
39
  <input type="text" id="sp-name" />
40
40
  </div>
@@ -44,5 +44,5 @@ var hn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
44
44
  </div>
45
45
  <hr class="sp-divider" />
46
46
  <button class="btn primary" id="sp-rebuild" style="display:none">\u91CD\u5EFA\u540C\u540D\u540C\u8DEF\u5F84 session</button>
47
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(P);function _n(e){let t=e.getBoundingClientRect();P.style.top=t.bottom+4+"px",P.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Rt(e,t,s){D=e,z=!!s?.tombstone,qe=s?.agentId;let n=document.getElementById("sp-name");n.value=e;let l=P.querySelector(".sp-field label");l.textContent=z?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=z?"":"none";let c=document.getElementById("sp-delete");c.textContent=z?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let i=document.getElementById("sp-save");i.textContent=z?"\u91CD\u547D\u540D":"Save",Ee.classList.add("open"),P.style.display="block",_n(t),setTimeout(()=>n.focus(),50)}function G(){Ee.classList.remove("open"),P.style.display="none"}Ee.addEventListener("click",G),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===D){G();return}try{let t=z?{oldName:D,newName:e,agentId:qe}:{oldName:D,newName:e};if(!(await fetch(C(z?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;G(),_(),!z&&y===D&&ye(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+D+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{let e=await fetch(C("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:qe})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}G(),_()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(z){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+D+'"\uFF1F'))return;try{if(!(await fetch(C("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:qe})})).ok)return;G(),_()}catch{}return}if(confirm('Delete session "'+D+'"?'))try{if(!(await fetch(C("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D})})).ok)return;G(),y===D&&(g&&(g.destroy(),g=null),y=null,I.style.display="none",X.style.display="flex"),_()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",G);var Yn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{let t=e.target.closest("button[data-key]");if(!t||!g)return;let s=Yn[t.dataset.key];s&&(g.sendInput(s),g.focus())});var N=document.getElementById("mk-input"),$t=document.getElementById("mk-send");function zt(){if(!N||!g)return;let e=N.value;e&&(g.sendInput(e+"\r"),N.value="",N.rows=1,g.focus())}if(N&&(N.addEventListener("input",()=>{N.rows=1;let e=N.value.split(`
48
- `).length;N.rows=Math.min(e,4)}),N.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),zt())})),$t&&$t.addEventListener("click",zt),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var nt=document.getElementById("process-panel"),Le=document.getElementById("proc-list"),qn=document.getElementById("proc-close"),st=document.getElementById("proc-backdrop"),On=document.getElementById("header-ram");function Wn(){Le.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(C("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){Le.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let s of e){let n=s.rss,l=n<1048576?(n/1024).toFixed(0)+"K":(n/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+s.pid+'"><span class="proc-mem">'+s.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+h(s.command)+'">'+h(s.command)+'</span><button class="proc-kill" title="Kill PID '+s.pid+'">&times;</button></div>'}Le.innerHTML=t;for(let s of Le.querySelectorAll(".proc-row")){let n=s.querySelector(".proc-kill");n&&n.addEventListener("click",l=>{let r=l.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(C("/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(()=>{Le.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Kn(){nt.classList.add("open"),st.classList.add("open"),Wn()}function Oe(){nt.classList.remove("open"),st.classList.remove("open")}On.addEventListener("click",e=>{e.stopPropagation(),nt.classList.contains("open")?Oe():Kn()}),qn.addEventListener("click",Oe),st.addEventListener("click",Oe);var Vn=2e3,xe=null,at=null,ne=new Map,se=new Map,Xn=5e3,jt=new Map;function it(e,t){return(t?"a:"+t+":":"l:")+e}function Jn(e,t,s){let n=it(e,t);if(s==="working"){let f=jt.get(n);if(f&&Date.now()<f)return}let l=se.get(n),r=e===y&&(t??null)===B,c=s==="idle"&&l?.state==="working";se.set(n,{session:e,agentId:t,state:s,doneUntil:c?Date.now()+Vn:void 0,unread:c?!r:!1});let i=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',u=m.querySelector(i);u&&We(u,n,se.get(n))}function We(e,t,s){let n=ne.get(t);n&&(clearTimeout(n),ne.delete(t)),e.classList.remove("working","just-done");let l=s.session===y&&(s.agentId??null)===B;s.state==="working"?e.classList.add("working"):l&&s.doneUntil&&Date.now()<s.doneUntil?(e.classList.add("just-done"),ne.set(t,setTimeout(()=>{e.classList.remove("just-done"),ne.delete(t)},Math.max(0,s.doneUntil-Date.now())))):!l&&s.unread&&e.classList.add("just-done")}function ot(){for(let[e,t]of se){let s=t.agentId?'.session-item[data-agent="'+CSS.escape(t.agentId)+'"][data-session="'+CSS.escape(t.session)+'"]':'.session-item[data-session="'+CSS.escape(t.session)+'"]:not([data-agent])',n=m.querySelector(s);n&&We(n,e,t)}}function Gn(e,t){let s=it(e,t),n=se.get(s);if(!n)return;let l=ne.get(s);l&&(clearTimeout(l),ne.delete(s)),n.doneUntil=void 0,n.unread=!1;let r=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=m.querySelector(r);c&&We(c,s,n)}function Ut(){if(xe||at)return;let e=localStorage.getItem("tmux-web-token"),t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{let s=new WebSocket(t);xe=s,s.onmessage=n=>{try{let l=JSON.parse(String(n.data));l&&l.type==="session.activity"&&typeof l.session=="string"&&Jn(l.session,l.agentId||void 0,l.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{xe===s&&(xe=null,at=setTimeout(()=>{at=null,Ut()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{xe=null}}window.__openSession=ye,window.__refreshSidebar=_,_(),Qe(),ot(),Ut(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&ot()}),setInterval(()=>{document.visibilityState==="visible"&&_()},1e4);
47
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(P);function _n(e){let t=e.getBoundingClientRect();P.style.top=t.bottom+4+"px",P.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function nt(e,t,s){D=e,z=!!s?.tombstone,qe=s?.agentId;let n=document.getElementById("sp-name");n.value=e;let l=P.querySelector(".sp-field label");l.textContent=z?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=z?"":"none";let c=document.getElementById("sp-delete");c.textContent=z?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let i=document.getElementById("sp-save");i.textContent=z?"\u91CD\u547D\u540D":"Save",Ee.classList.add("open"),P.style.display="block",_n(t),setTimeout(()=>n.focus(),50)}function G(){Ee.classList.remove("open"),P.style.display="none"}Ee.addEventListener("click",G),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===D){G();return}try{let t=z?{oldName:D,newName:e,agentId:qe}:{oldName:D,newName:e};if(!(await fetch(C(z?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;G(),_(),!z&&y===D&&ye(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+D+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{let e=await fetch(C("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:qe})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}G(),_()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(z){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+D+'"\uFF1F'))return;try{if(!(await fetch(C("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:qe})})).ok)return;G(),_()}catch{}return}if(confirm('Delete session "'+D+'"?'))try{if(!(await fetch(C("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D})})).ok)return;G(),y===D&&(g&&(g.destroy(),g=null),y=null,I.style.display="none",X.style.display="flex"),_()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",G);var Yn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{let t=e.target.closest("button[data-key]");if(!t||!g)return;let s=Yn[t.dataset.key];s&&(g.sendInput(s),g.focus())});var N=document.getElementById("mk-input"),$t=document.getElementById("mk-send");function zt(){if(!N||!g)return;let e=N.value;e&&(g.sendInput(e+"\r"),N.value="",N.rows=1,g.focus())}if(N&&(N.addEventListener("input",()=>{N.rows=1;let e=N.value.split(`
48
+ `).length;N.rows=Math.min(e,4)}),N.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),zt())})),$t&&$t.addEventListener("click",zt),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var st=document.getElementById("process-panel"),Le=document.getElementById("proc-list"),qn=document.getElementById("proc-close"),at=document.getElementById("proc-backdrop"),On=document.getElementById("header-ram");function Wn(){Le.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(C("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){Le.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let s of e){let n=s.rss,l=n<1048576?(n/1024).toFixed(0)+"K":(n/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+s.pid+'"><span class="proc-mem">'+s.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+h(s.command)+'">'+h(s.command)+'</span><button class="proc-kill" title="Kill PID '+s.pid+'">&times;</button></div>'}Le.innerHTML=t;for(let s of Le.querySelectorAll(".proc-row")){let n=s.querySelector(".proc-kill");n&&n.addEventListener("click",l=>{let r=l.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(C("/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(()=>{Le.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Kn(){st.classList.add("open"),at.classList.add("open"),Wn()}function Oe(){st.classList.remove("open"),at.classList.remove("open")}On.addEventListener("click",e=>{e.stopPropagation(),st.classList.contains("open")?Oe():Kn()}),qn.addEventListener("click",Oe),at.addEventListener("click",Oe);var Vn=2e3,xe=null,it=null,ne=new Map,se=new Map,Xn=5e3,jt=new Map;function ot(e,t){return(t?"a:"+t+":":"l:")+e}function Jn(e,t,s){let n=ot(e,t);if(s==="working"){let f=jt.get(n);if(f&&Date.now()<f)return}let l=se.get(n),r=e===y&&(t??null)===B,c=s==="idle"&&l?.state==="working";se.set(n,{session:e,agentId:t,state:s,doneUntil:c?Date.now()+Vn:void 0,unread:c?!r:!1});let i=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',u=m.querySelector(i);u&&We(u,n,se.get(n))}function We(e,t,s){let n=ne.get(t);n&&(clearTimeout(n),ne.delete(t)),e.classList.remove("working","just-done");let l=s.session===y&&(s.agentId??null)===B;s.state==="working"?e.classList.add("working"):l&&s.doneUntil&&Date.now()<s.doneUntil?(e.classList.add("just-done"),ne.set(t,setTimeout(()=>{e.classList.remove("just-done"),ne.delete(t)},Math.max(0,s.doneUntil-Date.now())))):!l&&s.unread&&e.classList.add("just-done")}function lt(){for(let[e,t]of se){let s=t.agentId?'.session-item[data-agent="'+CSS.escape(t.agentId)+'"][data-session="'+CSS.escape(t.session)+'"]':'.session-item[data-session="'+CSS.escape(t.session)+'"]:not([data-agent])',n=m.querySelector(s);n&&We(n,e,t)}}function Gn(e,t){let s=ot(e,t),n=se.get(s);if(!n)return;let l=ne.get(s);l&&(clearTimeout(l),ne.delete(s)),n.doneUntil=void 0,n.unread=!1;let r=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=m.querySelector(r);c&&We(c,s,n)}function Ut(){if(xe||it)return;let e=localStorage.getItem("tmux-web-token"),t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{let s=new WebSocket(t);xe=s,s.onmessage=n=>{try{let l=JSON.parse(String(n.data));l&&l.type==="session.activity"&&typeof l.session=="string"&&Jn(l.session,l.agentId||void 0,l.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{xe===s&&(xe=null,it=setTimeout(()=>{it=null,Ut()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{xe=null}}window.__openSession=ye,window.__refreshSidebar=_,_(),Qe(),lt(),Ut(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&lt()}),setInterval(()=>{document.visibilityState==="visible"&&_()},1e4);
@@ -1,19 +1,19 @@
1
- import{initTerminal as Ue}from"./terminal-core.js";const _=window.__TMUX_WEB_SHELL__;let f=null,u=null;const D=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let h=D;window.__tmuxWebScopeAgent=D;let ae="sessions",I="",q="",V="",ye="";function v(e){return h?"/a/"+encodeURIComponent(h)+e:e}const r=document.getElementById("sidebar-content"),M=document.getElementById("main-placeholder"),b=document.getElementById("terminal-container"),P=document.getElementById("file-editor"),We=document.getElementById("fe-path"),E=document.getElementById("fe-status"),j=document.getElementById("fe-content"),H=document.getElementById("fe-save"),ze=document.getElementById("fe-delete"),Fe=document.getElementById("fe-back"),he=document.getElementById("fe-new-name"),Ge=document.getElementById("fe-new-btn"),le=document.getElementById("settings-backdrop"),be=document.getElementById("settings-popover"),we=document.querySelector(".sidebar");function qe(){return window.matchMedia("(max-width: 640px)").matches}function ce(){qe()&&we.classList.add("collapsed")}function Le(){we.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Le(),Ee("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Le(),Ee("files")});function Ee(e){ae=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"?(re="",B()):at()}let re="";async function B(){if(ae!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===re)return;re=s,Xe(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{const n=window.__openNewSessionModal;typeof n=="function"?n():document.getElementById("new-session-modal")?.classList.add("open")}),ve()}const ke="tmux-web-sidebar-group-collapsed";function Se(){try{return new Set(JSON.parse(localStorage.getItem(ke)||"[]"))}catch{return new Set}}function Ve(e){try{localStorage.setItem(ke,JSON.stringify([...e]))}catch{}}function Je(e,n,t){if(!e)return"\u2026";const s=t&&(e===t||e.startsWith(t+"/"))?"~"+e.slice(t.length):e,i=s.split("/").filter(Boolean);if(i.length===0)return e;for(let o=1;o<=i.length;o++){const a=i.slice(-o).join("/");if(!n.some(l=>!l||l===e?!1:(t&&(l===t||l.startsWith(t+"/"))?"~"+l.slice(t.length):l).split("/").filter(Boolean).slice(-o).join("/")===a))return a}return s}function Ce(e,n,t,s=!0){const i=new Map;for(const c of e){const l=c.path||"";i.has(l)||i.set(l,[]),i.get(l).push(c)}const o=[...i.keys()],a=Se();for(const[c,l]of i){const p=Je(c,o,n),L=a.has(c),y=document.createElement("div");y.className="session-group-header"+(L?" collapsed":""),y.title=c||"No working directory",y.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${d(p)}</span>`,y.addEventListener("click",()=>{const T=Se(),ge=y.nextElementSibling;T.has(c)?(T.delete(c),y.classList.remove("collapsed"),ge?.classList.remove("collapsed")):(T.add(c),y.classList.add("collapsed"),ge?.classList.add("collapsed")),Ve(T)});const oe=document.createElement("div");oe.className="session-group-body"+(L?" collapsed":"");for(const T of l)oe.appendChild(t?Ke(T,t,s):Ye(T));r.appendChild(y),r.appendChild(oe)}}function Ke(e,n,t){const s=document.createElement("div");return s.className="session-item"+(e.name===u&&h===n?" active":"")+(t?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",n),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
1
+ import{initTerminal as Ue}from"./terminal-core.js";const _=window.__TMUX_WEB_SHELL__;let f=null,u=null;const D=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let h=D;window.__tmuxWebScopeAgent=D;let ae="sessions",I="",q="",V="",he="";function v(e){return h?"/a/"+encodeURIComponent(h)+e:e}const r=document.getElementById("sidebar-content"),M=document.getElementById("main-placeholder"),b=document.getElementById("terminal-container"),P=document.getElementById("file-editor"),We=document.getElementById("fe-path"),E=document.getElementById("fe-status"),j=document.getElementById("fe-content"),H=document.getElementById("fe-save"),ze=document.getElementById("fe-delete"),Fe=document.getElementById("fe-back"),be=document.getElementById("fe-new-name"),Ge=document.getElementById("fe-new-btn"),le=document.getElementById("settings-backdrop"),we=document.getElementById("settings-popover"),Le=document.querySelector(".sidebar");function qe(){return window.matchMedia("(max-width: 640px)").matches}function ce(){qe()&&Le.classList.add("collapsed")}function Ee(){Le.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Ee(),ke("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Ee(),ke("files")});function ke(e){ae=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"?(re="",B()):at()}let re="";async function B(){if(ae!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===re)return;re=s,Xe(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{const n=window.__openNewSessionModal;typeof n=="function"?n():document.getElementById("new-session-modal")?.classList.add("open")}),ge()}const Se="tmux-web-sidebar-group-collapsed";function Ce(){try{return new Set(JSON.parse(localStorage.getItem(Se)||"[]"))}catch{return new Set}}function Ve(e){try{localStorage.setItem(Se,JSON.stringify([...e]))}catch{}}function Je(e,n,t){if(!e)return"\u2026";const s=t&&(e===t||e.startsWith(t+"/"))?"~"+e.slice(t.length):e,i=s.split("/").filter(Boolean);if(i.length===0)return e;for(let o=1;o<=i.length;o++){const a=i.slice(-o).join("/");if(!n.some(c=>!c||c===e?!1:(t&&(c===t||c.startsWith(t+"/"))?"~"+c.slice(t.length):c).split("/").filter(Boolean).slice(-o).join("/")===a))return a}return s}function Ie(e,n,t,s=!0){const i=new Map;for(const l of e){const c=l.path||"";i.has(c)||i.set(c,[]),i.get(c).push(l)}const o=[...i.keys()],a=Ce();for(const[l,c]of i){const p=Je(l,o,n),L=a.has(l),y=document.createElement("div");y.className="session-group-header"+(L?" collapsed":""),y.title=l||"No working directory",y.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${d(p)}</span>`,y.addEventListener("click",()=>{const T=Ce(),ye=y.nextElementSibling;T.has(l)?(T.delete(l),y.classList.remove("collapsed"),ye?.classList.remove("collapsed")):(T.add(l),y.classList.add("collapsed"),ye?.classList.add("collapsed")),Ve(T)});const oe=document.createElement("div");oe.className="session-group-body"+(L?" collapsed":"");for(const T of c)oe.appendChild(t?Ke(T,t,s):Ye(T));r.appendChild(y),r.appendChild(oe)}}function Ke(e,n,t){const s=document.createElement("div");return s.className="session-item"+(e.name===u&&h===n?" active":"")+(t?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",n),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
2
2
  <span>${d(e.name)}</span>${t?"":'<span class="meta">offline</span>'}`,t&&s.addEventListener("click",i=>{i.target.closest(".session-edit-btn")||O(e.name,n)}),s}function Ye(e){const n=document.createElement("div");n.className="session-item"+(e.name===u&&!h?" active":""),n.setAttribute("data-session",e.name),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
3
3
  <span>${d(e.name)}</span>
4
4
  <button class="session-edit-btn" data-session="${d(e.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
5
- `,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||O(e.name)});const t=n.querySelector(".session-edit-btn");return t&&t.addEventListener("click",s=>{s.stopPropagation(),_e(e.name,t)}),n}function Xe(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(D?" clickable":""),t.textContent=D?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",D&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t),Ce(n,e.home);const s=e.agents||[];for(const o of s){if(!o.sessions||o.sessions.length===0)continue;const a=o.agentId===h,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!a&&o.online?" clickable":""),c.textContent=o.agentName+(o.online?"":" (offline)"),!a&&o.online&&(c.title="\u6253\u5F00 "+o.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),r.appendChild(c),Ce(o.sessions,e.home,o.agentId,o.online)}const i=e.tombstones||[];if(i.length>0){const o=document.createElement("div");o.className="sidebar-section-label tombstone-label",o.textContent="\u5931\u6548\u4F1A\u8BDD",r.appendChild(o);for(const a of i){const c=document.createElement("div");c.className="session-item session-tombstone",c.setAttribute("data-session",a.name),a.agentId&&c.setAttribute("data-agent",a.agentId);const l=a.agentId?a.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";c.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
6
- <span>${d(a.name)}</span><span class="meta">\u5931\u6548${l}</span>
7
- <button class="session-edit-btn" data-session="${d(a.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;const p=c.querySelector(".session-edit-btn");p&&p.addEventListener("click",L=>{L.stopPropagation(),_e(a.name,p,{tombstone:!0,agentId:a.agentId})}),r.appendChild(c)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function O(e,n){if(u===e&&f&&h===(n||null))return;P.style.display="none",M.style.display="none",b.style.display="",b.classList.add("terminal-pending"),ce(),f&&(te(),f.destroy(),f=null),u=e,h=n||null,window.__tmuxWebScopeAgent=h,window.__tmuxWebCurrentSession=u,bt(e,n);const t=me(e,n);je.set(t,Date.now()+yt);const s=A.get(t);if(s&&s.state==="working"){s.state="idle",s.doneUntil=void 0,s.unread=!1;const i=r.querySelector(n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');i&&ie(i,t,s)}for(const i of r.querySelectorAll(".session-item"))i.classList.toggle("active",i.getAttribute("data-session")===e&&(i.getAttribute("data-agent")||null)===h);try{f=await Ue(b,e,{terminal:_.terminal,scrollback:_.scrollback,theme:_.theme,wsBase:h?"/ws/a/"+encodeURIComponent(h):_.wsBase||"/ws"}),Ae()}catch(i){console.error("[shell] terminal init failed:",i);const o=i&&typeof i=="object"&&"message"in i?i.message:String(i);b.textContent="Failed to open terminal for "+e+": "+o,b.classList.remove("terminal-pending")}}let J=[],m=null,Ie=!1;function Ze(e){if(!m||!m.files.length)return null;const n=m.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of m.files)if(s.path===t)return s.status;return null}async function Qe(e){Ie=!0;try{const n=await fetch(v("/api/git/status?path="+encodeURIComponent(e)));n.ok?m=await n.json():m=null}catch{m=null}finally{Ie=!1}}function et(){if(!m||!m.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=m;let s=`<div class="sidebar-git"><div class="git-branch-badge">
5
+ `,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||O(e.name)});const t=n.querySelector(".session-edit-btn");return t&&t.addEventListener("click",s=>{s.stopPropagation(),pe(e.name,t)}),n}function Xe(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(D?" clickable":""),t.textContent=D?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",D&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t),Ie(n,e.home);const s=e.agents||[];for(const o of s){if(!o.sessions||o.sessions.length===0)continue;const a=o.agentId===h,l=document.createElement("div");l.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!a&&o.online?" clickable":""),l.textContent=o.agentName+(o.online?"":" (offline)"),!a&&o.online&&(l.title="\u6253\u5F00 "+o.agentName,l.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),r.appendChild(l),Ie(o.sessions,e.home,o.agentId,o.online)}const i=e.tombstones||[];if(i.length>0){const o=document.createElement("div");o.className="sidebar-section-label tombstone-label",o.textContent="\u5931\u6548\u4F1A\u8BDD",r.appendChild(o);for(const a of i){const l=document.createElement("div");l.className="session-item session-tombstone",l.setAttribute("data-session",a.name),a.agentId&&l.setAttribute("data-agent",a.agentId);const c=a.agentId?a.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
6
+ <span>${d(a.name)}</span><span class="meta">\u5931\u6548${c}</span>
7
+ <button class="session-edit-btn" data-session="${d(a.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`,l.title="\u5931\u6548\u4F1A\u8BDD\uFF1A\u70B9\u51FB\u7BA1\u7406\uFF08\u91CD\u5EFA/\u91CD\u547D\u540D/\u5220\u9664\uFF09",l.addEventListener("click",()=>{pe(a.name,l,{tombstone:!0,agentId:a.agentId})});const p=l.querySelector(".session-edit-btn");p&&p.addEventListener("click",L=>{L.stopPropagation(),pe(a.name,p,{tombstone:!0,agentId:a.agentId})}),r.appendChild(l)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function O(e,n){if(u===e&&f&&h===(n||null))return;P.style.display="none",M.style.display="none",b.style.display="",b.classList.add("terminal-pending"),ce(),f&&(te(),f.destroy(),f=null),u=e,h=n||null,window.__tmuxWebScopeAgent=h,window.__tmuxWebCurrentSession=u,bt(e,n);const t=ve(e,n);je.set(t,Date.now()+yt);const s=A.get(t);if(s&&s.state==="working"){s.state="idle",s.doneUntil=void 0,s.unread=!1;const i=r.querySelector(n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');i&&ie(i,t,s)}for(const i of r.querySelectorAll(".session-item"))i.classList.toggle("active",i.getAttribute("data-session")===e&&(i.getAttribute("data-agent")||null)===h);try{f=await Ue(b,e,{terminal:_.terminal,scrollback:_.scrollback,theme:_.theme,wsBase:h?"/ws/a/"+encodeURIComponent(h):_.wsBase||"/ws"}),_e()}catch(i){console.error("[shell] terminal init failed:",i);const o=i&&typeof i=="object"&&"message"in i?i.message:String(i);b.textContent="Failed to open terminal for "+e+": "+o,b.classList.remove("terminal-pending")}}let J=[],m=null,Be=!1;function Ze(e){if(!m||!m.files.length)return null;const n=m.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of m.files)if(s.path===t)return s.status;return null}async function Qe(e){Be=!0;try{const n=await fetch(v("/api/git/status?path="+encodeURIComponent(e)));n.ok?m=await n.json():m=null}catch{m=null}finally{Be=!1}}function et(){if(!m||!m.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=m;let s=`<div class="sidebar-git"><div class="git-branch-badge">
8
8
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
9
- ${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&R&&m&&(e.stopPropagation(),He())});const U=document.getElementById("header-git"),Be=document.getElementById("git-diff-view"),xe=document.getElementById("gd-path"),K=document.getElementById("gd-status"),Y=document.getElementById("gd-content"),tt=document.getElementById("gd-back");let R=null;function W(e){if(!e||!e.branch){U.style.display="none",R=null;return}R=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),U.innerHTML=i,U.style.display="block"}U.addEventListener("click",e=>{R&&m&&(e.stopPropagation(),He())});const g=document.getElementById("git-popover"),X=document.getElementById("git-popover-backdrop");function de(){const e=U.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Te(){const e=R,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(Q(),e){ae="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),x(e)}})}let Me="";function He(){if(!m)return;const e=m.files||[];if(!e.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
9
+ ${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&R&&m&&(e.stopPropagation(),Ne())});const U=document.getElementById("header-git"),xe=document.getElementById("git-diff-view"),Te=document.getElementById("gd-path"),K=document.getElementById("gd-status"),Y=document.getElementById("gd-content"),tt=document.getElementById("gd-back");let R=null;function W(e){if(!e||!e.branch){U.style.display="none",R=null;return}R=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),U.innerHTML=i,U.style.display="block"}U.addEventListener("click",e=>{R&&m&&(e.stopPropagation(),Ne())});const g=document.getElementById("git-popover"),X=document.getElementById("git-popover-backdrop");function de(){const e=U.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Me(){const e=R,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(Q(),e){ae="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),x(e)}})}let He="";function Ne(){if(!m)return;const e=m.files||[];if(!e.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
10
10
  <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let n=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let t=0;t<e.length;t++){const s=e[t],i=s.additions||0,o=s.deletions||0;let a="";switch(s.status){case"M":a='<span class="git-file-status M">M</span>';break;case"A":a='<span class="git-file-status A">A</span>';break;case"D":a='<span class="git-file-status D">D</span>';break;case"?":a='<span class="git-file-status ?">?</span>';break;case"R":a='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
11
11
  ${a}<span class="file-path">${d(s.path)}</span>
12
12
  <span class="file-add">${i>0?"+"+i:""}</span>
13
13
  <span class="file-del">${o>0?"-"+o:""}</span>
14
- </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Me=n,g.innerHTML=n,g.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>Re(i.path)))})}de(),g.classList.add("open"),X.classList.add("open"),Te()}function nt(){return window.innerWidth>=1024}function Ne(e,n){const s=(e+(n&&e?`
14
+ </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',He=n,g.innerHTML=n,g.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>$e(i.path)))})}de(),g.classList.add("open"),X.classList.add("open"),Me()}function nt(){return window.innerWidth>=1024}function Re(e,n){const s=(e+(n&&e?`
15
15
  `:"")+n).split(`
16
- `);let i="",o=0,a=0;for(const c of s){const l=c;if(l.startsWith("@@")){const p=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,a=parseInt(p[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(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">${d(l)}</span></div>`);continue}if(l.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(l)}</span></div>`;else if(l.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${d(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(l)}</span></div>`;else{if(!l&&!o&&!a)continue;o++,a++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(l)}</span></div>`}}return{html:i,lineCount:s.length}}let Z=null;async function Re(e){const n=R;if(n)if(Q(),nt()){b.style.display="none",f&&(te(),f.destroy(),f=null,Z=u,u=null),W(null),P.style.display="none",M.style.display="none",Be.style.display="flex",xe.textContent=e,K.textContent="Loading\u2026",Y.innerHTML="";try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();it(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);K.textContent="Error",Y.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
16
+ `);let i="",o=0,a=0;for(const l of s){const c=l;if(c.startsWith("@@")){const p=c.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,a=parseInt(p[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(c)}</span></div>`;continue}if(c.startsWith("diff --git")||c.startsWith("index ")||c.startsWith("--- ")||c.startsWith("+++ ")||c.startsWith("new file")||c.startsWith("deleted file")){c.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(c)}</span></div>`);continue}if(c.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(c)}</span></div>`;else if(c.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${d(c)}</span></div>`;else if(c.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(c)}</span></div>`;else{if(!c&&!o&&!a)continue;o++,a++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(c)}</span></div>`}}return{html:i,lineCount:s.length}}let Z=null;async function $e(e){const n=R;if(n)if(Q(),nt()){b.style.display="none",f&&(te(),f.destroy(),f=null,Z=u,u=null),W(null),P.style.display="none",M.style.display="none",xe.style.display="flex",Te.textContent=e,K.textContent="Loading\u2026",Y.innerHTML="";try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();it(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);K.textContent="Error",Y.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
17
17
  <button class="diff-back">\u2190 Back</button>
18
18
  <span class="diff-filename">${d(e)}</span>
19
19
  <span class="diff-loading">Loading\u2026</span>
@@ -23,11 +23,11 @@ import{initTerminal as Ue}from"./terminal-core.js";const _=window.__TMUX_WEB_SHE
23
23
  </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function st(e,n,t){if(!n&&!t){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
25
  <span class="diff-filename">${d(e)}</span>
26
- </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=Ne(n,t);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
26
+ </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=Re(n,t);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
27
27
  <button class="diff-back">\u2190 Back</button>
28
28
  <span class="diff-filename">${d(e)}</span>
29
29
  <span class="diff-filesize">${i} lines</span>
30
- </div><div class="diff-content">${s}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=Me,de(),m&&g.querySelectorAll(".git-popover-item").forEach(o=>{const a=parseInt(o.dataset.idx||"0",10),c=m.files[a];!c||c.status==="D"||(o.style.cursor="pointer",o.addEventListener("click",()=>Re(c.path)))}),Te()})}function it(e,n,t){if(!n&&!t){K.textContent="No diff",Y.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}=Ne(n,t);xe.textContent=e,K.textContent=i+" lines",Y.innerHTML=s}function ot(){Be.style.display="none",Z?(b.style.display="",b.classList.add("terminal-pending"),O(Z),Z=null):u?(b.style.display="",b.classList.add("terminal-pending"),Ae()):M.style.display="flex"}tt.addEventListener("click",ot);function Q(){g.classList.remove("open"),X.classList.remove("open")}X.addEventListener("click",Q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Q(),se())});async function $e(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();m=i,W(i);return}}}catch{}m=null,W(null)}let ee=null;function Ae(){te(),u&&($e(u),ee=setInterval(()=>{u&&$e(u)},3e4))}function te(){ee&&(clearInterval(ee),ee=null)}function at(){if(J=_.fsRoots||[],J.length===0){if(u){lt(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 J){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",()=>x(e)),r.appendChild(n)}}async function lt(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(v("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?x(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 x(e){I=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=Qe(e);try{const s=await(await fetch(v("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",W(m),r.innerHTML+=et();const i=ct(e);if(i&&!J.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>x(i)),r.appendChild(c)}if(s.dirs)for(const a of s.dirs){const c=a.split("/").pop()||a.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> ${d(c)}`,l.addEventListener("click",()=>x(a)),r.appendChild(l)}else r.innerHTML="";const o=s.files||[];for(const a of o){const c=a.split("/").pop()||a.split("\\").pop(),l=Ze(a);let p="",L="";if(l)switch(l){case"M":p="git-mod",L='<span class="git-file-status M">M</span>';break;case"A":p="git-add",L='<span class="git-file-status A">A</span>';break;case"D":p="git-del",L='<span class="git-file-status D">D</span>';break;case"?":p="git-untracked",L='<span class="git-file-status ?">?</span>';break;case"R":p="git-mod",L='<span class="git-file-status R">R</span>';break}const y=document.createElement("div");y.className="file-tree-item file"+(p?" "+p:""),y.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${L}${d(c)}`,y.addEventListener("click",()=>rt(a)),r.appendChild(y)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function ct(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function rt(e){q=e,b.style.display="none",f&&(te(),f.destroy(),f=null,u=null),W(null),ce(),M.style.display="none",P.style.display="flex",We.textContent=e,E.textContent="Loading...",H.style.display="none";try{const n=await fetch(v("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();E.textContent=s.error||"Failed to load",j.value="";return}const t=await n.json();j.value=t.content,V=t.content,ye=t.content,E.textContent=t.size+" bytes",H.style.display=""}catch{E.textContent="Failed to load",j.value=""}}j.addEventListener("input",()=>{V=j.value}),H.addEventListener("click",async()=>{H.textContent="Saving...",H.disabled=!0;try{const e=await fetch(v("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q,content:V})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Save failed";return}ye=V,E.textContent="Saved"}catch{E.textContent="Save failed"}finally{H.disabled=!1,H.textContent="Save"}}),ze.addEventListener("click",async()=>{if(confirm("Delete "+q+"?"))try{const e=await fetch(v("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Delete failed";return}P.style.display="none",M.style.display="flex",I&&x(I)}catch{E.textContent="Delete failed"}}),Fe.addEventListener("click",()=>{P.style.display="none",M.style.display="flex",I&&x(I)}),Ge.addEventListener("click",async()=>{const e=he.value.trim();if(!e)return;const n=I+"/"+e;try{const t=await fetch(v("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();E.textContent=s.error||"Create failed";return}he.value="",I&&x(I)}catch{E.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=be.classList.toggle("open");le.classList.toggle("open",e)}),le.addEventListener("click",()=>{be.classList.remove("open"),le.classList.remove("open")});let w="",C=!1,ne;const z=document.createElement("div");z.className="session-popover-backdrop",document.body.appendChild(z);const k=document.createElement("div");k.id="session-popover",k.className="session-popover",k.style.display="none",k.innerHTML=`<div class="sp-field">
30
+ </div><div class="diff-content">${s}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=He,de(),m&&g.querySelectorAll(".git-popover-item").forEach(o=>{const a=parseInt(o.dataset.idx||"0",10),l=m.files[a];!l||l.status==="D"||(o.style.cursor="pointer",o.addEventListener("click",()=>$e(l.path)))}),Me()})}function it(e,n,t){if(!n&&!t){K.textContent="No diff",Y.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}=Re(n,t);Te.textContent=e,K.textContent=i+" lines",Y.innerHTML=s}function ot(){xe.style.display="none",Z?(b.style.display="",b.classList.add("terminal-pending"),O(Z),Z=null):u?(b.style.display="",b.classList.add("terminal-pending"),_e()):M.style.display="flex"}tt.addEventListener("click",ot);function Q(){g.classList.remove("open"),X.classList.remove("open")}X.addEventListener("click",Q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Q(),se())});async function Ae(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();m=i,W(i);return}}}catch{}m=null,W(null)}let ee=null;function _e(){te(),u&&(Ae(u),ee=setInterval(()=>{u&&Ae(u)},3e4))}function te(){ee&&(clearInterval(ee),ee=null)}function at(){if(J=_.fsRoots||[],J.length===0){if(u){lt(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 J){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",()=>x(e)),r.appendChild(n)}}async function lt(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(v("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?x(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 x(e){I=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=Qe(e);try{const s=await(await fetch(v("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",W(m),r.innerHTML+=et();const i=ct(e);if(i&&!J.some(l=>e===l)){const l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>x(i)),r.appendChild(l)}if(s.dirs)for(const a of s.dirs){const l=a.split("/").pop()||a.split("\\").pop(),c=document.createElement("div");c.className="file-tree-item",c.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(l)}`,c.addEventListener("click",()=>x(a)),r.appendChild(c)}else r.innerHTML="";const o=s.files||[];for(const a of o){const l=a.split("/").pop()||a.split("\\").pop(),c=Ze(a);let p="",L="";if(c)switch(c){case"M":p="git-mod",L='<span class="git-file-status M">M</span>';break;case"A":p="git-add",L='<span class="git-file-status A">A</span>';break;case"D":p="git-del",L='<span class="git-file-status D">D</span>';break;case"?":p="git-untracked",L='<span class="git-file-status ?">?</span>';break;case"R":p="git-mod",L='<span class="git-file-status R">R</span>';break}const y=document.createElement("div");y.className="file-tree-item file"+(p?" "+p:""),y.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${L}${d(l)}`,y.addEventListener("click",()=>rt(a)),r.appendChild(y)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function ct(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function rt(e){q=e,b.style.display="none",f&&(te(),f.destroy(),f=null,u=null),W(null),ce(),M.style.display="none",P.style.display="flex",We.textContent=e,E.textContent="Loading...",H.style.display="none";try{const n=await fetch(v("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();E.textContent=s.error||"Failed to load",j.value="";return}const t=await n.json();j.value=t.content,V=t.content,he=t.content,E.textContent=t.size+" bytes",H.style.display=""}catch{E.textContent="Failed to load",j.value=""}}j.addEventListener("input",()=>{V=j.value}),H.addEventListener("click",async()=>{H.textContent="Saving...",H.disabled=!0;try{const e=await fetch(v("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q,content:V})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Save failed";return}he=V,E.textContent="Saved"}catch{E.textContent="Save failed"}finally{H.disabled=!1,H.textContent="Save"}}),ze.addEventListener("click",async()=>{if(confirm("Delete "+q+"?"))try{const e=await fetch(v("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Delete failed";return}P.style.display="none",M.style.display="flex",I&&x(I)}catch{E.textContent="Delete failed"}}),Fe.addEventListener("click",()=>{P.style.display="none",M.style.display="flex",I&&x(I)}),Ge.addEventListener("click",async()=>{const e=be.value.trim();if(!e)return;const n=I+"/"+e;try{const t=await fetch(v("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();E.textContent=s.error||"Create failed";return}be.value="",I&&x(I)}catch{E.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=we.classList.toggle("open");le.classList.toggle("open",e)}),le.addEventListener("click",()=>{we.classList.remove("open"),le.classList.remove("open")});let w="",C=!1,ne;const z=document.createElement("div");z.className="session-popover-backdrop",document.body.appendChild(z);const k=document.createElement("div");k.id="session-popover",k.className="session-popover",k.style.display="none",k.innerHTML=`<div class="sp-field">
31
31
  <label>Rename session</label>
32
32
  <input type="text" id="sp-name" />
33
33
  </div>
@@ -37,5 +37,5 @@ import{initTerminal as Ue}from"./terminal-core.js";const _=window.__TMUX_WEB_SHE
37
37
  </div>
38
38
  <hr class="sp-divider" />
39
39
  <button class="btn primary" id="sp-rebuild" style="display:none">\u91CD\u5EFA\u540C\u540D\u540C\u8DEF\u5F84 session</button>
40
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(k);function dt(e){const n=e.getBoundingClientRect();k.style.top=n.bottom+4+"px",k.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function _e(e,n,t){w=e,C=!!t?.tombstone,ne=t?.agentId;const s=document.getElementById("sp-name");s.value=e;const i=k.querySelector(".sp-field label");i.textContent=C?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";const o=document.getElementById("sp-rebuild");o.style.display=C?"":"none";const a=document.getElementById("sp-delete");a.textContent=C?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";const c=document.getElementById("sp-save");c.textContent=C?"\u91CD\u547D\u540D":"Save",z.classList.add("open"),k.style.display="block",dt(n),setTimeout(()=>s.focus(),50)}function N(){z.classList.remove("open"),k.style.display="none"}z.addEventListener("click",N),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===w){N();return}try{const n=C?{oldName:w,newName:e,agentId:ne}:{oldName:w,newName:e};if(!(await fetch(v(C?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).ok)return;N(),B(),!C&&u===w&&O(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+w+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{const e=await fetch(v("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})});if(!e.ok){const n=await e.json();alert(n.error||"\u91CD\u5EFA\u5931\u8D25");return}N(),B()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(C){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+w+'"\uFF1F'))return;try{if(!(await fetch(v("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})})).ok)return;N(),B()}catch{}return}if(confirm('Delete session "'+w+'"?'))try{if(!(await fetch(v("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w})})).ok)return;N(),u===w&&(f&&(f.destroy(),f=null),u=null,b.style.display="none",M.style.display="flex"),B()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",N);const pt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!f)return;const t=pt[n.dataset.key];t&&(f.sendInput(t),f.focus())});const S=document.getElementById("mk-input"),De=document.getElementById("mk-send");function Pe(){if(!S||!f)return;const e=S.value;e&&(f.sendInput(e+"\r"),S.value="",S.rows=1,f.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;const e=S.value.split(`
41
- `).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Pe())})),De&&De.addEventListener("click",Pe),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 pe=document.getElementById("process-panel"),F=document.getElementById("proc-list"),ft=document.getElementById("proc-close"),fe=document.getElementById("proc-backdrop"),ut=document.getElementById("header-ram");function mt(){F.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(v("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){F.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}F.innerHTML=n;for(const t of F.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const o=i.target.closest(".proc-row");!o||!confirm("Kill PID "+o.dataset.pid+"?")||fetch(v("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(o.dataset.pid,10)})}).then(a=>a.json()).then(a=>{a.ok?o.style.opacity="0.3":alert("Failed: "+(a.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{F.innerHTML='<div class="proc-empty">Failed to load</div>'})}function vt(){pe.classList.add("open"),fe.classList.add("open"),mt()}function se(){pe.classList.remove("open"),fe.classList.remove("open")}ut.addEventListener("click",e=>{e.stopPropagation(),pe.classList.contains("open")?se():vt()}),ft.addEventListener("click",se),fe.addEventListener("click",se);const gt=2e3;let G=null,ue=null;const $=new Map,A=new Map,yt=5e3,je=new Map;function me(e,n){return(n?"a:"+n+":":"l:")+e}function ht(e,n,t){const s=me(e,n);if(t==="working"){const p=je.get(s);if(p&&Date.now()<p)return}const i=A.get(s),o=e===u&&(n??null)===h,a=t==="idle"&&i?.state==="working";A.set(s,{session:e,agentId:n,state:t,doneUntil:a?Date.now()+gt:void 0,unread:a?!o:!1});const c=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',l=r.querySelector(c);l&&ie(l,s,A.get(s))}function ie(e,n,t){const s=$.get(n);s&&(clearTimeout(s),$.delete(n)),e.classList.remove("working","just-done");const i=t.session===u&&(t.agentId??null)===h;t.state==="working"?e.classList.add("working"):i&&t.doneUntil&&Date.now()<t.doneUntil?(e.classList.add("just-done"),$.set(n,setTimeout(()=>{e.classList.remove("just-done"),$.delete(n)},Math.max(0,t.doneUntil-Date.now())))):!i&&t.unread&&e.classList.add("just-done")}function ve(){for(const[e,n]of A){const t=n.agentId?'.session-item[data-agent="'+CSS.escape(n.agentId)+'"][data-session="'+CSS.escape(n.session)+'"]':'.session-item[data-session="'+CSS.escape(n.session)+'"]:not([data-agent])',s=r.querySelector(t);s&&ie(s,e,n)}}function bt(e,n){const t=me(e,n),s=A.get(t);if(!s)return;const i=$.get(t);i&&(clearTimeout(i),$.delete(t)),s.doneUntil=void 0,s.unread=!1;const o=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',a=r.querySelector(o);a&&ie(a,t,s)}function Oe(){if(G||ue)return;const e=localStorage.getItem("tmux-web-token");let t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{const s=new WebSocket(t);G=s,s.onmessage=i=>{try{const o=JSON.parse(String(i.data));o&&o.type==="session.activity"&&typeof o.session=="string"&&ht(o.session,o.agentId||void 0,o.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{G===s&&(G=null,ue=setTimeout(()=>{ue=null,Oe()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{G=null}}window.__openSession=O,window.__refreshSidebar=B,B(),ce(),ve(),Oe(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&ve()}),setInterval(()=>{document.visibilityState==="visible"&&B()},1e4);
40
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(k);function dt(e){const n=e.getBoundingClientRect();k.style.top=n.bottom+4+"px",k.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function pe(e,n,t){w=e,C=!!t?.tombstone,ne=t?.agentId;const s=document.getElementById("sp-name");s.value=e;const i=k.querySelector(".sp-field label");i.textContent=C?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";const o=document.getElementById("sp-rebuild");o.style.display=C?"":"none";const a=document.getElementById("sp-delete");a.textContent=C?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";const l=document.getElementById("sp-save");l.textContent=C?"\u91CD\u547D\u540D":"Save",z.classList.add("open"),k.style.display="block",dt(n),setTimeout(()=>s.focus(),50)}function N(){z.classList.remove("open"),k.style.display="none"}z.addEventListener("click",N),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===w){N();return}try{const n=C?{oldName:w,newName:e,agentId:ne}:{oldName:w,newName:e};if(!(await fetch(v(C?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).ok)return;N(),B(),!C&&u===w&&O(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+w+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{const e=await fetch(v("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})});if(!e.ok){const n=await e.json();alert(n.error||"\u91CD\u5EFA\u5931\u8D25");return}N(),B()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(C){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+w+'"\uFF1F'))return;try{if(!(await fetch(v("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})})).ok)return;N(),B()}catch{}return}if(confirm('Delete session "'+w+'"?'))try{if(!(await fetch(v("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w})})).ok)return;N(),u===w&&(f&&(f.destroy(),f=null),u=null,b.style.display="none",M.style.display="flex"),B()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",N);const pt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!f)return;const t=pt[n.dataset.key];t&&(f.sendInput(t),f.focus())});const S=document.getElementById("mk-input"),De=document.getElementById("mk-send");function Pe(){if(!S||!f)return;const e=S.value;e&&(f.sendInput(e+"\r"),S.value="",S.rows=1,f.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;const e=S.value.split(`
41
+ `).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Pe())})),De&&De.addEventListener("click",Pe),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 fe=document.getElementById("process-panel"),F=document.getElementById("proc-list"),ft=document.getElementById("proc-close"),ue=document.getElementById("proc-backdrop"),ut=document.getElementById("header-ram");function mt(){F.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(v("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){F.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}F.innerHTML=n;for(const t of F.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const o=i.target.closest(".proc-row");!o||!confirm("Kill PID "+o.dataset.pid+"?")||fetch(v("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(o.dataset.pid,10)})}).then(a=>a.json()).then(a=>{a.ok?o.style.opacity="0.3":alert("Failed: "+(a.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{F.innerHTML='<div class="proc-empty">Failed to load</div>'})}function vt(){fe.classList.add("open"),ue.classList.add("open"),mt()}function se(){fe.classList.remove("open"),ue.classList.remove("open")}ut.addEventListener("click",e=>{e.stopPropagation(),fe.classList.contains("open")?se():vt()}),ft.addEventListener("click",se),ue.addEventListener("click",se);const gt=2e3;let G=null,me=null;const $=new Map,A=new Map,yt=5e3,je=new Map;function ve(e,n){return(n?"a:"+n+":":"l:")+e}function ht(e,n,t){const s=ve(e,n);if(t==="working"){const p=je.get(s);if(p&&Date.now()<p)return}const i=A.get(s),o=e===u&&(n??null)===h,a=t==="idle"&&i?.state==="working";A.set(s,{session:e,agentId:n,state:t,doneUntil:a?Date.now()+gt:void 0,unread:a?!o:!1});const l=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=r.querySelector(l);c&&ie(c,s,A.get(s))}function ie(e,n,t){const s=$.get(n);s&&(clearTimeout(s),$.delete(n)),e.classList.remove("working","just-done");const i=t.session===u&&(t.agentId??null)===h;t.state==="working"?e.classList.add("working"):i&&t.doneUntil&&Date.now()<t.doneUntil?(e.classList.add("just-done"),$.set(n,setTimeout(()=>{e.classList.remove("just-done"),$.delete(n)},Math.max(0,t.doneUntil-Date.now())))):!i&&t.unread&&e.classList.add("just-done")}function ge(){for(const[e,n]of A){const t=n.agentId?'.session-item[data-agent="'+CSS.escape(n.agentId)+'"][data-session="'+CSS.escape(n.session)+'"]':'.session-item[data-session="'+CSS.escape(n.session)+'"]:not([data-agent])',s=r.querySelector(t);s&&ie(s,e,n)}}function bt(e,n){const t=ve(e,n),s=A.get(t);if(!s)return;const i=$.get(t);i&&(clearTimeout(i),$.delete(t)),s.doneUntil=void 0,s.unread=!1;const o=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',a=r.querySelector(o);a&&ie(a,t,s)}function Oe(){if(G||me)return;const e=localStorage.getItem("tmux-web-token");let t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{const s=new WebSocket(t);G=s,s.onmessage=i=>{try{const o=JSON.parse(String(i.data));o&&o.type==="session.activity"&&typeof o.session=="string"&&ht(o.session,o.agentId||void 0,o.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{G===s&&(G=null,me=setTimeout(()=>{me=null,Oe()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{G=null}}window.__openSession=O,window.__refreshSidebar=B,B(),ce(),ge(),Oe(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&ge()}),setInterval(()=>{document.visibilityState==="visible"&&B()},1e4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "2.2.25",
3
+ "version": "2.2.26",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {