我以为自己在做一个小功能。
最后才发现,我其实是在和 macOS 的权限链、进程宿主、图形会话狠狠干架。
我想做的事听起来非常简单:
我有一台远端 Mac,Chrome 里会打开微信登录二维码页面。
机器一重启,登录态可能丢,需要重新扫码。
但我看不到这台 Mac 的屏幕——我唯一能操作它的入口,是飞书。
所以我冒出一个特别直接的想法:
能不能我在飞书里发一句“截图”,这台 Mac 就把当前页面截下来,再回传给我?
真正做起来我才发现:难的根本不是“截图”本身,难的是它背后的系统边界:
- 飞书消息怎么进到本机服务
- 本机服务怎么调用 macOS 的 GUI 截图能力
- 截图怎么回传到飞书
- 为什么同样一段代码,Terminal 里能跑,后台启动却死活不行
- 为什么“自动启动”不等于“开机后立刻可用”
最终我落地了两个能力:
- 飞书私聊发截图指令 → 本机截图 → 回传图片
- 登录 macOS 后自动用 Terminal 启动 longconn → 保住截图权限链
这篇文章把过程、踩坑和最终方案一次讲清楚。
一、这个需求表面是截图,实质是远程可视化能力
我最终想要的效果其实是一句话:
在飞书里发“截图”,远端 Mac 把当前页面截回来给我。
因为现实就是:
- Chrome 会出现微信登录二维码页面
- 重启后登录态可能丢
- 人不在机器旁边,看不到屏幕
- 飞书是唯一可用的远程入口
所以“截图”不是一个孤立脚本,而是一条完整链路:
- 入口:飞书消息
- 执行:Mac 本机(且要能碰 GUI)
- 回传:飞书图片消息
一句话:**这不是单点脚本问题,是在线链路能力。**我最初只是想做一个简单功能:在飞书里发一句“截图”,远端 Mac 就把当前页面截回来。
真正落地时才发现,难点根本不是截图命令本身,而是完整链路:飞书消息如何进入本机服务、本机进程是否具备 macOS 图形会话与录屏权限、截图结果如何稳定回传。
这次排障给我最大的教训是:
- 同样代码,Terminal 启动能截图,后台启动可能失败;
- 这不是代码对错,而是进程宿主与权限链差异;
- 先把链路边界画清楚,再谈功能优雅。
最终我把截图能力直接接进 longconn 的在线消息链,支持“截图 / 截图 微信 / 截图 登录 / 截图 全屏”,并把启动方式固化为:登录后由 Terminal 拉起 longconn,保证权限稳定。
如果你也在做远程自动化,这件事非常值得先搞明白:你以为在做“一个小功能”,其实你在做“系统边界工程”。
二、我一开始差点走错:这事不能先做 skill
第一反应很容易是:做个截图 skill。
但很快我意识到不对,因为用户需要的不是“指导你怎么截图”,而是:
飞书消息一到,本机立刻截图,并把结果发回去。
这必须接入“正在运行的飞书长连接服务”,也就是 longconn.js。
所以我最终选择:直接改工程,把截图能力接进 longconn 的消息处理链路里,而不是先做 skill。
结论:这个需求的核心不是 skill,而是在线服务能力。
三、最终我把截图入口接在了 longconn 里
现在截图命令由 longconn.js 直接处理,支持:
- 截图
- 截图 微信
- 截图 登录
- 截图 全屏
整体链路:飞书消息 → longconn.js 收到事件 → 解析截图命令 → 激活 Chrome/Chromium → 读取窗口 bounds → 调用 screencapture → 上传飞书图片 → 回发图片+文字说明。
这意味着:截图能力被“内嵌”进 longconn 的在线链路里,而不是绕一圈再调外部工具。
四、代码层面我加了什么
1)新增环境变量
SCREENSHOT_DIR=./outbox-screenshotsSCREENSHOT_APP_NAMES=Google Chrome,ChromiumSCREENSHOT_WINDOW_TITLE_KEYWORD=COMMANDER_DISPATCH_URL=http://127.0.0.1:18801/dispatch
2)新增关键函数(longconn.js)
parseScreenshotCommandfocusChromeWindowcaptureChromeScreenshotuploadFeishuImageFromFilesendFeishuImageByOpenId
到这里功能“看上去”已经完整了,但真正折磨人的坑,从这儿开始。
五、踩坑 1:我以为在写截图,其实在写“谁有资格截图”
一开始我把问题想成:怎么把 screencapture 调起来。
但真正的问题是:
- 谁在执行截图?
- 执行者有没有录屏权限?
- 这个进程是不是处在可用的图形会话里?
结论:这件事不是命令执行,而是“在线进程在特定宿主中执行 GUI 操作”。
六、踩坑 2:AppleScript 读 Chrome 标签页标题不稳定,别硬刚
我最初想做得更“聪明”:用户发“截图 微信”,就去找标题包含“微信”的标签页精准截图。
但在这台机器上,title of active tab of front window 会直接报 AppleScript 语法错误(-2741)。
最终我做了取舍:
- 命令格式保留“关键字”(截图 微信/登录)
- 实现不再依赖标题匹配
- 统一退化为:激活前台浏览器窗口 → 直接截图
结论:不要过早依赖 Chrome 标签页词典,前台窗口截图更稳。
七、踩坑 3(最核心):同样代码,Terminal 能截图,后台启动却不行
我遇到过这个报错:
screencapture exit 1: could not create image from display
关键对比:
- launchctl 后台启动 longconn:截图失败
- 在已授权录屏的 Terminal.app 手动启动 longconn:截图成功
这说明问题不在代码,而在进程宿主与权限链。
结论:在 macOS 上,“同一份代码由谁拉起来”会直接决定截图能不能成功。
八、踩坑 4:截图链路通了,不代表普通消息链路也通了
截图命令在 longconn.js 内本地完成,不依赖 commander;普通文本消息则依赖 COMMANDER_DISPATCH_URL -> commander -> openclaw。
因此会出现“截图能用,但普通消息报 ECONNREFUSED 127.0.0.1:18800”的情况。
根因:启动时没显式设置 COMMANDER_DISPATCH_URL,进程回退默认 18800。
结论:同一个服务里有多条链路,验证了一条不代表另一条没问题。
九、为什么改截图逻辑,不需要重启整个 openclaw
边界要分清:
- 截图逻辑在
longconn.js内执行 → 改完只重启 longconn - 普通问答/任务转发依赖 commander/openclaw → 才需要动 commander/openclaw
结论:别为了截图去重启全套系统,排障会被自己拖慢。
十、当前最稳定的启动方式:手动从 Terminal 拉起
cd /Users/bonnie/.openclaw/workspace/feishu-bridge
set -a
source ./.env
set +a
/opt/homebrew/bin/node ./longconn.js
启动成功会看到:
[longconn] connected. waiting for events...
写在最后
这次经历让我彻底意识到:
你以为在做“截图功能”,其实你在做的是“远程可视化能力的系统工程”。
如果你也在做类似的远程自动化,记住一句话:
先把链路边界画清楚,再谈功能优雅;先把权限宿主打通,再谈自动启动。
欢迎留言交流你踩过的权限坑。