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 或 Docker?systemd 管理起来偏重,每次改配置还要 reload;Docker 能隔离环境,但镜像体积大,而且 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 都要求较新版本的 Node)
- PM2(进程守护,后面会用到)
- 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 方式更可控,方便指定版本。我习惯用 npm:
npm 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。ws:WebSocket 服务,用于前端和后台终端之间建立实时双向通道。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 工作区"的核心卖点,实现上要区分两层:会话元数据层和完整上下文层。
会话元数据存 SQLite:
CREATE 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 Code,1 个 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_PORT=8080 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 以服务方式运行 Codex,PM2 环境的 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 status | pm2 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,尤其是多人协作、多项目并行的场景,认真建议你花半天时间把这套工作区搭起来。投入不大,但每天省下的时间和少掉的烦躁,会比你预期的多很多。