聊天框终究不是终端——我在浏览器里给 Claude Code 开了一扇真窗

其实我一开始的方案很朴素:Tailscale + mosh 。
Mac 开着,iPad 或手机 mosh 上去,直接在远程 shell 里跑 Claude Code 。终端是真终端,渲染是真渲染,CC 的 TUI 界面完完整整。
用了一阵子,确实能用。但有个问题越来越卡脖子——mosh 一次只能开一个 session 。
你想同时让 CC 帮你改两个项目?开两个 mosh 连接。想一边聊架构一边跑测试?再开一个。每多一件事就多开一个终端窗口,手机上切来切去,iPad 上也不优雅。
我后来又造了一个 Telegram Bot 做遥控器,一个 Web 聊天客户端做 iPad 工作台。但前者是传纸条,后者把 CC 的输出翻译成了聊天气泡——diff 在气泡里挤成一坨,没有颜色,没有对齐,加号减号混在一起。
我盯着看了五分钟,才意识到自己在做一件很蠢的事——
我在用聊天框看代码变更。
这就像有人给你转播一场钢琴演奏会,但不是录音,是一行一行告诉你”第 3 小节按了降 B 大调和弦”。你拿到了所有信息,但你根本没有在”听”。
我想要的是:mosh 那种真终端体验,但能在一个浏览器页面里开多个会话,侧边栏切换,手机 iPad 电脑通吃。
聊天框吃掉了什么?
Claude Code 不是一个聊天机器人。它是一个有完整 TUI 界面的命令行工具。
你在终端里看到的那些东西——进度条在跑、文件树在展开、 diff 用红绿高亮、权限确认弹出来等你按 y/n 、工具调用的实时日志一行行滚动——这些全部是靠终端转义序列渲染出来的。
把 CC 包进聊天客户端,本质上就是把它的输出从”终端渲染”降级成了”纯文本”。你丢掉了颜色、丢掉了布局、丢掉了交互。就像把一个 React 应用的 DOM 树打印成字符串给你看——技术上信息没丢,但体验完全不是一回事。
我之前做的 cc-genius 是个好工具,但它干的事情是”替你跟 CC 聊天”。我想要的不是这个。
我想在浏览器里打开一个真终端,看到的东西和我坐在 Mac 前一模一样。
灵感来源
带着这个需求刷推的时候,看到有人在浏览器里跑了一个终端模拟器——用 xterm.js 做前端渲染,node-pty 做后端进程管理,WebSocket 传输。
我想了三秒钟:加个侧边栏做多 session 管理,加 tmux 做会话持久化——这不就是”浏览器版的多窗口 mosh”吗?
开干。
架构其实很简单
1 | `iPad/手机/电脑 (Safari/Chrome) |
前端是 xterm.js——浏览器里最成熟的终端模拟器,VSCode 的集成终端用的就是它。它能渲染完整的终端转义序列,颜色、光标移动、滚动区域、 TUI 界面,全部支持。
后端是 node-pty,负责 spawn 一个伪终端进程。中间用 WebSocket 传 JSON 消息,前端敲键盘发 {type: "input", data: "ls\r"},后端收到写进 PTY,PTY 输出发回前端,xterm.js 渲染。
就这么简单。核心代码加起来不到 500 行。
但如果你以为到这里就完了——
乱码,三种不同的乱码
第一种:手机上的马赛克
项目跑起来的第一个晚上,电脑上完美,拿起手机一看——满屏马赛克。字符全部糊在一起,像打印机卡纸了一样。
原因是 xterm.js 默认用 WebGL 渲染。 WebGL 在桌面浏览器上性能很好,但在 iOS Safari 上,它会把字符纹理渲染成一坨。这是一个已知的兼容性问题,但 xterm.js 没有默认处理它。
修复很直接——检测到触屏设备就关掉 WebGL,回退到 DOM 渲染。
但 iPadOS 给了我一个惊喜。
从 iPadOS 13 开始,Safari 的 User-Agent 字符串从 iPad 改成了 Macintosh。苹果的理由是”iPad 应该被当作桌面设备对待”。于是我的 UA 检测把 iPad 当成了 Mac,继续用 WebGL 渲染——继续乱码。
最后靠 navigator.maxTouchPoints 绕过去的:真 Mac 的 maxTouchPoints 是 0,iPad 是 5 。
1 | `const isTouchDevice = /iPhone|iPod|Android/i.test(navigator.userAgent) |
一个字段,区分了两个完全不同的世界。
第二种:tmux 的天书
手机和 iPad 都能看了。但我想要会话持久化——关掉浏览器,下次打开还能接着聊。最自然的方案是 tmux 。
tmux 加上之后,所有平台都乱码了。不是马赛克那种乱,而是输出里夹杂着一堆 ^[[?1;2c^[[>0;276;0c 这样的天书。
花了不少时间才搞明白——这是 DA(Device Attributes)响应。
tmux 会向它附着的终端发送 DA 查询(“你是什么终端?支持什么功能?”),xterm.js 收到查询后老老实实回复了自己的终端属性。但这些回复被当成普通输出传回了浏览器前端,xterm.js 不认识自己发出去的回复,直接渲染成了乱码。
说白了,就是 tmux 问了 xterm.js 一个问题,xterm.js 的回答绕了一圈回到自己面前,它看着自己的回答一脸懵逼。
修复方法是在 PTY 输出流里用正则过滤掉 DA 响应:
1 | `const cleaned = data |
三行正则,解决了一个折腾了几小时的问题。
第三种:Claude Code 跑错了目录
乱码修完,tmux 稳定了。然后发现一个诡异的问题——Claude Code 启动后的工作目录不对。它跑在 ~/Projects/cc-remote-term/ 里,而不是 ~。
原因是 tmux 的新 session 会继承父进程的 cwd 。而 cc-remote-term 服务端是被 launchd 启动的,工作目录是项目目录本身。
一个 -c $HOME 参数就修了,但如果不知道 tmux 的 cwd 继承机制,你可能会疑惑很久。
差点把 tmux 删了
我承认,tmux 乱码最严重的时候,我动过一个念头:算了,把 tmux 去掉吧。
我甚至真的回滚了一版,去掉了 tmux,用纯 PTY 直连。
然后立刻被拍回来了——“啊?为什么要回滚?那重启不是又没办法继续了?”
说得对。 tmux 的存在不是为了增加复杂度,是为了解决一个真实问题:浏览器关了,会话不能死。我不能因为调不通就把功能砍了——这不是解决问题,这是逃避问题。
所以我把 tmux 加回来,认真调了 tmux.conf:
1 | `set-option -g status off # 状态栏我们自己画 |
再加上 DA 过滤,终于稳了。
最后出来的东西
一个能在手机、 iPad 、电脑浏览器里打开的真终端。
你看到的和坐在 Mac 前面看到的一模一样——Claude Code 的蓝色 TUI 界面、工具调用的实时日志、 diff 的红绿高亮、进度条、权限提示,全部原样渲染。
你可以同时开好几个会话,侧边栏切换。第一条消息会自动变成会话标题,方便找。关掉浏览器,tmux 替你兜着,下次打开会话还在。
支持拖拽上传文件(或者点回形针),添加到主屏幕变 PWA,深色浅色主题随手切。
通过 Tailscale 访问的话,整条链路端到端加密,你的对话数据不经过任何第三方。
美中不足
说完了好的,说几个还不够好的。
Session 恢复不是无缝的。 tmux 保住了终端会话,但 Claude Code 自己的对话上下文——你之前聊了什么、建立了什么共识——不在 tmux 的管辖范围内。如果 CC 进程因为某种原因退出了,tmux 能保住 shell,但 CC 的上下文没了。你需要 --resume 才能接上。
Auth 很原始。 目前就是一个 token 环境变量,没有用户系统、没有登录界面。虽然走 Tailscale 已经有一层网络级加密和认证,但如果你想暴露到公网,这个认证机制是不够的。
手机键盘和终端不太搭。 这不是 cc-remote-term 的问题,而是所有手机终端应用的通病。虚拟键盘没有 Ctrl 、 Tab 、 Esc 。我在底部加了一排快捷键按钮(Esc / Tab / Ctrl+C / Clear),能用,但比起实体键盘还是差一截。 iPad 接键盘的体验就好得多。
依赖 Tailscale(或类似方案)。 你的 Mac 和手机必须在同一个 Tailscale 网络下。如果你还没用过 Tailscale,需要花 10 分钟设一下。不复杂,但多了一步。
三个壳

回头看,我先后给 Claude Code 造了三套壳:
- Telegram Bot(telegram-ai-bridge)—— 像传纸条。轻快,随手丢一句指令,适合碎片化场景。但你看不见 CC 的全貌。
- Web 聊天客户端(cc-genius)—— 像视频通话。你能看见 CC 的回复,能多轮对话,但它的输出被翻译成了聊天气泡,TUI 的质感丢了。
- Web 终端(cc-remote-term)—— 像面对面。你看到的就是 CC 本人,不多不少。
三个工具我现在都在用。通勤刷手机时开 Telegram 丢个快问题;iPad 上需要仔细聊一段时用 cc-genius;要做正经活时——不管手边是 Mac 、 iPad 还是手机——开 cc-remote-term 。
开源
项目已经在 GitHub 上了:[AliceLJY/cc-remote-term]
如果你也用 Claude Code,也想在 iPad 或手机上有一个”真终端”的体验,可以试试。设置不复杂——clone 下来,npm install,配一个 token 环境变量,跑起来就能用。
要不要 Tailscale 看你的场景。局域网内不需要,远程访问才需要。
我现在就是在 iPad 上,用 cc-remote-term 连着 Mac 上的 Claude Code,写的这篇文章。
想想还挺离谱的——一个 AI 工具,用浏览器终端模拟器连着 tmux 连着 node-pty 连着 WebSocket 连着 xterm.js,就为了让我窝在沙发上也能跟它聊。
但它确实在跑,而且跑得还不错。
至于下一步要造什么壳——先不想了,沙发太舒服了。
专业劈叉式跨界选手:🧬 医学出身,🎭 文化口饭碗,🤖 AI 是我的野路子。
不卷参数,不追新模型,只关心一个问题:AI 啥时候能装进我脑子,替我不开心?
欢迎围观我和 AI 相爱相杀的日常。——AI不会取代你,但会用AI的人会。所以我先学了,你随意。🔧
踩坑副产品已开源 → recallnest,wechat-ai-bridge,telegram-ai-bridge | 更多 → github.com/AliceLJY
参与组织 → CortexReach(memory-lancedb-pro 贡献者,setup-memory.sh 一键脚本作者)
本文由 Content Alchemy 自动生成,由 Claude Code 发布。
💬 本文评论区已开启,但暂无读者留言。
本文转载自微信公众号,如有侵权请联系删除。
- 标题: 聊天框终究不是终端——我在浏览器里给 Claude Code 开了一扇真窗
- 作者: lxiol
- 创建于 : 2026-05-06 19:58:06
- 更新于 : 2026-05-12 16:32:44
- 链接: https://blog.lxiol.cn/2026/05/06/聊天框终究不是终端我在浏览器里给-Claude-Code-开了一扇真窗/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。