我一开始只是想在 macOS 菜单栏里放几个数字。
Codex 的 5 小时和 7 天额度还剩多少,什么时候重置,最近每天大概用了多少。这样就不用工作到一半再打开客户端页面,或者等到请求被限制以后才想起来看。
这听起来很像一个周末小工具。一个状态栏图标,一块弹出菜单,几行数据。
真正做 CodexUsage 以后,我发现界面反而是最轻的部分。数字从哪里来、刷新一次要读多少文件、应用能不能碰现有凭据、菜单关着时还要不要工作,以及别人下载后能不能正常打开,每一件都比画图标更像产品问题。
菜单栏只有一点点地方,却把整条工程链压得很紧。
在显示数字以前,先说清楚它代表什么
用量工具最怕的不是数字暂时拿不到,而是几个口径不同的数字被拼成一个看起来很精确的答案。
CodexUsage 会按顺序尝试不同数据来源。优先使用 Codex CLI 已保存的 OAuth 凭据请求服务端用量接口,得到额度窗口与重置时间;请求不可用时,再回退到本地会话日志里的结构化 rate_limits 快照。每日 token 趋势则来自本地 JSONL 事件聚合,事件缺失时才读取 SQLite 中的线程级汇总。
这些来源不能假装完全等价。服务端额度回答「这个账号还剩多少」,本地趋势更接近「这台机器最近发生了什么」。账号范围、统计时机和数据粒度不同,本地日趋势与服务端页面出现差异并不奇怪。
界面要把来源和回退状态表达出来。与其给用户一个无法解释的精确百分比,我更愿意让他知道当前看到的是服务端额度,还是本地快照。
数字可信,不只是因为计算没写错,也因为它愿意交代自己从哪里来。
借用已有凭据,不等于把凭据变成自己的资产
应用需要使用 Codex CLI 已经保存的 access token 才能请求目标接口,但它不需要读取密码,也没有理由再复制一份 token 到自己的配置里。
这条边界如果只写在 README 里还不够。代码不能把 token 发往其他服务,错误处理不能把认证内容落进日志,本地派生索引只保存趋势计算所需的信息,不保存凭据。安全问题也应该进入私密报告渠道,而不是让用户把日志和 token 贴到公开 Issue。
桌面工具离用户的本地环境太近了。它常常能读文件、访问钥匙串、发送网络请求,也因此很容易用「为了方便」解释权限扩张。
我更愿意反过来问,每一份数据是不是完成当前功能真的需要,使用以后能不能不留下新的副本。读取现有凭据是一项能力,也是一条需要主动收紧的责任边界。
一个菜单展开动作,不该顺手扫描几个月日志
每日趋势来自不断增长的 JSONL 会话文件。最直接的实现,是每次打开菜单都重新扫描最近 30 天。数据少时几乎感觉不到,文件多起来以后,一个本该安静待在状态栏里的工具会开始卡顿,甚至持续占用 CPU。
所以远端额度与本地趋势使用不同刷新节奏。额度按设置周期请求;趋势只在菜单展开且缓存过期时更新。第一次读取分块扫描并建立派生索引,后续尽量只处理新增字节。
增量读取真正难的地方不是记住读到哪里,而是知道这个位置什么时候失效。账号范围变化、时区变化、日志被截断或替换,都可能让旧索引不再可信。缓存必须连同失效条件一起设计,否则只是把过期答案保存得更快。
菜单栏动画也有类似问题。普通窗口里一次看起来很顺滑的 SwiftUI 动画,如果常驻以后每秒触发布局和重绘,几个小时后就会变成明显能耗。静止时要真的停止,菜单收起后也不该继续刷新。
状态栏工具的性能不能按一次点击来算,要按它陪用户待上一整天来算。
swift run 能启动,离可安装应用还很远
开发环境里跑起来,只证明源码在当前机器上工作。真正交给别人时,还需要结构正确的 .app、DMG、自动更新 ZIP 和校验文件。
Bundle ID、版本、架构、资源、签名和公证都要进入验证范围。DMG 能不能挂载和拖拽安装,ZIP 解压以后应用是否能启动,GitHub Release 下载回来的文件能否通过 SHA-256 校验,自动更新是否能找到约定名称的 ZIP 并正确替换应用,这些都不是 swift run 能提前替我们证明的。
ad-hoc 签名与 Developer ID 签名也不能混在一句「支持 macOS 安装」里。前者适合本机或明确告知限制的构建;普通用户希望下载后直接打开,则需要 Developer ID 签名与公证配合 Gatekeeper。
一个小应用的发布链不会因为窗口很小就自动缩短。
原生小工具最好的体验,是一直在却几乎感觉不到
菜单栏里最终只显示几个字符。用户不会因为背后有分层数据源、增量索引和多种制品就更喜欢它,也不应该被迫理解这些东西。
但工程上把这些事做好,正是为了让他不用理解。数字有来路,刷新不打断工作,凭据没有被复制,动画不会悄悄耗电,更新也不会把一个能用的应用换成打不开的包。
我做 CodexUsage 以后,对「小工具」这三个字多了一点敬畏。界面小,只代表暴露给用户的表面小,不代表背后的责任少。
状态栏里的几个数字真正值得信任时,它们看起来应该很平常。
就在那里,安安静静的。