作者为Meta Muse Code CLI编写了开源桌面客户端Helicon,踩坑PTY交互方案来实现批准机制(Agent执行npm test等敏感操作时的用户授权),记录了为何简单的字符串匹配不可靠,并开源了完整实现。
Meta 在 8 月发布了 Muse Code:一款运行在终端里的编程 Agent,订阅费用每月 5 美元起。模型效果不错。它的运行环境以子 Agent 并行执行,并维护一份只追加的事件日志,可以随时回放。
有两件事困扰了我足够久,值得花两周去解决。
它是纯终端应用。在 Windows 上根本没有原生构建,所以现在好几篇对比评测都告诉读者:如果你用 Windows 或者想要一个桌面应用,就该选竞品。
于是我做了 Helicon:一款面向实际 Muse CLI 的开源桌面和 Web 客户端。这篇文章讲的是那一个设计决策,它决定了所有其他事情,因为这是我最先犯错、也是大多数人在包装 CLI 时都会犯错的地方。
我最先犯的错误:爬取终端
给 CLI 套上一层 UI 最直接的方式是:在 pty 中启动它,解析输出内容,然后渲染出来。
我就是这么做的。花了半天时间,还真跑通了——那种"演示版"意义上的跑通。
然后我尝试实现审批功能。Agent 想执行 npm test,用户需要批准或拒绝。在一个被爬取的终端里,"Agent 正在请求审批"只是你从 ANSI 转义码中用模式匹配抠出来的一串字符串,你的回应是写回 pty 的按键。你在从屏幕碰巧显示的格式化文本里去推断一个安全决策。上游每一次输出格式的调整都会变成你客户端的一个正确性 bug,其中某些 bug 会执行出没人批准过的命令。
Meta 发布了 Muse Code SDK,MIT 许可,用于通过 Muse Session Protocol 编程驱动 Muse。CLI 本身暴露了 muse serve,该命令通过 stdio 使用 MSP 协议通信。
所以 Helicon 的守护进程在每个工作区启动一个 muse serve 主机,并通过官方 SDK 与它对话 MSP。Muse 是 Agent。Helicon 是客户端。
这意味着审批作为协议事件到达,而不是解析后的文本。Diff 作为结构化数据到达。在终端启动的会话会带着历史记录出现在侧边栏里,因为 CLI 已经把它们按协议可回读的格式写入磁盘。没有什么东西是从屏幕外观推断出来的。
我给任何想包装 CLI 的人的建议是:如果厂商提供了协议文档,协议就是唯一不会在你眼皮底下移动的界面。输出格式不是 API,不管今天它看起来多稳定。
决定账单的那部分
这是我没有预料到会成为最重要功能的部分。
Muse Code 订阅只在 Muse 自己的运行环境里计费。如果你用通用的 OpenAI 兼容客户端指向这个 API,你就会按即用即付费率计费。同一个模型,不同的账单。
我见过有人在公开场合艰难地算清楚这一点,然后得出错误结论。某个帖子中有一个用户两次声明 Muse Code 订阅"不能在任何 GUI 运行环境里使用",说除了终端以外想在哪里用都得走即用即付。同一个帖子的另一个人确认了这个症状:使用厂商的运行环境从套餐里扣费,而其他所有选项都落在 API 上。
这个观察是对的。结论不对。
订阅没有被锁在终端里。它是被锁在运行环境里的。一旦你不再试图替换运行环境,而是开始驱动它,GUI 就不产生任何额外成本,因为工作量依然是通过 Muse、用你自己的 muse 账号流过去的。
这就是整个架构的全部论点,它比我能构建的任何 UI 功能都更有价值。如果你的包装器用原始模型 API 重新实现了 Agent 循环,你就悄无声息地把用户拖到了一份他们已经付过钱的模型的第二份账单上。
Muse 没有 Windows 二进制文件。这不是客户端能修复的事。
Helicon 做的事比"Muse Code 在 Windows 上运行"听起来更无聊。守护进程原生运行在 Windows 上,当它需要 Agent 时执行:
wsl -d Ubuntu -- muse serve
然后它要做双向路径转换,因为守护进程用 C:\Users\you\project 思考,而 Agent 用 /mnt/c/Users/you/project 思考。搞错的话每次文件操作都会落在一个看似合理实则错误的地方。
export function toWindowsPath(wslPath: string): string {
const match = wslPath.match(/^\/mnt\/([a-z])\/(.*)$/);
if (!match) {
throw new Error(`Cannot map to Windows: not a /mnt/<drive> path: ${wslPath}.`);
}
const drive = match[1].toUpperCase();
const rest = match[2].replace(/\//g, "\\");
return `${drive}:\\${rest}`;
}
注意那个 throw。无法映射的路径是一个 bug,不是可以随便猜的东西。
所以 WSL2 依然是必需的,我在欢迎页、安装步骤和常见问题里都写了这一点。需要改变的不是这个前提,而是你不再需要在自己机器上蹲在 Ubuntu 终端里来用一个你付过钱的工具。安装包有签名并支持自动更新,这在 Windows 上比来自其他平台的人想象的更重要。
守护进程是 Node。直到昨晚,这意味着用户还需要安装 Node 22 或更高版本 Helicon 才能启动,而在 Windows 上他们得装在 Windows 侧而不是 WSL 内部——这正是那种让人摸不着头脑的说明。
解决方案是 Tauri sidecar。构建脚本下载一个固定版本的 Node 构建,验证其 SHA256 与发布的校验和一致,然后把它放到 src-tauri/binaries/node-<target-triple>。对于通用 macOS 构建,lipo 合并两种架构。应用启动时优先使用可执行文件旁边的二进制文件,只有在找不到时才回退到系统 Node。
它在 Windows 安装包里约占 30MB。它从安装说明里去掉了整整一步,也去掉了整整一类"打不开"的问题。值。
一个值得复制的细节:bundled runtime 的文件夹被有意从传递给子进程的 PATH 中排除。用户通过应用运行 shell 命令,他们的 node 应该是他们自己的,不是我的。
项目按文件夹分组,工作树也包括在内。线程里有内联 diff,显示编辑发生在哪一行。审批在到达的瞬间就浮出水面,绝不批量处理,绝不绕过。一个成本视图展示每个线程按公布 API 费率计算会花多少钱,这样"这个套餐值不值"就变成了一个数字而不是感觉。命令面板、斜杠命令、全键盘操作。
同一套 React UI 发两套:作为 Tauri 桌面应用,以及作为指向远程守护进程的 Web 应用。一套代码库,两个壳。
macOS 构建还没有 Apple 公证,所以首次启动需要右键再点打开。Linux 没有打包构建,只有源码路径。SDK 是开发者预览版,所以协议可能在我脚下移动。Helicon 也不是这个空间里唯一的 GUI——有几个,包括面向 Zed 和 JetBrains 的 ACP 适配器,以及一个非官方的 VS Code 扩展。如果你想要在编辑器里用 Muse,用那些。Helicon 是给想要独立工作区的人准备的。
它是免费的,MIT 许可,非官方:不是由 Meta 制作、赞助或背书的。
github.com/HarjjotSinghh/helicon · helicon.sh
如果你在 Windows 上用 Muse Code,我想知道哪里会坏。那是我测试最多却最难验证的平台。