bonsai-cli 是这么连上 Bonsai 的
从今天起,把一台 Mac 连上 Bonsai 只要一步:装好 Bonsai for Mac,在它的窗口里登录。过一会儿,这台 Mac 就以它自己的名字出现在你的执行端页面上,而且已经启用到你的个人空间。不用开终端,不用 bonsai login,也不用核对验证码。
看不见的连接正是我们想要的。但这条连接能让 agent 应 Bonsai 的要求在你的电脑上跑起来,所以值得弄清楚它到底是什么。这篇从第一个请求讲到连接稳定下来,也讲讲为什么 app 可以跳过命令行里那一步「核对验证码」。
执行端只往外连,从不让人连进来
执行端(runtime)是你电脑上的那个 bonsai 进程。Bonsai for Mac 把它装在 app 里,自己照看;别的机器上由你自己运行。它负责从 Bonsai 接活,交给你电脑上的官方 harness(Claude Code 或 Codex),再把会话过程传回来。
Bonsai 从来不连你的电脑。执行端主动向 Bonsai 打开一条 WebSocket 并一直开着:run 从这条线上下来,会话事件从这条线上回去。所以不用开端口,不用打隧道,在什么路由器后面都能用;能代表你这台电脑的,只有它手里的那份凭据。
拿到凭据:配对
凭据是两样东西:一个 runtime ID(rt_ 加 32 位十六进制)和一枚 token(32 位十六进制)。两样都由 Bonsai 生成,而且只给一次。Bonsai 只存 token 的 SHA-256,所以就算是 Bonsai 自己的数据库,也交不出这枚 token。
拿凭据走的是设备码流程,和 gh auth login 同一种:
- 执行端带着机器名调
POST /runtime/pair。Bonsai 回一个短码(比如BCDF-GHJK)、一个链接,和一个只给这台执行端的 secret。 - 某个已经登录 Bonsai 的人确认这个短码。
- 同时,执行端每两秒带着 secret 调一次
POST /runtime/pair/poll。确认之后的下一次轮询里,Bonsai 才建出这台执行端,把 ID 和 token 交给它,只这一次。
secret 让别人取不走 token:短码会出现在屏幕上、出现在网址里,secret 不会。没确认的配对十分钟后过期。
为什么终端要你核对验证码,app 不用
在终端里运行 bonsai login,第 2 步发生在浏览器里的一个页面上:「把这台机器加成你的执行端?终端上显示的码是……」页面写明:只在你自己刚刚运行了 bonsai login、终端上显示的也是这个码时才确认。
这一步核对,是设备码流程里唯一真正的防线。风险不在于有人偷走你的 token,而在于有人在他自己的机器上发起配对,再把链接发给你。你一确认,他那台机器就成了你名下的执行端。它仍然接不到你没给它的活,但它本来就不该在那里。
Bonsai for Mac 跳过了这个页面。等它的窗口里显示出登录后的工作区,app 就发起一条配对,并在那个页面里直接确认:
POST /app/api/runtime {"op": "pair", "code": "BCDF-GHJK"}
这个请求是同源的,带着你的登录 cookie 和页面上的 CSRF 令牌,和执行端页面上那些按钮一模一样。
这样做是安全的,理由很简单:发起配对、确认配对、取回 token 的是同一个进程。验证码从来不离开 app,也就没有一条能被别人发给你的链接。而确认必须带页面的 CSRF 令牌,别的网站没法让你的浏览器替它确认一条配对。
名字用的是这台 Mac 自己的名字(系统设置 › 通用 › 关于本机)。你已经有同名的执行端时,Bonsai 把新的这台叫「…… 2」。app 每次启动最多自动试一次;没成功的话菜单里会写原因,点「连接这台 Mac…」再试一次,窗口没登录时会改走确认页。
token 放在哪里
放在 ~/.bonsai/bonsai-runtime.toml,只有你自己能读(权限 0600)。写入用的是原子替换,读它的人看到的要么是旧的一组,要么是新的一组,不会是两组混在一起。这个文件别人也能读的话,执行端拒绝启动。
app 自己不写这个文件。它把 token 从标准输入交给 app 包里的 bonsai(bonsai login --token-stdin),所以 app 和命令行共用同一份格式和校验。token 不会出现在命令行参数、日志、launchd 的 plist 或 app 的偏好设置里,也不会传给执行端拉起的 agent:每个子进程的环境变量里,执行端的那几个都被去掉了。
连接本身
有了凭据,执行端连 wss://livewithbonsai.com/runtime,请求头里带 Authorization: Bearer <token>。token 从不放进网址,服务端也只接受 wss://。
第一条消息必须是 runtime.hello,用来介绍这台机器:有哪些 harness、各自有哪些模型和设定、能在哪些项目里干活。每个项目是执行端自己起的一个 ID,加上归一后的 git remote,比如 github.com/you/app,网址里带的凭据会被去掉。**hello 里没有任何本机路径。**路径只留在你的电脑上,Bonsai 认项目只认 remote。
Bonsai 回 runtime.welcome,里面写着这台机器的主人。之后:
- 执行端每 25 秒发一次
ping。60 秒没收到pong,就断开重连。 - 网络断了按指数退避重连:从 1 秒开始,每次翻倍,最长 1 分钟,带一点随机抖动。
- 重连之后,由 Bonsai 逐个问执行端它该知道的那些 run。执行端不会自己把什么都重发一遍。
Bonsai 会记下最近一次的 hello,所以机器离线时,执行端页面仍然能显示它的项目和模型。至于在不在线,永远只看那条活着的连接。
新执行端能做什么
配对一完成,Bonsai 就把这台新执行端启用到你的个人空间,开放「这台机器上的全部项目」。你加入的其他空间要不要用它,由你在执行端页面上决定。
那么,哪些文件夹算「这台机器上的项目」?只有你添加的那些。执行端一开始一个都没有,也不会自己去扫整个家目录:在设置窗口里点「添加文件夹…」,或者运行 bonsai folders add <路径>,加一个仓库,或者装着很多仓库的上层文件夹。在这些文件夹里,执行端启动时和之后每 10 分钟找一遍带 remote 的 git 仓库:最深 5 层,最多 150 个,最近用过的优先;跳过隐藏文件夹、node_modules、target、Library 和媒体文件夹。点文件夹旁边的「移除」,或者运行 bonsai folders remove,它里面的项目就不再给 Bonsai 用。
笔记可以写它对应哪个仓库,但没法让执行端指向某个文件夹。从 remote 到路径的对应关系,只存在于你的电脑上。
断开
在执行端页面上删除它。它的 token 立刻失效:Bonsai 取消它手上的 run 并关掉连接,执行端停掉 Bonsai 在它上面开的所有会话,不再重连。
之后 app 不会悄悄把这台 Mac 再注册回去,因为删除是你有意做的。菜单和设置窗口会写明这台 Mac 已从 Bonsai 删除,并给出「连接这台 Mac…」,点一下就重新配对。
命令行的用法、怎么选文件夹等完整说明,见文档连接你的电脑。