Skip to content

Repository files navigation

Berth

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、缓存最近连接、同站点多连接,并支持远端浏览、编辑、上传、下载、新建、重命名和安全删除。

设计原则

  1. 终端优先:不复制 CLI 工具的交互层,只保证字节流、尺寸和生命周期可靠。
  2. 保持轻量:使用 Tauri 和系统 WKWebView,不内置 Chromium、插件宿主或语言服务。
  3. 真实数据:浏览器适配器不提供 mock;没有工作区时只展示启动页。
  4. 边界清晰:业务规则不依赖 React/Tauri,UI 不直接执行系统命令。
  5. 资源有主:每个 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 和终端注册表。

核心处理机制

1. 终端字节流与生命周期

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 转义,只插入文本,不自动提交命令。

2. 标签页内存策略

不同标签使用不同生命周期:

  • 终端标签始终挂载,否则会丢失 PTY 连接和 xterm 缓冲区。
  • 活动文件、媒体和 Diff 标签正常挂载。
  • 未修改的后台文件/媒体/Diff 标签卸载,需要时重新读取。
  • 有未保存草稿的文件继续挂载,保证切换标签不会丢失编辑内容。

这样既保留终端会话,又避免所有文件内容、媒体元素和高亮 DOM 随标签数量永久累积。

3. 文件读取、搜索与保存

  • 文件树按目录展开懒加载,不在打开工作区时递归读取整棵目录。
  • Git 工作区搜索使用 Git 提供的文件清单,遵循 .gitignore、仓库 exclude 和全局 ignore。
  • 非 Git 目录使用有边界的递归搜索,并跳过 .gitnode_modulestarget.next
  • 搜索结果最多返回 200 条。
  • 可编辑文本最大为 5 MB;限制在 Rust 边界执行,避免同一大文本在 Rust、contentdraft 和高亮 DOM 中重复驻留。
  • 保存完成后主动刷新 Git 状态,界面不会等待下一次轮询。

4. Git 状态同步

Git 状态通过多种信号协作更新:

  1. 工作区根目录变化时执行初始刷新。
  2. 窗口重新聚焦时刷新。
  3. 终端提交命令后延迟 650ms 刷新,覆盖用户在终端中执行 Git 命令的场景。
  4. 源代码管理侧栏可见且窗口聚焦时自动轮询。

轮询从 2.5 秒开始;连续无变化时逐步退避到 15 秒,检测到变化后恢复到 2.5 秒。并发刷新带有递增序号,较早结束的进程不能覆盖较新的结果;状态没有变化时复用旧引用,避免文件树无意义重渲染。

.gitignore 装饰使用 git check-ignore --stdin 按仓库批量处理已经加载的树节点,而不是为每个文件启动一个 Git 进程。Diff 原始数据限制为 2 MB,前端最多渲染 6000 行。

5. HTML 与媒体预览

  • 图片、音频和视频使用 Tauri asset URL 与 WKWebView 原生能力,音视频只预加载 metadata。
  • HTML 预览绑定 127.0.0.1 的随机端口,只允许访问入口文件所在目录内经过 canonicalize 校验的资源。
  • HTML 服务使用阻塞 accept,关闭时通过一次本地连接主动唤醒,不进行高频空轮询。
  • 图片、音频、视频等相对资源从磁盘流式写入连接,不在 Rust 中一次性读取完整文件。
  • 切换预览模式、关闭标签或关闭窗口都会停止对应服务;外部浏览器只允许打开 Berth 创建的回环地址。

6. AI 会话索引与恢复

  • Claude Code:根据工作区路径定位项目目录,每个候选 JSONL 最多读取 256 KB,用于提取 session id、标题、分支和更新时间。
  • Codex:以只读、无互斥方式查询本地 state_5.sqlite 索引,不加载完整 transcript。
  • 每个工作区、每个工具只展示最近 20 条记录,并缓存有限的元数据以加快再次打开速度。
  • 恢复会话时使用工具自己的原生命令;同一 provider + session id 已有终端时只聚焦现有面板,不重复启动。

7. SSH 主连接与 SFTP 文件生命周期

  • 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.toml

HTML 预览测试需要绑定本机回环端口。如果运行环境禁止本地监听,需要给予测试进程相应权限。

构建

npm run tauri -- build

macOS 安装包输出到:

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 历史会话通过原工具索引重新进入。

About

Berth 是一个面向 macOS 的轻量终端工作台,用来在同一个窗口中管理多根目录、文件、Git 变更和多个终端。

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages