跳到正文

Notch So Good

一只叫 Chawd 的像素螃蟹住在 MacBook 的刘海里,替你盯着正在跑的编程 agent:它干活时那里是一个计时器,它需要你时刘海展开成按类型着色的通知,它要权限时按钮直接出现在刘海里。

Screenshot of Notch So Good
编辑截图, 29 Sep 2026Notch So Good ↗

这是什么

一个 macOS 菜单栏应用,把 MacBook 的刘海变成运行中编程 agent 的状态显示。Claude Code 或 OpenAI Codex CLI 干活时,一条黑色胶囊从刘海两侧展开,里面是动画像素螃蟹和一个实时会话计时器;agent 需要输入时刘海展开成按类型着色的通知,点一下跳回它所在的那个终端窗口;权限请求则用刘海里的 Allow / Always Allow / Deny 按钮,或 ⌃⌥A 与 ⌃⌥D 两个快捷键回答。它读取 agent 自己机器上的登录信息来显示会话与每周额度条,自己不连任何服务器。

谁做的一位独立开发者,GitHub 账号可以追到 2016 年,写这条记录时有一个公开关注者。87 次提交全是他写的,十一个 pull request 全是对自己的仓库开的,十六个 release 全是他发的;成果有三种分发方式——自己 tap 里的 Homebrew cask、一个 npm 包、一个 curl | bash 脚本——后面还接着能自更新的构建。

它是怎么搭起来的

组成 · 6

一个 SwiftUI 菜单栏应用,旁边坐着一个小 Python 桥接,两者用一个 Unix 域套接字 /tmp/notchsogood.sock 和一套共享的事件词汇连起来。这套分工就是设计本身:应用持有状态和呈现,桥接只负责与 agent 对话、不做任何决定;而因为 Claude Code 的 hook 是双向的,权限那条路是反着走的——hook 阻塞在套接字上等刘海给答复,所以一次权限是请求与响应,而不是一条通知。应用关于一个会话知道的每一件事都是作为事件到达的,它显示的每一件事都由这些事件推导出来,所以胶囊、展开的通知和菜单栏弹窗是同一份会话列表的三个视图,而不是三条代码路径。没有服务器也没有账号:额度是从使用者自己机器上的登录信息里读的——Claude Code 在 Keychain 里的 OAuth 凭证、Codex CLI 的 ~/.codex/auth.json——唯一可选的网络调用是遥测,除非某次构建带上了 key,否则它一直关着。

NotchSoGood/ —— 应用本体
一个 Swift 包(Package.swift,macOS 14+),分成 Models/(SessionStatus、NotificationType、PermissionMode、AgentSource)、Services/(38 KB 的 NotificationManager、26 KB 的 PermissionServer、SessionFileWatcher、UsageLimitsStore、UsageLimitsParser、StatsStore、Telemetry)、Views/(六个文件,其中 SessionPillView.swift 一个就是 76,007 字节)、Windows/(NotchPanel 和一个 20 KB 的 NotchWindowController)以及 Utilities/(NotchGeometry、NotchShape、PillLayout、MotionTokens、ProcessTree、WindowMatcher、TerminalLauncher、DisplayRouter、HotkeyManager)。
HookInstaller/ —— 桥接
15 KB 的 hook.py 是 agent 唯一会执行的东西;四个 shell 脚本负责安装它(Claude Code 用 install-hooks.sh,Codex 用 install-codex-hooks.sh,检测到的所有 agent 用 install-all-hooks.sh,curl 路径用 get.sh),两个测试程序覆盖它:test_hook.py 与 integration_test.py。桥接对状态是只读的:它转发事件,而遇到 PreToolUse 就等。
套接字与事件集合
Claude Code 收到十个事件——九个即发即忘、五秒超时(SessionStart、SessionEnd、Stop、Notification、UserPromptSubmit、PreCompact、SubagentStart、SubagentStop、PostToolUse),加上 130 秒的 PreToolUse,好让它比应用自己 120 秒的决策窗口活得更久。Codex 收到五个(SessionStart、Stop、UserPromptSubmit、PreToolUse、PostToolUse),写进 ~/.codex/hooks.json,同时还要在 config.toml 里开启 codex_hooks 开关。每个事件都带上 agent 当前的权限模式,这正是刘海不会把同一个会话卡两次的原因。
Tests/ 与 run-tests.sh
六项检查,没有用 XCTest。每个套件都是一个普通的 main.swift,用 swiftc 和它所覆盖的那几个源文件一起编译——ElapsedFormatter、ProcessTree 配 WindowMatcher、UsageLimitsParser、以及 DisplayRouter 配 NotchGeometry 与 MascotView——再加上 Python 的 hook 测试,和一次被要求「不得产生任何 warning」的 release 构建。额度解析器是拿 Tests/Fixtures/oauth-usage.json 里录下来的一份 API 响应喂的,不是发真请求。
打包与更新
Homebrew tap 用的 Casks/notch-so-good.rb、npx 路径用的 npm/install.mjs、以及 install.sh、uninstall.sh、build-app.sh、build-universal.sh、release.sh、generate-icon.swift,再加一份给 Sparkle 用的 appcast.xml。三条安装路径、一条构建流水线,而版本号同时出现在应用、cask、npm manifest 和 appcast 里。
仓库层面的 agent 材料
CLAUDE.md 有 2,858 字节,最长的一节是一份设计简报——用户是谁、品牌口吻(「weird, warm, technically precise」)、带具名参考与反参考的美学方向,以及五条设计原则,第一条是「硬件原生感」。它旁边放着 plans/、带三十个软链接的 skills-lock.json、提交进仓库的 .gstack/qa-reports/ 下的 QA 报告、PH/ 里的 Product Hunt 文案,以及一份 llms.txt。

取舍,以及它替代了什么

  • 用 Unix 域套接字,而不是 localhost 上的 TCP 端口 替代 最初几个版本监听的 TCP 端口 27182

    v3.0.0 的 pull request 一行给了两个理由——更快,而且「更安全(靠文件系统权限)」。

  • hook 命令是一个真正的 Python 文件 替代 嵌在设置文件里的一段 JSON 转义单行命令

    安装器自己的注释:「转义形式不可读、不可测、也容易被弄坏」。这次重写和它所暴露出的那处更正写在同一条 release note 里——hook 超时曾经大了 1000 倍。

  • 超时以秒计,并把 PreToolUse 给到 130 秒 替代 把这个值当成毫秒

    安装器里写明 Claude Code 内部会把这个数字乘 1000,而 PreToolUse「必须比应用自己 120 秒的决策窗口活得更久」——正是先前那个错误的数值掩盖掉的两个事实。

  • 额度从使用者自己机器上的登录信息里读 替代 账号、服务器,或一次经代理的 API 调用

    README 写的是「No cloud, no accounts」,额度解析自 Claude Code 的 Keychain 凭证与 Codex 的 ~/.codex/auth.json。Keychain 的读取被换成 Apple 自己的 /usr/bin/security 工具,理由明确写着:这样「一次 Always Allow 就能在每一次更新后继续有效」,而不是每个版本都重问一次。

  • bypass、auto、plan 模式下不再弹第二次提示 替代 只要 hook 触发就让刘海问一次

    这些模式意味着 agent 已经在自己决定了,而因为 PreToolUse 是双向的,那次多余的提示会拦住工具,而不只是重复一个决定。acceptEdits 同样跳过文件编辑的提示,会话行上则有一枚徽章显示当前模式。

依据README 的「How It Works」一节、v3.0.0/v3.1.0/v4.0.0/v4.3.0/v4.4.0 的 pull request 正文、4.4.0 与 4.5.0 的 release notes、CLAUDE.md、run-tests.sh、plans/README.md、skills-lock.json、HookInstaller/install-hooks.sh,以及仓库的文件树。

制作过程

