1. 项目概述1.1 这个项目到底解决什么问题先说结论这是一套把 Claude Code / Codex 这类终端 AI 编码工具从临时跑一下变成随时可用的持久化 Web 工作区的完整方案。用过 Claude Code 或 Codex 的朋友应该都有同感这些工具本身很强大但使用体验上有个尴尬点——它们是终端应用会话状态、项目上下文、历史记录都依赖本地终端环境。今天开个终端跑一会明天想继续要么重新加载上下文要么面对一堆丢失的会话记录。如果换台电脑、或者想让团队成员一起用麻烦更多。这个项目的思路很直接把 AI 编码工具包一层 Web 服务起一个常驻的本地 Web 工作区让浏览器成为统一入口。Claude Code 的会话、Codex 的任务记录、项目的文件结构全都持久化保存下来随时打开浏览器就能接着干。听起来不复杂但实际落地要考虑的东西不少进程管理、会话持久化、多项目隔离、终端交互适配、资源占用控制等等。1.2 适合谁来用每天高频使用 Claude Code / Codex 的开发者尤其是同时用多个项目的场景。想把 AI 编码能力暴露给团队但又不想每个人都折腾终端环境搭建的人。需要在多台设备之间切换希望工作状态能续上的人。对 Vibecoding 这种边聊边写代码的工作方式上瘾但苦于会话经常丢的人。我自己是三种情况都占所以花了些时间把方案从能用打磨到顺手。下面把整个搭建过程和踩过的坑都梳理出来。2. 整体设计思路与关键技术选型2.1 为什么不做浏览器插件而是包一层 Web 服务市面上的方案很多有 IDE 插件、有终端复用工具、有各类 Web UI 包装器。我最终选择自建 Web 服务核心原因有三个。第一IDE 插件绑死编辑器。Claude Code 在 VSCode 里确实有官方插件但 Codex 的体验就参差不齐。团队里有人用 VSCode有人用 JetBrains还有人用 Neovim统一不了。第二终端复用工具需要每个人掌握 tmux 之类的操作学习成本是隐性的。新人上手时光理解附着到会话和新建会话的区别就要花不少时间。第三Web 服务天然就是一个统一入口。浏览器人人会用不需要额外装客户端也不存在我在 Mac 上装了Windows 上怎么办的问题。只要起一个服务所有设备都通过浏览器访问。2.2 核心组件选型与取舍整个方案涉及三个层次AI 编码工具本身、进程管理和持久化、Web 交互层。AI 编码工具选择上我同时接了 Claude Code 和 Codex而不是二选一。原因是这两个工具的能力侧重不同Claude Code 在长上下文理解和多文件重构上表现更稳Codex 在快速生成和任务执行链路上有自己的优势。实际项目中我会根据任务类型切换。比如重构一个老模块用 Claude Code写一个独立的小工具脚本用 Codex。工具栏里放两个入口各干各的活。进程管理方面用 PM2 做常驻守护。为什么不用 systemd 或 Dockersystemd 管理起来偏重每次改配置还要 reloadDocker 能隔离环境但镜像体积大而且 Claude Code 和 Codex 的认证凭证挂载进容器还得额外折腾。PM2 轻量、支持开机自启、日志管理方便最重要的是它对 Node 生态的产物支持得最好——Claude Code 和 Codex 本质上都是 Node 应用PM2 可以直接接管它们的生命周期。持久化层用 SQLite 存会话元数据和任务记录文件系统存完整上下文。为什么不全塞进数据库因为 Claude Code 的会话上下文里可能包含大量代码片段和文件引用存数据库反而增加序列化和反序列化的开销。文件系统天然就是树状结构和项目目录保持一致查询起来也直观。2.3 架构上的三个关键设计决策第一个决策每个项目独立会话空间。很多类似工具把所以会话混在一起用标签页区分。我踩过这个坑——两个项目同时推进时上下文互相污染AI 经常记错当前在哪个项目。所以架构上强制按项目隔离每个项目有独立的会话列表和上下文目录。切换项目就像切换工作区AI 不会串场。第二个决策会话持久化不只存对话记录还存文件操作快照。Claude Code 执行文件修改后会在会话目录里生成变更记录。这样即使会话结束也能回溯AI 当时对哪些文件做了什么改动。对代码审查和问题排查非常有价值。第三个决策Web 终端层用 xterm.js 模拟真实终端交互而不是做简单的聊天对话框封装。原因很实际Claude Code 和 Codex 的交互不仅是文字对话还有文件编辑确认、命令执行审批、多步骤工具调用。这些交互在真实终端里是逐行输出的聊天框装不下。xterm.js 直接渲染 ANSI 转义序列行为表现和真实终端几乎一致AI 输出的各种状态符号、进度条、颜色标记都能正确显示。2.4 不做成银弹的清醒认知这个架构有一个需要坦白的局限它不是把 AI 编码工具重写成一个 Web 应用而是在终端工具外面加了一层 Web 壳。所有底层的 AI 能力、模型调用、工具链逻辑还是交给 Claude Code 和 Codex 自己处理。Web 层只负责三件事提供稳定的访问入口、持久化会话状态、展示终端交互。这个定位让方案保持了极简的维护成本。Claude Code 升级、Codex 更新底层命令行工具更新完之后Web 层不需要任何改动因为它是通过标准终端协议去驱动底层工具的。3. 环境准备与安装配置3.1 前置依赖清单动手之前先把环境理一遍。我的主力开发机是 Ubuntu 22.04以下依赖都是在这套环境下验证过的macOS 上的步骤基本一致Windows 建议用 WSL2。需要准备的东西Node.js 18.x 及以上Claude Code 和 Codex 都要求较新版本的 NodePM2进程守护后面会用到Git版本控制Claude Code 的某些功能依赖一个可用的 Claude Code 或 Codex 账号凭证检查 Node 版本node -v npm -v如果版本过低建议用 nvm 安装新版本curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 20 nvm use 203.2 安装 Claude Code 与 Codex有两个方式安装官方 CLI 和 npm 包。官方 CLI 是交互式安装脚本npm 方式更可控方便指定版本。我习惯用 npmnpm install -g anthropic-ai/claude-code npm install -g openai/codex验证安装claude --version codex --version如果提示命令找不到检查 npm 全局 bin 目录是否在 PATH 里npm bin -g echo $PATH注意安装完成后务必先手动执行一次claude和codex完成登录认证通常是浏览器 OAuth 流程。Web 层不会替你完成认证它只会读取系统里已保存的凭证。3.3 初始化认证的常见坑这一步最容易让新手卡住我展开说说。Claude Code 的认证方式第一次执行claude时终端会输出一个登录链接用浏览器打开并按提示授权。授权完成后凭证会写入~/.claude/目录下的配置文件中。Codex 的认证方式执行codex时同样会引导到浏览器完成 OAuth 登录。凭证默认存储在系统 keyring 或配置文件里。我在多台机器上部署的经验认证完成后把配置目录完整备份一份。换机器或者重装系统时直接恢复省去重新 OAuth 的麻烦。# 备份认证配置 tar -czf claude-auth.tar.gz ~/.claude/ tar -czf codex-auth.tar.gz ~/.codex/3.4 踩过的坑Codex 模型不可用问题这里要分享一个真实踩过的坑。我在测试时给 Codex 指定了一个自定义模型名结果提示the gpt-5.6-sol model is not supported when using codex with a ...原因不是工具坏了而是 Codex 对模型名称有严格的校验列表自定义模型名必须和它支持的模型列表匹配。解决方案是删除本地缓存重新配置codex logout rm -rf ~/.codex/ codex login重新登录后使用codex --model时指定官方支持的模型名称。这个问题在网上被大量搜索我最初也以为是配置错误后来发现就是模型白名单校验。4. 核心系统搭建与实操过程4.1 创建工作区服务骨架基础目录结构如下easy-web-vibecoding/ ├── server.js # Web 服务主入口 ├── terminal.js # 终端会话管理器 ├── sessions.js # 会话持久化逻辑 ├── projects.js # 项目注册与管理 ├── public/ │ ├── index.html # 前端页面 │ ├── style.css │ └── app.js # 前端交互逻辑 └── data/ # 持久化数据目录 ├── sessions/ # 会话记录文件 └── projects.json # 项目列表创建项目并安装依赖mkdir easy-web-vibecoding cd easy-web-vibecoding npm init -y npm install express ws node-pty sqlite3依赖说明express提供 HTTP 服务托管前端页面和 API。wsWebSocket 服务用于前端和后台终端之间建立实时双向通道。node-pty在 Node 里创建伪终端pseudo-terminal这是把 Claude Code / Codex 接入 Web 的关键技术。没有它你没办法在浏览器里模拟真实的终端交互。sqlite3存会话元数据。4.2 实现核心终端会话管理一个关键的点是你不能直接在服务器上用child_process.spawn把 Claude Code 启动起来就完事。因为 Claude Code 的交互式界面需要 TTY终端设备后台的字符串管道没办法正常渲染。node-pty就是为了解决这个问题——它能在后台创建一个虚拟终端设备让 Claude Code 以为自己在真实终端里运行。核心代码实现// terminal.js const os require(os); const pty require(node-pty); function createSession(projectDir, command) { const shell process.env.SHELL || bash; // 创建伪终端 const term pty.spawn(shell, [], { name: xterm-256color, cols: 120, rows: 30, cwd: projectDir, env: process.env }); // 启动 AI 编码工具 term.write(cd ${projectDir} ${command}\r); return term; } module.exports { createSession };cols和rows是伪终端的初始尺寸前端页面里 xterm.js 会自动调整但初始值不能太小否则对话界面显示会很挤。120 列比较稳妥。4.3 WebSocket 桥接层实现前后端的实时双向通信。我用了ws库避免引入 Socket.IO 的额外依赖。Socket.IO 功能更强但如果只是终端数据流的转发ws足够干净利落。// server.js const express require(express); const http require(http); const WebSocket require(ws); const { createSession } require(./terminal); const app express(); const server http.createServer(app); const wss new WebSocket.Server({ server }); app.use(express.static(public)); // 托管前端 // 会话注册表 const activeSessions new Map(); wss.on(connection, (ws, req) { ws.on(message, (message) { const data JSON.parse(message); if (data.type start) { // 为指定项目启动一个新的 AI 编码会话 const term createSession(data.projectDir, data.command); // 终端输出转发给前端 term.onData((output) { ws.send(JSON.stringify({ type: output, data: output })); }); // 用户输入转发给终端 ws.on(message, (inputMsg) { const input JSON.parse(inputMsg); if (input.type input) { term.write(input.data); } }); term.onExit(({ exitCode }) { ws.send(JSON.stringify({ type: exit, data: exitCode })); activeSessions.delete(data.projectId); }); activeSessions.set(data.projectId, term); } }); });这段代码解决了 Web 终端最核心的问题用户按键从浏览器 → WebSocket → 伪终端 → 正在运行的 Claude Code / Codex工具输出反方向回来实时渲染在浏览器里。4.4 会话持久化设计持久化是持久化 Web 工作区的核心卖点实现上要区分两层会话元数据层和完整上下文层。会话元数据存 SQLiteCREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, tool TEXT NOT NULL, started_at DATETIME, ended_at DATETIME, status TEXT, context_path TEXT ); CREATE INDEX idx_project_id ON sessions(project_id);会话内容存文件系统data/sessions/ ├── project-alpha/ │ ├── 2025-01-15_claude-code_001/ │ │ ├── transcript.log # 完整终端输出 │ │ ├── file_changes.json # 文件变更记录 │ │ └── context.json # 项目上下文摘要 │ └── 2025-01-15_codex_001/ └── project-beta/为什么坚持元数据入库、内容落盘的混合方案因为会话记录的检索需要结构化查询——比如找出昨天对 project-alpha 的所有 Codex 会话SQLite 一条 SQL 就搞定。但会话内容本身是纯文本流写入文件系统更自然而且可以用grep、tail等标准命令直接查看不方便的地方就是要自己去文件系统里翻。4.5 前端工作区界面前端用 xterm.js 做终端渲染核心初始化代码// public/app.js const { Terminal } require(xterm); const { FitAddon } require(xterm-addon-fit); const term new Terminal({ cursorBlink: true, fontSize: 14, fontFamily: monospace, theme: { background: #1e1e2e, foreground: #cdd6f4 } }); const fitAddon new FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById(terminal)); fitAddon.fit(); // 输入转发 term.onData((data) { ws.send(JSON.stringify({ type: input, data })); });前端有几个细节需要注意FitAddon必须加载否则终端的尺寸不会跟随浏览器窗口变化显示会错位。主题配色按个人风格调整但尽量选深色底、浅色字AI 输出里的高亮信息对比更清楚。在页面加载完成后调用fit()不然初始渲染会出现滚动条错位。4.6 配置 PM2 持久守护Web 服务本身写好了还要确保它一直活着重启机器后自动拉起。pm2 start server.js --name easy-web-vibecoding pm2 save pm2 startuppm2 startup会生成一个系统服务配置执行它输出的命令即可。这样整个工作区就变成一个常驻服务开机自动运行。查看日志pm2 logs easy-web-vibecoding重启pm2 restart easy-web-vibecoding这里有一个经验PM2 默认的日志轮转需要配置否则跑一两个月日志文件会膨胀到几个 G。pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 50M pm2 set pm2-logrotate:retain 75. 实战演示完整工作流5.1 创建项目并从零开发一个工具脚本我用一个实际任务演示给团队写一个日志清理工具。在浏览器打开工作区选择项目目录project-alpha点击新建 Claude Code 会话。终端弹出 Claude Code 的交互界面后输入需求在当前目录创建一个 Python 脚本 log_cleaner.py功能扫描指定目录下的 .log 文件按文件修改时间排序删除超过 30 天的文件支持 --dry-run 参数预览将要删除的文件。Claude Code 开始执行先创建文件然后展示 diff询问是否确认修改。这一步在浏览器终端里看得一清二楚和本地终端体验一致。确认后Claude Code 还自动生成了简单的单元测试。整个会话结束后我在data/sessions/project-alpha/下找到这次会话的完整记录包括file_changes.json可以看到 AI 创建了log_cleaner.py和test_log_cleaner.py。5.2 切换 Codex 并行处理另一个任务在同一个工作区我不关掉 Claude Code 的会话直接新建一个 Codex 会话指定同一个项目目录。这次任务是写一个 JSON 转 CSV 的小工具。Codex 的交互方式和 Claude Code 不太一样它的工具调用链更长输出的步骤提示更多。xterm.js 渲染这些 ANSI 控制码没有出错进度条和状态符号显示正常。两个会话并行运行互不干扰。之所以要支持并行会话是因为实际工作流中我经常让 Claude Code 做代码审查的同时让 Codex 生成一个新功能原型。顺序执行太浪费时间了。5.3 持久化恢复第二天接着干第二天到办公室打开浏览器进入工作区。从侧边栏看到昨天的历史会话列表——项目project-alpha下有 3 个会话2 个 Claude Code1 个 Codex。点击昨天的 Claude Code 会话点击恢复系统读取transcript.log重新渲染终端内容并把当前工作目录切换到当时所在的位置。我输入一句继续Claude Code 就能以上下文为基础继续工作。恢复会话的实现原理伪终端不会真正回到过去但可以重新执行 Claude Code 的会话恢复命令它的--resume参数让工具自己加载历史状态。Web 层需要做的就是把之前的终端显示内容重放一遍让开发者视觉上看到完整上下文。5.4 远程访问与团队协作工作区跑在开发机上局域网内其他机器通过浏览器访问。默认端口可以自己定我习惯用 8080。pm2 start server.js --name easy-web-vibecoding -- --port 8080 # 或者设置环境变量 EASY_WEB_PORT8080 pm2 start server.js团队成员访问http://服务器IP:8080登录后选择项目、新建会话。大家共享同一套会话记录一个同事调过的 bug另一个同事能从会话记录里看到完整过程。如果有多人同时使用会话隔离是靠项目 用户双层维度。我没把用户系统做得太重——一个简单的用户名输入框就够刻意保持轻量化。注意默认配置没有做访问控制。如果部署在生产环境或公网务必在前面加一层反向代理如 Caddy 或 Nginx做基本认证HTTP Basic Auth或者至少限制内网访问。6. 常见问题与排查技巧6.1 终端卡住不输出现象打开会话很久终端里什么也不显示或者输出停在某个位置。排查步骤看 PM2 日志确认 Claude Code / Codex 是否正常启动。检查伪终端的cwd工作目录是否正确——如果目录不存在工具可能启动失败但错误信息没显示出来。检查凭证是否过期手动在服务器上执行claude或codex看能否正常进入交互界面。经验大部分卡住问题和凭证有关。OAuth 凭证有效期过了终端工具会静默等待重新认证但 Web 终端里往往看不到明显的提示。6.2 Codex 报错 auth token is unavailable这个报错在网络搜索里极其高频。codex auth token is unavailable原因基本就一个Codex 的登录凭证没找到。要么从未登录要么 keyring 里的凭证在服务环境下无法访问。最直接的排查# 确认是否已登录 codex login status # 如果状态异常重新登录 codex logout codex login还有一个隐蔽问题如果用 PM2 以服务方式运行 CodexPM2 环境的 DBUS 会话可能无法访问系统的 keyring。解决办法是让 Codex 将凭证以文件方式保存CODEX_AUTH_FILE环境变量或配置选项避免依赖系统的 keyring 服务。6.3 Xterm.js 显示错位或滚动异常前端终端渲染偶发错位。常见原因和解决浏览器窗口缩放后没有重新调用fit()。解决监听resize事件在窗口变化时调用fitAddon.fit()。终端初始尺寸和实际渲染容器不一致。解决在Terminal.open()后延迟一小段再调用fit()。多会话切换时xterm.js 实例没有正确销毁。解决切换前调用term.dispose()。6.4 进程泄漏导致服务器变慢长时间运行后服务器内存占用升高。原因每个会话在后台对应一个node-pty终端进程如果前端页面关闭时没有通知服务器结束会话进程就变成僵尸进程持续运行。解决办法在 WebSocket 的close事件里主动杀掉关联的终端进程。ws.on(close, () { const term activeSessions.get(data.projectId); if (term) { term.kill(); activeSessions.delete(data.projectId); } });另外可以给会话设置闲置超时比如 30 分钟没有输入就自动关闭。6.5 Claude Code 提示所在国家不支持搜索热词里有claude code might not be available in your country。这通常和网络环境有关具体表现是服务端判断请求来源地域后拒绝服务。这类问题别去折腾代码层面的 workaround——工具本身有合规限制。我的建议是使用受支持区域的代理服务。或者换用其他合规的 AI 编码工具作为底层驱动。这个工作区的架构优势在此体现出来——把 Claude Code 换成其他标准终端工具Web 层几乎不用改重组一下配置即可。6.6 常用排查命令速查表症状排查命令可能的处理服务没启动pm2 statuspm2 start server.js日志刷屏pm2 logs easy-web-vibecoding配置日志轮转Claude Code 不响应手动执行claude检查 OAuth 凭证Codex 认证失败codex login status重新codex login伪终端无法创建检查node-pty安装重新npm install node-pty浏览器连不上curl http://localhost:8080检查防火墙和端口多会话错乱pm2 restart easy-web-vibecoding重启服务清理会话注册表7. 扩展思路与进阶玩法7.1 接入 DeepSeek 等兼容模型搜索热词里codex接入deepseek、claude code接入deepseek热度很高。原理上Claude Code 和 Codex 都支持通过兼容的模型 API 接口来指定第三方模型。以 Codex 为例设置CODEX_MODEL环境变量或配置项指向兼容的模型标识。这样可以把 AI 编码工具的对话模型换成 DeepSeek 的模型来跑特定任务成本通常更低。但要注意不同模型的工具调用能力和上下文窗口差异很大。换模型之后AI 编码工具的部分能力可能不完整——比如某些复杂的长上下文重构任务模型能力不够就会瞎改代码。建议把模型选择加到工作区的项目配置里按任务类型切换。7.2 用编码规范约束 AI 输出热词里有一条编码添加编码规范约束这恰好是 Vibecoding 最容易被忽视的点。直接在提示词里反复强调请遵守项目编码规范效果有限AI 会在长上下文里忘记规则。更好的做法是在项目根目录放一个AGENTS.md或CLAUDEMD文件把项目的编码规范、目录结构、命名约定写清楚。Claude Code 启动时会自动读取这类约束文件形成系统级约束。Codex 也支持类似的文档读取机制。我用这个方式让 AI 生成的代码从一开始就符合规范而不是生成完再改。实际效果差异很大——尤其是团队有统一的 commit 风格、注释规范、错误处理模式时。7.3 把工作区变成团队知识库会话持久化带来的一个副产品所有历史会话记录就是这个团队的 AI 开发知识库。新人加入项目不用从头问这个项目怎么跑、那次 bug 怎么排查的直接浏览历史会话记录就能看到之前所有和 AI 协作解决问题的完整过程。这比任何人写的文档都更真实——因为它记录了当时的完整上下文、遇到的报错、尝试的方案、最终的选择。更进一步可以基于这些会话记录生成每周 AI 协作报告总结本周 AI 编码工具处理了哪些任务、涉及哪些文件、有没有反复回滚的改动。对团队复盘和代码审查都很有价值。7.4 移动端访问因为部署在浏览器手机和平板上也能直接访问工作区。配合 xterm.js 的触屏支持在平板上阅读 AI 生成的代码、查看会话记录、做简单的指令操作体验已经足够。对通勤路上回看代码修改、或者在会议中快速查一下之前 AI 做过什么改动的场景这个功能帮助特别大。8. 结尾一些实际经验和教训最后分享点我自己的体会。第一Vibecoding 这股风潮里最大的误区是把它当成让 AI 全自动写代码然后期望代码直接能上线。实际上手之后你会发现最有价值的工作方式是把 AI 当成一个非常熟悉你的项目、但需要你持续校准方向的同事。这个 Web 工作区解决的核心痛点就是让你和这个同事的协作过程可以被持续追踪、被随时恢复。第二会话持久化不是简单的保存聊天记录而是要能回溯每一次文件改动、每一个决策点。我做的文件变更快照最初只是为了排查问题后来发现它本身就是一份高质量的代码审查素材——AI 自己改过的代码回头看时可能连 AI 自己都忘了改过什么。第三工具链的复杂性要克制。这个方案从头到尾只用了 Express、WebSocket、node-pty、SQLite 这几个基础组件没有引入消息队列、没有容器编排、没有微服务。能用一个轻量单服务解决的问题就不要用分布式方案去制造运维复杂度。如果你也在高频使用 Claude Code 或 Codex尤其是多人协作、多项目并行的场景认真建议你花半天时间把这套工作区搭起来。投入不大但每天省下的时间和少掉的烦躁会比你预期的多很多。
