Relaymote 通过读取 Claude Desktop JSON 文件 + Chrome DevTools Protocol 接管权限弹窗,实现手机远程控制 Claude Code 会话。
我在 Claude Desktop 的 Code 标签页里同时开着很多 Claude Code 会话。走开一段时间回来时,通常会有一个会话正停在权限提示那里。我希望能从手机上查看和回复这些会话。必须要是同一个会话,而不是副本。关键在于,这个桌面应用没有任何公开 API。
这篇文章讲的是我在 Relaymote(MIT 协议、Node.js、Windows 优先)里最终采用的设计方案,其中的经验教训适用于任何"为不支持远程控制而生的桌面应用的遥控器"。
这个划分让其他所有事情都变得可行:
读取从不触碰应用本身。 Claude Desktop 会为每个 Code 会话写入一个小的 JSON 文件(包含标题、文件夹、模型、归档状态),Claude Code 则把每个对话写成一份 .jsonl 转录文件。Relaymote 直接读取这些文件,并且从末尾读取转录内容,因此长会话也能快速打开。所以即桌面应用已经关闭,手机依然能列出和读取会话。
执行操作则通过应用本身。 某些操作只存在于应用内部:发送消息、响应权限提示、重命名、归档。对于这些操作,Relaymote 会挂载到应用主进程的调试器上(Chrome DevTools Protocol,地址是 127.0.0.1)。如果应用有执行该操作的内部方法,就调用该方法;只有在没有这类方法时,才回退到驱动 UI 的方式。
读取操作是流量的大头,因此桌面更新改变 UI 时,只会影响操作,绝不会影响查看功能。
当有人正坐在键盘前时,驱动 UI 是危险的。每一个 UI 操作都经过一个序列化的通道,并等待一个空闲门控:即短时间内没有键盘或鼠标输入。因此这个遥控器从构造上就是礼貌的——它不会跟你抢焦点。
手机端是一个 Web 应用,这就带来了一个安全问题:任何能向编码 Agent 发送消息的东西,间接地都可能在 PC 上执行命令。于是:
控制端口仅绑定到回环地址(loopback only)。
应用端口的每次请求都需要访问密钥。配对过程是一个 QR 链接,密钥只交换一次,存为 HttpOnly cookie。
在 Cloudflare tunnel 后面,密钥仍然是必需的。如果配置了 Cloudflare Access,其 JWT 会被验证(签名、受众、签发者、过期时间),绝不会单独从请求头信任。
推送通知是端到端加密的(RFC 8291),密钥在机器上生成。
两个小功能在实际日常使用中证明最为重要:
自动恢复。 当会话因用量限制停止时,Relaymote 记录重置时间,并在时间到达后发送一个简短的"continue"。应用退出时处于中途的会话会在下次启动时被检测到并继续。
从被动读取获取状态。 状态圆点和未读状态来自每 30 秒读取一次侧边栏。没有什么是为了看一眼状态而专门打开的。
尽早把读取和执行分开。文件是稳定的接口;UI 不是。
对任何涉及 UI 的操作都要序列化,并且用人类空闲状态作为门控。
把"能发消息"当作"能运行代码",同等力度地锁好门。
把脆弱性写下来。我的在 README 里:桌面更新可能破坏操作功能,直到修复版本发布,而读取功能保持正常。
代码在 GitHub 上,如果你想看调试器桥接层(lib/bridge.js)或空闲门控的 UI 通道(lib/desktop.js)。如果你做过 Electron 应用的 CDP 自动化,我很期待收到反馈。
非官方;与 Anthropic 没有附属关系或背书。