7 个阶段
  1. 01

    五个月、十六个 release、七个星

    仓库创建于 2026-03-12,第一次提交的标题是「Initial release — Dynamic Island for Claude Code」;到 2026-08-13 的五个月里,它累积了 87 次提交和十六个打了 tag 的 release,从 v1.1.0 一路到 v4.5.0,没有一个是预发布。分发有三种形态:Homebrew cask、一个只负责下载应用本体的 npm 包、以及一个 shell 脚本,之后由 Sparkle 负责更新。提交曲线并不平缓:三月 62 次、四月 7 次、五六月没有、七月 12 次、八月 6 次,而最后一次提交是 4.5.0 的 appcast 更新,不是代码改动。2026-09-30 时仓库的数字是 7 个星、0 个 fork、0 个 watcher、1 位贡献者——对应的是一个打包、签名、自带更新、已经有十六个 release 的应用。87 次提交里 82 次挂在作者的 GitHub 账号上,另外 5 次用的是一台 MacBook 的本地邮箱。74 次提交把某个 Claude 模型写成了共同作者——Opus 4.6 四十次、百万上下文的 Opus 4.6 二十三次、Fable 5 六次、同上下文的 Opus 5 五次——而其中三次的尾注是在单行提交信息里用字面量 \n 写的,于是 GitHub 上整条信息读起来是一行,那条尾注从来不算一条尾注。

  2. 02

    一份带健康分的 QA 报告,以及它的结论后来落到了哪

    2026-03-19,仓库里多了一份 .gstack/qa-reports/qa-report-notchsogood-2026-03-19.md:一次对 v1.2.2 的十五分钟审查,放在以 gstack 命名的目录下——那是本档案单独收录的一个交付工具。它给这个构建打了 62/100 的健康分,记下 1 个 critical、3 个 high、5 个 medium、11 个 low,分项是视觉 85、性能 70、UX 65、可访问性 60。critical 那条说的是三个动画演示——wave、walk、peek-a-boo——会让应用以 EXC_BAD_ACCESS (SIGSEGV) 崩溃,那次测试生成了四份崩溃报告。报告把原因追到 DemoWindowController.open():MiniChawdView 创建的计时器会回调进一个正在 autorelease pool 排空时被释放的视图层级;它还指出这个崩溃是非确定性的,取决于某个计时器间隔是否恰好和释放时机撞上。二十四条脚本化测试通过,失败的正好是三个崩溃的演示。这些建议有一条能追下去的后半生:报告要求给 NotificationManager 加 @MainActor,这个标注今天就在那个类上;报告要求悬停以 60fps 加两帧去抖,v4.0.0 把 30fps 轮询换成了事件驱动;报告要求输入校验、VoiceOver 标签、以及把通知发到 agent 正在跑的那块屏幕,v4.4.0 上线了文本长度截断、胶囊/行/按钮的 VoiceOver 标签,并把「跟随显示器」当作头条。本档案记的是这个对应关系,不是因果——release notes 从没说哪条建议推动了哪处改动。

  3. 03

    那个拦住了它自己所问工具的权限提示

    v4.4.0 的 pull request 正文是一份写成了 release note 的四段 bug 报告,第一段是这个仓库里最锋利的一次失败。Claude Code 的每一份 hook 载荷都带 permission_mode,作者是读 2.1.220 的二进制确认的——它就坐在基础 hook schema 里 session_id 和 cwd 旁边。应用从来没读过它,于是一个本来就跑在 bypass 模式下的会话,仍然会从刘海收到第二次询问;而因为 PreToolUse 是双向的,那次询问不只是重复了 agent 的决定,它把工具拦住了。修法是让 bypassPermissions、auto 和 plan 直接放行、把 acceptEdits 视为覆盖文件编辑、在这些模式下一并抑制权限类通知,并在会话行上放一枚显示当前模式的徽章。同一个 release 还修了三个彼此独立的原因造成的状态不准——其中一个是在每个 handler 里都写着的 guard let idx = firstIndex(...) else { return },它把应用没看着启动的那些会话发来的事件全部静默丢弃——并把用于定位宿主终端的「嗅探环境变量」换成了沿 hook 的进程树往上走,这才让 tmux、ssh 和登录 shell 下的窗口定位真正可用。仓库本地的 allow 规则(.claude/settings.local.json)也开始和全局规则一起被遵守。

  4. 04

    两个安装器,其中一个装的是旧版 hook

    4.5.0 的说明记下了同一类问题里更安静的一种:install.sh——源码安装那条路径——「一直在写入它自己那套过期的、4.0 之前的 hook」,于是源码安装得到的 hook 集合与其它每一条进入这个应用的路径都不同;现在它装的是和别处一样的那一套。hook 这一层本身在更早一个 release 就被重建过,安装器在自己的注释里写了原因:hook 命令「调用一个真正的 Python 文件(hook.py),而不是一段经过 JSON 转义的单行命令——转义形式不可读、不可测、也容易被弄坏」。这次重写是和它所暴露出来的那处更正一起宣布的:hook 超时曾经大了 1000 倍。安装器现在给九个即发即忘的事件设五秒超时,给 PreToolUse 设 130 秒,因为后者必须比应用自己 120 秒的决策窗口活得更久。它会在 hook.py 编译失败时拒绝继续,会在改 settings.json 之前先备份,并在那个文件不是合法 JSON 时中止——一个解析不了的 hook 会让 agent 的工具调用一直挂着。

  5. 05

    十一个 pull request,没有一条来自别人的评论

    十一个 pull request 全是作者对自己的仓库开的,每一个下面正好有一条评论,全部来自一个审查机器人:九条里写着「Review failed — The pull request is closed」,一条是额度用尽的通知并带倒计时(「Next review available in: 58 minutes」,同一段里还写明按文件计费),一条真的产出了这次改动的走查。没有一个 pull request 上的任何一条评论来自人类。 它们的正文读起来是 release note 而不是提案,这也符合那套工作流——pull request 是已完成工作的记录:v4.3.0 那条以「QA: release build clean, code-reviewed (one race found and fixed), signed zip sha-verified」结尾,更早的一条为一次权限系统重写报告了「40/40 QA tests passing」——那次重写会先读 Claude Code 自己的 allow 规则、危险模式设置和 MCP 工具名,再决定到底要不要弹这个提示。

  6. 06

    按哈希锁定的技能,扇出到三十个工具

    仓库里有两样东西描述了一种本档案在别处没见过的形态。skills-lock.json 是一份把技能当依赖的锁文件:四条记录,每条写明上游仓库和该文件的 sha256——apple-design、improve-animations、review-animations 来自 emilkowalski/skills,linkedin-content 来自 inferen-sh/skills。最后那个的真身是 .agents/skills/linkedin-content/SKILL.md,9,648 字节;而三十个名叫 skills/linkedin-content 的软链接指向它,一个落在三十个不同 agent 的 dot 目录里:.adal、.agent、.augment、.claude、.codebuddy、.commandcode、.continue、.cortex、.crush、.factory、.goose、.iflow、.junie、.kilocode、.kiro、.kode、.mcpjam、.mux、.neovate、.openhands、.pi、.pochi、.qoder、.qwen、.roo、.trae、.vibe、.windsurf、.zencoder,再加仓库根目录下的一个。另一处,plans/ 里放着五份编号的动画计划,每一份都标着 DONE,而它的 README 定死了执行顺序——「005(先做 token——001/003 要引用它)→ 001 → 002 → 003 → 004」——并写了一条专门给并行作业看的约束:「001 和 002 都要动 NotchNotificationView.swift——串行执行,不要并行」。

  7. 07

    为了被找到它做了什么,以及那换来了什么

    仓库里备齐了一套发布的行头。llms.txt 3,302 字符,是写给 agent 而不是写给人看的;PH/product-hunt-listing.md 里放着标语、图集说明、maker 留言和一条「首次回复评论」的模板——全都在任何发布之前就提交进了仓库。作者还把自己的项目投给了至少两份清单:一次标题是「Add Notch So Good — macOS notch companion for Claude Code」,另一次把它描述成一个带像素伙伴的、基于刘海的会话监视器。到 2026-09-30 为止,这一切换来的是 7 个星、0 个 fork、0 个未关闭 issue。这个位置并不空:一个叫 TokenNotch 的项目在 4.5.0 发布同一天创建,从额度那一侧覆盖同样的两个 agent,并把自己的吉祥物说成是官方的那两只——本记录写作时它有 18 个星。README 把吉祥物放在最前面,而星数是本档案关于「读者拿它做了什么」仅有的证据。

相关档案

全部档案 →