← 项目
Decaf

Decaf

一个 macOS 菜单栏应用——只在编程 agent 真正干活时让 Mac 保持清醒,agent 一旦在等你,就放它去睡。

类型
其他
角色
独立开发
Status
进行中
Tech
Swift 6 SwiftUI AppKit IOKit FSEvents Swift Package Manager XcodeGen Swift Testing GitHub Actions Homebrew Cask
Started
2026年8月
Decaf 的应用图标——一杯咖啡

Decaf 是一个懂得何时停下的 caffeinate 菜单栏版本。编程 agent 真正在干活时,它让 Mac 保持清醒;agent 一结束——或者只是在等你回话——它立刻松手。

唯一的规则:跑二十分钟的工具调用是工作;屏幕上一个没人回答的提示不是。

Decaf 菜单——Claude Code 正在工作,三个会话各自标注状态,下方是手动保持清醒的控制项和显示选项

整个应用就是这个菜单:什么在跑,以及你能对它做什么。

为什么做这个

这个品类里的每一个防休眠工具,本质都是一个你必须记得关掉的开关。忘了关,Mac 整夜不睡;关得太早,一次长构建死在半路。

难点从来不是”阻止休眠”——那只是一次 IOKit 调用。难的是判断:把”agent 正在思考”和”agent 正在等你”区分开,而且要稳到误报(Mac 永远不睡)和漏报(构建到一半睡过去)都不常发生。

安装

brew install --cask AlanY1an/decaf/decaf

或者直接从 最新 release 下载已签名并公证的 DMG。需要 macOS 14 或更高版本。

四种菜单栏图标状态:空闲、手动保持、agent 工作中并显示会话数、被安全保护暂停

四种菜单栏状态。第一种下 Mac 会正常休眠。

它怎么判断

三层检测,从最精确的开始。它们不是互相替代的方案——下层专门补上层的盲区。

信号精度角色
L1 · hooksClaude Code 在 SessionStartUserPromptSubmitNotificationStopSessionEndPostToolUse 上执行的辅助程序精确到这一轮对话主路径。经你明确同意后安装,深度合并,不动你已有的 hooks
L2 · 文件活动~/.claude 上的 FSEvents,5 分钟空闲窗口约 5 分钟零配置兜底。看不到一轮的边界,只知道还有东西在写
L3 · CPU 采样proc_pid_rusage持续只作证,不触发。它只能推翻一个 hold,永远不能发起一个

其中两个推断承担了大部分判断:

  • 权限弹窗。 你点”允许”时 Claude Code 什么都不发,所以”批准过了”的证据是工具自己跑完了——一个标记为等待权限的会话收到 PostToolUse,就直接回到工作态。工具不可能在自己的弹窗没被回答时完成,这个推断因此站得住。
  • 等待信号。 当 agent 声明自己要等——定时唤醒、带超时的监听——这个声明会以工具调用记录的形式落进 transcript。Decaf 读出那个时间点,陪它一起等,而不是在这段空档里睡过去。

一次二十分钟的构建既不写文件也不打印任何东西。必须四个证人都同意某个会话确实安静下来了,hold 才会释放——这正是它能撑过长构建的原因。

Hook 程序的契约

一个行为不当的 hook 会拖垮 Claude Code 本身,所以 decaf-bridge 被刻意做成仓库里最小的东西:读 stdin,解析五个头部字段,连 UNIX socket,写一行 JSON,退出码 0。任何失败都静默地以 0 退出——不写 stdout(那会被当成上下文注入你的对话),不写 stderr,绝不返回非零,整个过程被一个 90 ms 的看门狗兜住。它只允许 import 一个模块,CI 脚本会检查链接库白名单,确保这条约束一直成立。

安全护栏

  • 每个 assertion 都带 30 分钟的 TimeoutActionRelease——万一 Decaf 崩了,powerd 会释放它,而不是让 Mac 永远醒着。
  • 续期是先建后放,在超时时间的一半处执行,从不用 IOPMAssertionSetProperty 延长。
  • 低电量模式、快速用户切换、电量过低都会释放 hold。合盖永远优先。

隐私

Decaf 监听 ~/.claude,这件事需要一个直接的回答:它不发任何网络请求——没有遥测,没有统计,连更新检查都没有;它从不读你的对话内容,只读时间戳、会话 id 和 token 计数。解析器是封闭枚举,字段被测试钉死,所以想放宽它会直接让构建失败,而不是指望 code review 抓住。

它写入 ~/.claude/settings.json 之前,会把要合并的 JSON 原样给你看;设置里可以一键撤销。

发布

v0.1.0 已签名并公证发布,DMG 之外还配了 Homebrew tap,brew install --cask 开箱即用。Xcode 工程由 XcodeGen manifest 生成且不入库,CI 在每次 push 上跑 package 测试。

它不在 Mac App Store 上,以后也不会——沙盒让 Decaf 做的事变成不可能,而不只是不方便。

名字

这个品类里所有东西都以兴奋剂命名。这个以”知道何时停下”命名。副标题保留 caffeinate 这个词,因为那是你已经认识的命令;但应用本身刻意不叫这个名字,这样它永远不会遮住 /usr/bin/caffeinate

打个招呼 👋