Berth 是一个面向 macOS 的轻量终端工作台,用来在同一个窗口中管理多根目录、文件、Git 变更和多个终端。
Berth 管理的是终端,不直接编排 AI Agent。Claude Code、Codex 和其他 CLI 工具都只是运行在真实 shell 中的普通进程;会话历史继续由各工具自己的本地存储负责,Berth 只读取必要的索引元数据。
- 启动页:打开文件夹、恢复最近窗口、新建应用窗口。
- 多根工作区:向当前窗口追加目录,并可从文件树移除某个根目录。
- 文件管理:懒加载文件树、搜索、新建文件、与 Finder 双向复制/粘贴文件和文件夹、重命名、经确认后移入 macOS 废纸篓、在访达中显示、在目录中创建终端。
- 文件编辑:轻量文本编辑、语法高亮、
Command/Ctrl + S保存。 - 文件预览:图片、音频、视频、Markdown 和 HTML;HTML 可在本地浏览器中打开。
- 终端:真实 POSIX PTY、login shell、拖入路径、快捷短语、面板 resize、在系统终端中打开。
- 灵活布局:规则 M × N 网格和递归分割布局共用一套模型,面板可拖拽停靠并调整比例。
- AI 会话索引:读取 Claude Code 与 Codex 最近会话的元数据,并在唯一终端中恢复,避免重复进入同一会话。
- 源代码管理:多根 Git 仓库状态、忽略文件装饰、Diff、文件或仓库级暂存/取消暂存。
- 快捷短语:在设置中增删改,跨窗口同步,并注入指定终端而不是共享输入状态。
- SSH / SFTP:读取系统
~/.ssh/config、缓存最近连接、同站点多连接,并支持远端浏览、编辑、上传、下载、新建、重命名和安全删除。
- 终端优先:不复制 CLI 工具的交互层,只保证字节流、尺寸和生命周期可靠。
- 保持轻量:使用 Tauri 和系统 WKWebView,不内置 Chromium、插件宿主或语言服务。
- 真实数据:浏览器适配器不提供 mock;没有工作区时只展示启动页。
- 边界清晰:业务规则不依赖 React/Tauri,UI 不直接执行系统命令。
- 资源有主:每个 PTY、监听器、预览服务和全局事件都必须有明确的创建者与清理路径。
React UI(features / shared/ui)
│
▼
交互编排(hooks)──── 工作台状态与用例(store)
│ │
└─────────┬──────────┘
▼
业务模型与能力接口(domain)
│
▼
平台适配器(infrastructure)
│ Tauri invoke / Channel
▼
Rust 系统能力(src-tauri/src)
目录职责:
src/domain:业务模型、纯函数以及桌面/Git 能力接口;不依赖 React。src/store:工作区、布局、标签、终端会话和快捷短语的状态转换。src/features:按产品能力拆分的 React UI 组件。src/hooks:跨组件交互、异步用例和资源订阅的编排层。src/infrastructure:Tauri/浏览器适配器、持久化和进程内事件。src/shared:无业务状态的 UI 原语与工具函数。src-tauri/src/commands:文件、Git、预览、窗口和系统集成命令。src-tauri/src/terminal.rs:POSIX PTY、shell 和终端注册表。
xterm.onData
│ UTF-8 bytes
▼
write_to_terminal ──► PTY master ──► login shell / CLI
│
▼
xterm.write ◄── Tauri Channel ◄── PTY reader thread
- xterm 只在 Tauri 终端面板首次挂载时动态加载,启动页和文件预览不会加载终端运行时。
- Rust 使用
openpty创建独立 PTY,通过terminal_id在注册表中隔离 writer 与 child。 - shell 以交互式 login shell 启动,使
.zprofile、.zshrc和 NVM 配置与系统终端保持一致。 - 创建 shell 前移除
npm_config_prefix/NPM_CONFIG_PREFIX,避免tauri dev的 npm 环境阻止 NVM 初始化。 ResizeObserver同时驱动 xterm fit 与TIOCSWINSZ,面板调整后 CLI 能收到正确行列数。- shell 自然退出、标签关闭、原生窗口销毁和应用退出都会从注册表移除进程并执行
kill/wait;重复清理是幂等的。 - 单窗口最多运行 16 个终端,每个终端保留 2000 行 scrollback,防止资源无上限增长。
- 原生文件拖放按指针位置路由:命中 SFTP 区域时上传到当前远端目录,其余区域交给当前选中的终端;终端路径经过 POSIX shell 转义,只插入文本,不自动提交命令。
不同标签使用不同生命周期:
- 终端标签始终挂载,否则会丢失 PTY 连接和 xterm 缓冲区。
- 活动文件、媒体和 Diff 标签正常挂载。
- 未修改的后台文件/媒体/Diff 标签卸载,需要时重新读取。
- 有未保存草稿的文件继续挂载,保证切换标签不会丢失编辑内容。
这样既保留终端会话,又避免所有文件内容、媒体元素和高亮 DOM 随标签数量永久累积。
- 文件树按目录展开懒加载,不在打开工作区时递归读取整棵目录。
- Git 工作区搜索使用 Git 提供的文件清单,遵循
.gitignore、仓库 exclude 和全局 ignore。 - 非 Git 目录使用有边界的递归搜索,并跳过
.git、node_modules、target、.next。 - 搜索结果最多返回 200 条。
- 可编辑文本最大为 5 MB;限制在 Rust 边界执行,避免同一大文本在 Rust、
content、draft和高亮 DOM 中重复驻留。 - 保存完成后主动刷新 Git 状态,界面不会等待下一次轮询。
Git 状态通过多种信号协作更新:
- 工作区根目录变化时执行初始刷新。
- 窗口重新聚焦时刷新。
- 终端提交命令后延迟 650ms 刷新,覆盖用户在终端中执行 Git 命令的场景。
- 源代码管理侧栏可见且窗口聚焦时自动轮询。
轮询从 2.5 秒开始;连续无变化时逐步退避到 15 秒,检测到变化后恢复到 2.5 秒。并发刷新带有递增序号,较早结束的进程不能覆盖较新的结果;状态没有变化时复用旧引用,避免文件树无意义重渲染。
.gitignore 装饰使用 git check-ignore --stdin 按仓库批量处理已经加载的树节点,而不是为每个文件启动一个 Git 进程。Diff 原始数据限制为 2 MB,前端最多渲染 6000 行。
- 图片、音频和视频使用 Tauri asset URL 与 WKWebView 原生能力,音视频只预加载 metadata。
- HTML 预览绑定
127.0.0.1的随机端口,只允许访问入口文件所在目录内经过 canonicalize 校验的资源。 - HTML 服务使用阻塞
accept,关闭时通过一次本地连接主动唤醒,不进行高频空轮询。 - 图片、音频、视频等相对资源从磁盘流式写入连接,不在 Rust 中一次性读取完整文件。
- 切换预览模式、关闭标签或关闭窗口都会停止对应服务;外部浏览器只允许打开 Berth 创建的回环地址。
- Claude Code:根据工作区路径定位项目目录,每个候选 JSONL 最多读取 256 KB,用于提取 session id、标题、分支和更新时间。
- Codex:以只读、无互斥方式查询本地
state_5.sqlite索引,不加载完整 transcript。 - 每个工作区、每个工具只展示最近 20 条记录,并缓存有限的元数据以加快再次打开速度。
- 恢复会话时使用工具自己的原生命令;同一 provider + session id 已有终端时只聚焦现有面板,不重复启动。
- SSH 连接仍运行在真实终端中。Berth 使用系统
/usr/bin/ssh建立独立ControlMaster套接字,SFTP 复用该连接,因此密码、密钥口令和主机确认仍由终端处理。 - 站点来源为
~/.ssh/config中明确的 Host 别名;IdentityFile、ProxyJump、端口、用户和通配规则继续由 OpenSSH 解释。 - 最近连接只在本地保存目标、显示名称、置顶状态和连接时间,不保存密码、密钥内容或临时套接字路径。
- SFTP 后端统一通过系统
/usr/bin/sftp的批处理通道执行。目标、控制套接字和批处理路径均在 Rust 边界校验,路径中的换行和控制字符不会进入命令流。 - 本地文件树、SFTP 与 macOS 文件剪贴板共用一套复制/粘贴用例:支持 Finder 与 Berth 双向粘贴、本地互拷、远端互拷,以及本地与远端之间交叉粘贴。只有用户主动执行粘贴时才读取系统剪贴板,并通过
changeCount判断 Berth 内的结构化来源是否仍然有效,不进行后台轮询。 - 粘贴目标优先使用选中的文件夹;选中文件时使用其所在目录。目标已有同名项目时生成“副本”“副本 2”等名称,不静默覆盖现有内容。
- 文件或文件夹移入废纸篓前必须确认;目标范围内存在未保存标签时拒绝移动。成功后关闭对应的已保存标签、清除失效剪贴板引用,并并行刷新文件树与 Git 状态。工作区根目录只允许从当前窗口移除,不提供整目录移入废纸篓入口。
- SFTP 跨连接复制通过权限为
0700的系统临时目录中转,任务结束后立即递归清理。将远端项目复制到 Finder 时,本地缓存会保留到系统剪贴板被替换,以保证离开 Berth 后仍可粘贴;Berth 下次启动会清理超过 24 小时的异常遗留缓存。 - 远端文本最大 5 MB,保存前比较打开时的大小与修改时间;发现服务器端已变化时拒绝覆盖,并保留编辑器草稿。
- 未保存的远端标签与本地文件使用相同的关闭保护:只有保存成功才允许关闭。远端删除必须二次确认,目录只允许删除空目录。
- 图片、音频和视频先下载到有大小上限的系统临时缓存,再复用本地媒体预览;标签卸载或关闭时主动删除缓存。
- 上传、下载、保存和删除都提供持续的进行中状态。当前系统 SFTP 为单次批处理,进度条表示任务仍在执行,不伪造百分比。
| 资源 | 当前限制 |
|---|---|
| 每窗口终端数 | 16 |
| 每终端 scrollback | 2000 行 |
| 可编辑文本 | 5 MB |
| 文件搜索结果 | 200 条 |
| Git Diff 原始数据 | 2 MB |
| Git Diff 渲染 | 6000 行 |
| AI 会话 | 每工作区、每工具 20 条 |
| 最近 SSH 连接 | 20 条 |
| 远端可编辑文本 | 5 MB |
| 远端媒体预览缓存 | 单文件 200 MB |
| Git 自动轮询 | 2.5–15 秒自适应 |
| 规则网格 | 最大 4 × 4 |
当前 release 参考数据:二进制约 12 MB,DMG 约 3.1 MB,macOS 启动页完整物理内存约 89 MB。实际占用会随系统版本、终端数量、终端内运行的进程和打开文件而变化。
环境要求:
- macOS
- Node.js 与 npm
- Rust stable
- Xcode Command Line Tools
安装依赖:
npm install浏览器模式只用于检查 UI,不提供伪造的文件、Git 或终端数据:
npm run dev启动原生桌面应用:
npm run tauri -- dev# TypeScript
npm run check
# Rust 格式与静态检查
cargo fmt --check --manifest-path src-tauri/Cargo.toml
cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
# Rust 测试
cargo test --manifest-path src-tauri/Cargo.tomlHTML 预览测试需要绑定本机回环端口。如果运行环境禁止本地监听,需要给予测试进程相应权限。
npm run tauri -- buildmacOS 安装包输出到:
src-tauri/target/release/bundle/dmg/Berth_<version>_aarch64.dmg
src-tauri/target 是 Rust 编译缓存,体积可能达到数 GB,但不属于最终应用安装体积。
- 只支持 macOS,不支持 Windows ConPTY 或 Linux PTY。
- 不提供插件系统、LSP、调试器、多光标等完整 IDE 能力。
- Git 当前覆盖查看、Diff、暂存和取消暂存,不处理提交、分支管理和冲突解决。
- 超过 5 MB 的文本不会直接进入内置编辑器;大文件应使用专用查看工具。
- 运行中的终端进程不会跨应用重启恢复;AI 历史会话通过原工具索引重新进入。