{"id":51286687,"url":"https://github.com/wangjs-jacky/claude-web-terminal","last_synced_at":"2026-06-30T06:03:52.167Z","repository":{"id":366499484,"uuid":"1276531869","full_name":"wangjs-jacky/claude-web-terminal","owner":"wangjs-jacky","description":"把 Mac mini 上的 Claude Code 通过 node-pty + xterm.js 投射到手机浏览器，tmux 持久化会话，经 Tailscale 私网访问","archived":false,"fork":false,"pushed_at":"2026-06-22T04:31:29.000Z","size":23,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T06:18:02.824Z","etag":null,"topics":["claude-code","mobile","node-pty","tailscale","tmux","web-terminal","xterm"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wangjs-jacky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-22T04:15:17.000Z","updated_at":"2026-06-22T04:31:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/wangjs-jacky/claude-web-terminal","commit_stats":null,"previous_names":["wangjs-jacky/claude-web-terminal"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/wangjs-jacky/claude-web-terminal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangjs-jacky%2Fclaude-web-terminal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangjs-jacky%2Fclaude-web-terminal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangjs-jacky%2Fclaude-web-terminal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangjs-jacky%2Fclaude-web-terminal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wangjs-jacky","download_url":"https://codeload.github.com/wangjs-jacky/claude-web-terminal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangjs-jacky%2Fclaude-web-terminal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34954286,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","mobile","node-pty","tailscale","tmux","web-terminal","xterm"],"created_at":"2026-06-30T06:03:50.757Z","updated_at":"2026-06-30T06:03:52.161Z","avatar_url":"https://github.com/wangjs-jacky.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# claude-web-terminal\n\n\u003e 把 Mac mini 上的 **Claude Code** 投射到手机浏览器：手机打开一个地址，就能像坐在电脑前一样交互操作。基于 `node-pty` + `xterm.js`，用 `tmux` 持久化会话，经 **Tailscale** 私网安全访问。\n\n![移动端工具条](https://img.shields.io/badge/mobile-toolbar-blue) ![tmux](https://img.shields.io/badge/session-tmux-green) ![tailscale](https://img.shields.io/badge/network-tailscale-orange)\n\n## 这是什么\n\n一个跑在 Mac mini 上的轻量 Node 服务：\n\n- **后端**：`node-pty` 包裹 `tmux attach`，把终端字节流通过 WebSocket 双向转发\n- **前端**：`xterm.js` 渲染终端 + 一套移动端友好的工具条（进入CC / 图片 / 清屏 / Tab / Esc / ^C / 方向键 / 翻页）\n- **会话**：固定 `tmux` 会话名，手机断线重连回到原会话，**Claude Code 不中断**\n- **网络**：手机装 Tailscale，走私有 IP，无需暴露公网端口\n\n```\n手机浏览器 (xterm.js + 工具条)\n      │  WebSocket（Tailscale 私网）\n      ▼\nMac mini: Node 服务 (express + ws)\n      │  node-pty\n      ▼\n  tmux attach -t claude   ←── 断线重连不丢\n      │\n   claude --dangerously-skip-permissions\n```\n\n## 准备工作\n\n| 依赖 | 说明 |\n|------|------|\n| Node.js ≥ 18 | 运行服务 |\n| tmux | 会话持久化（`brew install tmux`） |\n| claude（Claude Code CLI） | 被投射的目标 |\n| Tailscale | Mac mini 与手机都登录同一账号 |\n\n## 快速开始\n\n### 1. 安装\n\n```bash\ngit clone https://github.com/wangjs-jacky/claude-web-terminal.git\ncd claude-web-terminal\nnpm install\n```\n\n\u003e `node-pty` 是原生模块，安装时会自动编译，需要 Xcode Command Line Tools。\n\n### 2. 启动\n\n```bash\nnpm start\n```\n\n看到下面输出即成功：\n\n```\nclaude-web-terminal listening on http://0.0.0.0:7681\n  tmux session : claude\n  claude cmd   : claude --dangerously-skip-permissions\n```\n\n### 3. 手机访问\n\n1. 手机安装并登录 **Tailscale**（与 Mac mini 同账号）\n2. 在 Mac mini 上查看 Tailscale IP：`tailscale ip -4`（形如 `100.x.x.x`）\n3. 手机浏览器打开 `http://100.x.x.x:7681`\n\n搞定 —— 你的 Claude Code 就投射到手机上了。\n\n## 安装为 App（PWA，需 HTTPS）\n\n浏览器规定 **Service Worker / PWA 安装必须运行在 HTTPS 安全上下文**，纯 `http://100.x.x.x` 无法安装（但终端功能、触屏滚动都正常）。\n\n方案：用 **Tailscale 签发的可信证书**（Let's Encrypt），让本服务**直接**监听一个 HTTPS 端口（如 8443）。比 `tailscale serve` 更可控、可本地验证。\n\n\u003e 前提：Tailscale 管理后台已开启「HTTPS Certificates」+「MagicDNS」（默认多已开）。\n\n```bash\n# 1. 查出本机 .ts.net 域名\nTS_DOMAIN=$(tailscale status --json | python3 -c \"import sys,json;print(json.load(sys.stdin)['Self']['DNSName'].rstrip('.'))\")\necho \"$TS_DOMAIN\"   # 形如 jackymac-mini.tailxxxx.ts.net\n\n# 2. 签发证书到 certs/（已被 .gitignore 忽略，不会提交）\nmkdir -p certs\ntailscale cert --cert-file certs/cert.crt --key-file certs/cert.key \"$TS_DOMAIN\"\n\n# 3. 带 HTTPS 端口启动\nHTTPS_PORT=8443 npm start\n```\n\n启动日志会多出一行 `https : https://0.0.0.0:8443 (cert: ...)`。手机浏览器打开：\n\n```\nhttps://\u003c你的.ts.net域名\u003e:8443\n```\n\n打开后用浏览器菜单「**添加到主屏幕 / 安装应用**」，即可像原生 App 一样全屏启动。\n\n\u003e **证书续期**：Tailscale 证书有效期约 90 天。重新运行第 2 步的 `tailscale cert` 即可刷新（可加 cron 每月跑一次）。\n\u003e\n\u003e **为什么不用 `tailscale serve`**：serve 代理 HTTPS 在部分环境下对自身 tailnet IP 存在自连/握手问题，本方案由 Node 直接持证书监听，链路更短也更易排查。\n\n## 移动端手势\n\n| 手势 | 效果 |\n|------|------|\n| **上下滑动终端区** | 滚动查看历史输出（已禁用浏览器下拉刷新） |\n| **轻点终端区** | 唤起软键盘 |\n| **工具条按钮** | 见下表 |\n\n## 工具条说明\n\n| 按钮 | 作用 |\n|------|------|\n| **发送 / ↵** | 文本框内容发送到终端；`发送`不带回车，`↵`回车提交 |\n| **进入CC** | 发送 `claude --dangerously-skip-permissions` 启动 Claude Code |\n| **📷 图片** | 选手机图片上传到 Mac mini，自动把路径塞进终端供 Claude 识别 |\n| **清屏** | 清空当前终端显示 |\n| **⌨ 键盘** | 唤起手机软键盘（聚焦终端） |\n| **↵ / Tab / Esc / ^C / ^C×2** | 对应控制字符 |\n| **← ↑ ↓ → / Home / End** | 方向键与行首行尾 |\n| **↑上翻 / ↓下翻 / ⇊到底** | 滚动查看历史输出 |\n\n## 配置项（环境变量）\n\n| 变量 | 默认值 | 说明 |\n|------|--------|------|\n| `PORT` | `7681` | 监听端口 |\n| `HOST` | `0.0.0.0` | 监听网卡（Tailscale 场景靠 ACL 限制来源） |\n| `TMUX_SESSION` | `claude` | tmux 会话名 |\n| `CLAUDE_CMD` | `claude --dangerously-skip-permissions` | 会话内启动命令 |\n| `TOKEN` | 空 | 非空时要求 URL 带 `?token=xxx` 才能访问 |\n| `UPLOAD_DIR` | `~/claude-web-uploads` | 图片上传落盘目录 |\n| `HTTPS_PORT` | 空 | 设为如 `8443` 时额外监听 HTTPS（PWA 用） |\n| `CERT_FILE` | `certs/cert.crt` | HTTPS 证书路径 |\n| `KEY_FILE` | `certs/cert.key` | HTTPS 私钥路径 |\n\n示例：\n\n```bash\nPORT=8080 TOKEN=my-secret npm start\n# 访问 http://100.x.x.x:8080/?token=my-secret\n```\n\n## 开机自启（macOS launchd）\n\n1. 编辑 `launchd.plist`，把 `__DIR__` 改为项目绝对路径，`__NODE__` 改为 `which node` 的结果\n2. 安装：\n\n```bash\ncp launchd.plist ~/Library/LaunchAgents/com.jacky.claude-web-terminal.plist\nlaunchctl load -w ~/Library/LaunchAgents/com.jacky.claude-web-terminal.plist\n```\n\n卸载：\n\n```bash\nlaunchctl unload ~/Library/LaunchAgents/com.jacky.claude-web-terminal.plist\n```\n\n## 安全说明\n\n- 默认仅在 Tailscale 私网内可达，**不要**把 `7681` 端口转发到公网\n- 如担心同 tailnet 其他设备，开启 `TOKEN` 做一层简单校验\n- `--dangerously-skip-permissions` 会跳过 Claude Code 的权限确认，请确保只有你自己能访问\n\n## 工作原理\n\n1. 服务启动时 `tmux has-session`，不存在就 `tmux new-session -d` 起一个会话并在里面跑 `claude`\n2. 每个 WebSocket 连接通过 `node-pty` 执行 `tmux attach`，成为该会话的一个客户端\n3. 前端 `xterm.js` 把按键 → JSON 消息 → 后端 `pty.write()`；后端 `pty.onData` → WebSocket → 前端渲染\n4. 前端 `fit` 后把 `cols/rows` 发给后端 `pty.resize()`，保证不错行\n5. 手机断线 → WebSocket close → 后端 `pty.kill()` 仅 detach 该客户端，tmux 会话与 Claude Code 继续存活\n\n## License\n\nMIT © wangjs-jacky\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwangjs-jacky%2Fclaude-web-terminal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwangjs-jacky%2Fclaude-web-terminal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwangjs-jacky%2Fclaude-web-terminal/lists"}