Harbor Light:用悬浮灯看 Codex / Cursor 跑没跑完

仓库:https://github.com/yuanmomoya/harbor-light
现在是 0.1.0,macOS 12+、Windows 10/11(x64 / ARM64)都能装。程序是 Rust 写的,不依赖 Python 或 Swift 运行时。
平时把活丢给 Codex 或 Cursor 之后,我经常切去干别的。过几分钟再切回来,有时已经写完了,有时卡在权限确认,有时其实还在跑。来回切窗口挺烦的,就做了这个小工具:桌面上挂一盏红绿灯,🟡 在干活,🔴 在等你,🟢 是刚做完。
源码和安装包都在上面那个仓库里。Releases 页可以直接下:
https://github.com/yuanmomoya/harbor-light/releases
灯怎么看
| 状态 | 含义 | 效果 |
|---|---|---|
| 🟡 黄 | 模型在思考或在跑工具 | 呼吸 |
| 🟢 绿 | 这一轮结束了 | 亮一下,大概 3 秒回到空闲 |
| 🔴 红 | 要你点允许,或者报错了 | 闪得比较急 |
| 🔴🟡 红 + 黄 | 有一个在等你,另外还有任务在跑 | 两盏一起闪 |
| ⚪ 三灯都暗 | 没在干活 | 低亮 |
窗口是个黑色胶囊,拖到副屏也行,位置会存下来。显示器拔掉之后如果窗口跑到屏幕外面,会回到主屏右上角。Mac 点菜单栏图标,Windows 右键托盘,可以看状态、重装 Hooks、退出。
一个状态文件不够用
最早只给 Codex 用,状态写在 ~/.codex-status.json 里。一个文件记一个状态,后写的覆盖先写的。后来要接 Cursor,这个就撑不住了。
比如 Codex 那边项目 A 还在跑,灯是 🟡。Cursor 那边项目 B 刚好跑完,往同一个文件里写了 🟢。界面就会显示绿灯,Codex 其实还没停。
现在改成每个软件、每个对话各写一份 json:
~/.harbor-light/activities/<provider>/<activity-id>.json
字段大概是这些:
provider
activity_id
conversation_id
generation_id // 可选
workspace
state // working | waiting | done
event
updated_at
主键用 provider + conversation_id + generation_id。只按进程或者项目路径分会漏:同一个仓库里也能同时开两个对话。
悬浮窗还是只有一组灯,颜色按所有活动算,优先级是 waiting > working > done > idle。有 waiting 就 🔴,没有 waiting 但有 working 就 🟡,只剩刚结束的 done 就 🟢 一下。已经结束的绿灯,只要别处还有 waiting / working,就不参与显示。过期和心跳也是按单条活动算的,不会因为其中一个还在跳,把别的一起续上。
Cursor 退了只清 Cursor 的文件,Codex 还在跑的不会被带走。要是 Hook 没把结束事件发出来,这条活动会在 30 分钟没更新之后自己掉回空闲,不然 🟡 会一直挂着。
怎么拿到状态
走官方 Hooks。Codex 和 Cursor 各自把事件打到 harbor-light hook --provider ...,程序写成活动文件,再聚合成灯。对话正文不读,也不往外传。
Codex Hooks ─┐
├→ 适配器 → ~/.harbor-light/activities/<provider>/*.json ─┐
Cursor Hooks ┘ ├→ 聚合 → 灯
Codex rollout JSONL(兜底)─────────────────────────────────────────────┘
macOS 用 FSEvents 盯活动目录,大概 2 秒再扫一遍。Windows 大概 1 秒轮询一次进程、活动文件和 Codex 的 rollout。Codex 同一条会话可能同时出现在 Hook、旧的 ~/.codex-status.json 和 rollout 里,聚合前按 provider + 会话 ID 去重,Hook 的结果优先。
Codex
| 事件 | 灯 |
|---|---|
SessionStart / UserPromptSubmit / PreToolUse / PostToolUse | 🟡 |
PermissionRequest | 🔴 |
Stop / SessionEnd | 🟢,大约 3 秒后空闲 |
Hook 没点信任、漏触发、或者还是旧安装的时候,会再扫 ~/.codex/sessions/**/rollout-*.jsonl,里面的 exec_approval_request、apply_patch_approval_request、request_permissions 这些也能把 🔴 点起来。
Cursor
配置在 ~/.cursor/hooks.json,项目级是 <项目>/.cursor/hooks.json。官方文档:https://prod.cursor.com/docs/hooks
| 事件 | 处理 |
|---|---|
beforeSubmitPrompt | 🟡,当一轮开始 |
preToolUse / afterAgentThought | 续期,不新建任务 |
带 approval_required=true 或 permission=ask 这类字段 | 🔴 |
afterShellExecution / afterMCPExecution | 已有活动改回 🟡;新开一条容易被迟到事件重新点亮,所以不新建 |
stop(completed/aborted) | 🟢 |
stop(error) | 🔴 |
sessionEnd | 清掉这条对话 |
sessionStart | 只登记,不亮 🟡。开了个 Composer 不等于开始干活 |
preToolUse 不能当 🔴。自动批准的工具也会走这个事件,误报很多。
beforeShellExecution(sandbox=false) 也没拿来当「在等你点允许」。Cursor 现在没有单独的 PermissionRequest,字段也分不清「弹窗等你」和「马上要执行」。如果非沙箱命令一律亮 🔴,winget、装依赖都会误报。所以现在只有事件里写明要审批,或者执行报错,才走红灯。IDE 里那种 Plan 审批,Hooks 目前看不出来。
用户级 Hook 只管本地 Agent。Cloud Agent 不跑用户级 Hook,得往每个仓库装项目级的,这版没自动配。
软件崩了或者直接关掉时,靠进程兜底:
- macOS:
com.openai.codex、com.todesktop.230313mzl4w4u92 - Windows:
chatgpt.exe/codex.exe、cursor.exe
正常结束还是以 Hook 为准,进程退出只负责把对应 provider 的活动清掉。
TRAE 还没接
TRAE 3.5.66 加了 Hooks,设置里能配。但公开资料比较少,这几件事还不清楚:
- 事件完整列表,stdin 里的 JSON 长什么样
- 有没有稳定的会话 / 轮次 / 工作区 ID
- 正常结束、手动停止、报错能不能分开
- 有没有权限确认这种事件
- 普通对话、SOLO、子代理是不是同一套
所以没直接套 Cursor 的事件名。也没去读它的 SQLite 或 globalStorage,那些文件格式会变,里面经常有完整提示词,而且「正在跑」和「历史刚写进去」不好分。
后面打算先挂一个只记事件名和结构的 Hook,把提交、完成、手动停、工具确认、报错、关对话、两个项目同时跑、SOLO 这些场景过一遍。协议够用再接到现在的 provider 层;会话 ID 或结束事件不稳的话,最多做成有限支持,不会靠读内部库或者模拟点击窗口来硬做。
项目以前叫 Codex Light,后来改成 Harbor Light,就是因为后面不打算只接 Codex。
安装
https://github.com/yuanmomoya/harbor-light/releases
Windows
下 HarborLight-0.1.0-windows-x64-setup.exe,ARM 机器用 arm64-setup.exe。装到 %LOCALAPPDATA%\HarborLight,当前用户权限就行,会合并 Codex / Cursor 的用户级 Hooks,并加登录自启动。安装包没签名,SmartScreen 拦了就「更多信息 → 仍要运行」。
如果下的是 zip,解压后先跑一次:
.\HarborLight.exe install
只双击 exe 不会写 Hooks。
macOS
双击 HarborLight.pkg。没签名的话,按住 Control 再点打开。源码安装:
make install
安装是往现有 hooks.json 里合并,不会把你自己的条目盖掉。卸载也只删这个工具写进去的那几行。
手动配的话命令得写绝对路径,比如:
# macOS
/Applications/HarborLight.app/Contents/MacOS/harbor-light hook --provider cursor
# Windows
%LOCALAPPDATA%\HarborLight\HarborLight.exe hook --provider cursor
Cursor 这边 timeout 给了 8 秒,思考内容太长时 stdin 容易把默认超时撑爆。Codex 3 秒够用。
装完新开一轮对话,看 ~/.harbor-light/activities/codex/ 或 cursor/ 下面有没有 json,或者翻 ~/.harbor-light.log。
没做的几件事
对话 transcript 默认不读。
没用辅助功能去扫权限窗口,标题和控件树换个版本就对不上,还得要额外权限。
辅助功能、屏幕录制这些权限默认都不申请。
托盘里尽量显示项目名,完整路径不往外摊。
这些主要是图省事、少出幺蛾子。对方改内部目录或者改个窗口标题,这种方案最先挂。
要自己接别的 Agent 的话
先把多活动存储、聚合、按 provider 清理做完,再写适配器。🔴 只绑明确的「等用户」事件,工具调用不要拿来猜。安装器合并配置,卸载只删自己的条目。Windows 路径带空格、macOS 在 App Bundle 里的绝对路径,升级的时候都容易出问题。
能跑通这几条基本就够用了:两个软件同时跑时不会因为其中一个结束就显示空闲;手动停止后 🟡 会灭;退出 Cursor 不影响 Codex;两个项目能在托盘里分开看;过期按单条清,不定时把全部状态重置掉。
仓库再放一次:https://github.com/yuanmomoya/harbor-light
更多推荐


所有评论(0)