架构技术评论

HAPI 远程编程,多项目随时切换,并行多项目远程开发

Image

需求是这样的:有时你有多个项目,想在远程编程时切换,但并不希望流程太复杂(比如在离开家之前,必须把所有可能用到的项目都先开启并配置好)。

最近我给 hapi 提了一个 Pull Request https://github.com/tiann/hapi/pull/526 ,可以在需要的时候随时开启远程编程。

当然这也一个限制:需要指定一个根目录。在该根目录下的所有文件夹,你都可以随时随地按需开启 Coding Agent 进行编程。

下面有两个方法:

  1. 方法一:如果你之前没有接触过 hapi,可以参考此方法。
  2. 方法二:如果你看过我的上一篇文章,并配置了自己的 Cloudflare Tunnel,可以看方法二。

更多的介绍可以参考文章后面。

方法 1

步骤1:

  1. 指定你想公开的一个文件夹(该文件夹用于存放各种各样的仓库)
  2. 你可以在任何时刻,随时在该文件夹下的任何一个仓库开始编程
hapi runner start --workspace-root=~/focus

Pasted image 20260428084640.png
步骤 2

hapi hub --relay

Pasted image 20260428085352.png

然后访问这个对应的 URL 就可以远程编程了
Pasted image 20260428085908.png

方法 2

如果你之前参考了之前的文章,想配置一个自己的 Cloudflare Tunnel,可以用这个已经配置好的 Cloudflare Tunnel,你可以参考这个步骤:

步骤1:

  1. 指定你想公开的一个文件夹(该文件夹用于存放各种各样的仓库)
  2. 你可以在任何时刻,随时在该文件夹下的任何一个仓库开始编程
hapi runner start --workspace-root=~/focus

Pasted image 20260428084640.png

步骤2

hapi server

Pasted image 20260428084822.png

步骤3

cloudflared tunnel run hapi

Pasted image 20260428085047.png

在这里浏览自己域名的 Cloudflare Tunnel 就可以这样看,可以选定任何一个文件夹点击右下角的 Start Session 开始远程编程。
Pasted image 20260428085228.png


背景

hapi 是一个本地优先(local-first)的 Claude Code / Codex / Cursor Agent / Gemini / OpenCode 远程控制框架。它的工作方式是:在你的工作机上跑一个 runner,把会话注册到 hub,然后通过 Web、PWA 或 Telegram Mini App 远程查看和操作。

之前在 /sessions/new 启动一个新会话时,"工作目录"这一项只有一个文本输入框,需要你手动键入完整路径(最近用过的几条会以 chips 形式出现,但仅此而已)。这意味着两件不太顺手的事:

  1. 要从手机或浏览器上启动会话,得记得目录的绝对路径;
  2. 没办法在 Web 端浏览工作机的目录结构,决定"今天进哪个仓库"。

PR #526 给 Web UI 加了一个工作区浏览器(Workspace Browser),并配套加了一个 --workspace-root 开关,把"可浏览的范围"明确收敛到你指定的那个根目录里。这个特性已经合入 v0.17.2,下面讲一下怎么用。


升级到 >=v0.17.2

https://hapi.run/docs/guide/quick-start

npm install -g @twsxtd/hapi --registry=https://registry.npmjs.org

快速上手:开启工作区浏览

特性是显式 opt-in 的。换句话说,不加 --workspace-root 就跟以前一样,加了才会出现新的 /browse 页面以及相关入口。

# 用 ~/code 作为工作区根目录
hapi runner start --workspace-root ~/code

# 也支持 = 形式
hapi runner start --workspace-root=~/code

# 也支持绝对路径
hapi runner start --workspace-root /Users/you/code

~ 和 ~/foo 在 shell 没有展开的情况下也会被自己展开,所以从配置文件、systemd unit 之类的地方传过来不用担心。
Pasted image 20260428090409.png


Web UI 的几个新入口

打开 Web 端,你会看到三个变化。

1. /sessions 顶部多了一个文件夹图标

会话侧栏的顶部加了一个文件夹小图标,点它跳转到 /browse。这是新功能最直接的入口。
Pasted image 20260428090531.png

2. /browse 页面:带 git 标记的目录树

/browse 页面会自动落到 --workspace-root 指定的目录,左侧是面包屑(最多回退到 root,再往上是禁止的),中间是这个目录下的子目录列表。每个条目都会做一次 git 仓库探测:如果一个目录下有 .git,列表里会带一个小标记,点进去后页面底部还会出现一个 "Start Session" 按钮,点它会跳到 /sessions/new 并自动把目录字段填好。

如果你的 root 下有十几个仓库,这个页面基本上就是个"项目清单"。
Pasted image 20260428090547.png

Pasted image 20260428090640.png


安全边界

工作区浏览的核心约束是"不能跑出 root 的范围"。这件事是在后端 RPC 层做的,前端再怎么造请求都绕不过:

  • list-directory
     拒绝任何不在 root 范围内的路径,返回 {success: false, error: "Path is outside workspace root"}。
  • spawn-session
     同样校验 cwd —— 即使有人手搓 fetch,也无法在 root 之外起会话。
  • 符号链接的 lexical bypass 已修
    。早期实现只做字符串前缀判断,意味着如果 root 是 /safe、底下有一个软链 /safe/out -> /etc,按字符串看 /safe/out/passwd 是"在 root 里",但 realpath 是 /etc/passwd。Review 阶段把这个洞补上了:构造时对 root 做一次 realpath,每个进来的路径也走 realpath(spawn 目标如果还不存在,向上找最近一个存在的父目录解软链)再做包含判断。
  • v0.17.2 上线后又补了一条防御:没配 root 的机器,list-directory RPC 直接拒绝。Web UI 本来就把 Browse 入口藏起来了,但后端不该假设前端永远靠谱。

小结

--workspace-root 不是一个会改变 hapi 默认行为的大改动,而是一个显式开关 + 一个新的 Web 页面。它适合两类场景:

  1. 你在工作机上有一个固定的 ~/code / ~/work,常常需要从手机上挑一个仓库就开干;
  2. 你把工作机暴露给了别人(团队共享、远程协作),希望明确声明"只有这棵子树是可访问的"。

如果都不是 —— 完全可以忽略这个特性,hapi 老的命令一字未变。

PR:tiann/hapi#526
Release:v0.17.2