自托管 · tmux web 客户端

它跑完了,
而你不在电脑前。

Claude Code 想一轮要几分钟。tmux-next 把那几分钟还给你—— 在手机上看到每个会话的最后几行输出,谁在等你回复一眼就知道, 点进去接着聊。


为什么

终端是唯一没跟着你走的东西

邮件、日历、聊天早就在手机上了。可你让 Claude Code 跑一个重构, 它需要五分钟,而这五分钟里你只能守着笔记本——不是因为有事要做, 是因为你不知道它什么时候需要你

tmux 本来就是解药:进程留在服务器上,随时可以再连回去。 缺的只是一个能装进口袋的客户端。

长什么样

两个界面,就这些

会话列表:三个会话,每个显示最后几行输出;等待回复的带绿点,未发送的输入单独一行。

绿点是「在等你回复」。Claude Code 打完一轮会输出 ✻ Cogitated for …, 列表据此把等你的会话排到最前面——不用一个个点进去看。

能做什么

三件事

看到状态,不用点开

每个会话显示最后几行输出、还没发出去的输入、最后活跃时间。等你回复的排在最前。

从手机开新会话

点选目录一路往下钻,可以起名,也可以选择让 Claude 跳过权限确认直接跑。

断了就重建

锁屏、进地铁、换 Wi-Fi。不缓冲也不重放——重连时从 tmux 重新抓一次完整画面。

怎么做到的

用 control mode,不是起一个 PTY

tmux 有一个讲结构化协议的模式。用它,输出就是按 pane 分发的, 于是浏览器里看到的是程序本身的画面——没有 tmux 的分屏边框,没有状态栏。

浏览器 xterm.js │ WebSocket ▼ Bun 服务 127.0.0.1 │ tmux -C attach (control mode)tmux server ──┬── 你的会话 PROJ-1042 └── 你的会话 billing-ci 每个浏览器连接建一个 web-<uuid> grouped session 当作可销毁的挂载点,断开即销毁。

尺寸是共享的

window 尺寸是 window 的属性,同一个 window 无法对两个客户端渲染成两种宽度。 这是 tmux 的事实,不是缺陷。断开时调 resize-window -A 把尺寸还给剩下的客户端。

一条规则覆盖两种屏幕

先算「让 80 列铺满需要多大字号」,没超上限就用它——手机因此永远是 80 列。 超过上限(约 576px 宽)字号钉死,多余宽度换成列数。没有设备嗅探,切换点是算出来的。

拖动滚的是程序

tmux 原地重绘屏幕,从不让行滚出顶部,所以 xterm 的滚动缓冲永远是空的。 手势被翻译成合成的 WheelEvent,由 xterm 按程序协商好的鼠标协议编码; 程序不理会鼠标时一个字节都不会发出去,而这个「零输出」正好被当作信号,退化成 PgUp/PgDn。

命令是常量

启动命令会经过 sh -c,所以任何来自请求的东西都不允许拼进去。 目录作为独立 argv 传递,「跳过权限确认」是在两个固定字符串之间二选一,而不是拼接。

规模

小到可以整个读完

201个测试,大多真的起 tmux
0个前端框架
0个构建步骤
~3.9k行 TypeScript / JS