先说结论:CodexBar 是一个面向 Windows 的原生桌面用量仪表盘。它把 Codex 与 Cursor 分散在本地文件、会话记录、CLI 查询和服务端接口里的信息,整理成一个常驻托盘的小窗口:额度、Tokens、模型使用、估算费用、年度活动热力图,以及加载中、暂无数据、缓存和异常状态,都在同一个界面里表达清楚。
这个项目的出发点并不是“再做一个漂亮的统计卡片”,而是解决一个很实际的问题:不同客户端对总消耗、周额度和费用的口径可能不同,用户看到的数字也可能在切换账号、切换提供商或网络失败后短暂失真。CodexBar 更看重数据来源、状态边界和失败时的诚实表达。

一、为什么需要一个本地用量仪表盘
日常使用 Codex、Cursor 这类工具时,真正需要关注的通常不是单个请求,而是几个连续变化的量:
- 本周额度还剩多少,距离重置还有多久;
- 本周和累计 Tokens 到底是多少;
- 哪些模型消耗最多,是否存在某个模型突然异常放大;
- 估算费用是按什么价格口径算出来的;
- 最近一年哪些日期使用最集中,某一天究竟消耗了多少;
- 当前显示的是实时结果、缓存结果,还是暂时没有取得数据。
如果这些信息分散在多个网页、终端命令和数据库文件里,用户很难建立稳定的认知。更麻烦的是,界面在“尚未加载完成”时如果直接显示 0%,就会把“没有数据”伪装成“没有消耗”;切换 Codex 与 Cursor 时,如果旧结果覆盖了新结果,也会出现额度突然变成 100% 的错觉。
二、CodexBar 当前提供什么
- 提供商切换:在 Codex 与 Cursor 之间切换,页面只展示当前提供商对应的数据。
- 额度与 Tokens:展示本周额度、剩余比例、重置时间、本周 Tokens 和累计 Tokens;不同提供商使用各自真实可获得的字段。
- 模型使用情况:按模型聚合消耗并排序,帮助快速发现主要使用来源。
- 估算费用:明确标注为估算值,不把估算结果伪装成账单;Cursor 的赠送用量与标准价格口径分开处理。
- 近一年活动:使用 365 天热力图显示每日活动强度,单元格带轻微圆角,鼠标悬停可以看到具体日期和当日消耗。
- 托盘提示:鼠标悬停系统托盘图标时,同时显示 Codex 和 Cursor 的剩余额度。
- 可解释状态:首次获取、刷新、缓存、离线、异常和暂未获取,都有明确的文字状态。

三、整体架构:原生窗口 + 提供商适配 + 可验证状态
项目没有引入浏览器壳,而是采用 Windows 原生 Tkinter 界面,配合 ctypes/Win32 能力实现无边框窗口、圆角效果、托盘图标和窗口置顶等桌面行为。这样做的优点是启动成本低、安装包小、常驻时资源开销可控,也更容易直接访问当前 Windows 用户自己的本地状态。
- 界面层:
codexbar_desktop.py负责窗口、主题、提供商切换、卡片、热力图、模型列表、加载和错误状态。 - 数据层:分别解析 Codex 与 Cursor 的本地记录,并把外部查询结果归一化为界面需要的额度、Tokens、模型和费用字段。
- 缓存层:缓存写入当前用户目录,不把某一台开发机的结果打进 EXE;缓存只用于改善体验,不替代实时数据。
- 托盘层:
tray_icon.py独立处理 Windows 托盘消息、图标绘制和资源释放,避免托盘异常拖垮主窗口。 - 打包层:
CodexBar.spec通过 PyInstaller 组织资源与隐藏导入,输出可直接运行的 Windows EXE。
数据流可以概括为四步
- 从当前 Windows 用户的 Codex/Cursor 数据源读取原始记录。
- 解析日期、模型、输入输出 Tokens、编辑活动、额度和费用相关字段。
- 统一数值格式,并保留“未知、缺失、缓存、异常”等状态信息。
- 在 Tkinter 主线程中更新界面;后台线程只负责耗时的读取、CLI 或网络查询。
四、不同用户为什么可以各自看到自己的数据
CodexBar 的 EXE 只包含程序和资源,不包含开发者账号、密码、Token 或某一位用户的固定数据。程序启动后,针对“当前登录 Windows 用户”读取本机目录:
- Codex 侧使用当前用户的
%USERPROFILE%\.codex,读取会话数据库、JSONL 日志以及可用的 Codex CLI 账号/额度查询结果。 - Cursor 侧使用当前用户的
%APPDATA%\Cursor\User\globalStorage\state.vscdb等本地状态,并结合可获得的 Cursor 用量接口。 - 缓存、刷新时间和提供商选择也写在当前用户目录下,不与其他 Windows 用户共享。
因此,同一个 EXE 被不同用户打开时,读取到的就是各自用户目录和各自登录状态。没有本地数据、没有登录态或接口暂时不可用时,界面显示“暂未获取”,而不是假装显示 0。这个约束是项目的核心原则之一。
五、为什么要把“真实值、估算值和未知值”分开
用量统计最容易出错的地方不是画图,而是口径。CodexBar 在数据展示上遵循三个边界:
- 真实统计:来自本地会话/日志或提供商返回的字段,尽可能保留原始单位和日期。
- 估算费用:根据已知 Tokens、模型和价格口径计算,只能作为趋势参考,明确标注“估算费用”。
- 未知状态:字段缺失、接口失败或正在加载时,显示“暂未获取”或“加载中”,不把空值转换成 0。
尤其是 Cursor,赠送用量、标准价格和实际账单并不是同一个概念。项目把赠送用量单独表达,避免把标准价乘出来的数字误认为平台账单;未来如果提供商调整返回字段,也可以在适配层里修正,而不必改动整个界面。
六、近一年热力图:从“有使用”到“哪一天用了多少”
年度热力图参考 Codex 的活动视图,按最近 365 天排列为 53 列 × 7 行。颜色深浅表达当天活动强度,空白表示没有可用记录;单元格加入轻微圆角,减少大面积网格带来的机械感。鼠标悬停时显示具体日期和当天消耗,而不是只给一个模糊的颜色等级。
显示单位也根据提供商和数据口径处理:Codex 可以直接显示 Tokens,并在较大数字时换算成“万、千万、亿”;Cursor 保留其可获得的活动单位,避免把 edited 行数、Tokens 或额度百分比混成一个没有解释的数字。这样热力图既有可比的趋势,也不会掩盖提供商之间的定义差异。

七、切换和刷新为什么不会再“闪一下旧数据”
CodexBar 的加载流程有一个很重要的细节:每次开始读取提供商数据时都会创建新一代的加载标识。后台任务完成后,只有仍然属于当前一代的结果才允许回写界面;如果用户在任务完成前切换了提供商,旧任务的结果会被丢弃。
generation = begin_provider_load()
result = load_from_local_and_remote_sources()
if generation != current_provider_generation():
return # 旧任务不能覆盖新页面
render(result)
配合这个机制,切换到 Codex 或 Cursor 时会先进入 loading 状态;已有缓存时保留可解释的旧内容并在状态栏提示同步中,没有缓存时显示加载占位,而不是把空数据渲染成 100% 或 0%。
八、这次工程质量工作的重点
- 重构复杂的加载和窗口更新路径,减少 UI、数据解析和状态判断互相穿透。
- 补上窗口销毁、热力图销毁、托盘清理和后台任务回写之间的竞态保护。
- 对外部查询增加异常捕获和降级路径,网络或 CLI 失败时保留有效缓存。
- 托盘图标和 GDI 绘制资源在
finally路径释放,避免常驻应用慢慢积累句柄。 - 清理无效的 UI 重建逻辑和重复分支,默认置顶设置为关闭。
- 补充类型标注、边界测试和真实 EXE 启动检查。
截至 2026 年 8 月 9 日,当前工程验证结果为:97 个测试通过;标准 mypy 与严格 mypy 检查通过;Python 编译检查通过;PyInstaller 打包成功;对最终生成的 dist\CodexBar.exe 做过独立启动烟测。当前总体测试覆盖率约为 44%,这说明工程质量已经有基础,但后续仍应继续提高数据解析异常、外部接口失败和真实多用户环境的覆盖率。
九、如何运行和打包
项目使用 Python 作为开发语言,Windows 打包命令如下:
py -3 -m PyInstaller CodexBar.spec --noconfirm
打包完成后运行 dist\CodexBar.exe 即可。首次运行时,程序会根据当前用户环境尝试读取 Codex 和 Cursor 状态;如果用户尚未登录、目录不存在或服务暂时不可访问,应该看到“暂未获取”,这属于设计上的可解释结果,而不是安装失败。
十、当前限制与下一步
- 提供商接口、客户端本地数据库结构和价格口径可能随版本变化,适配器需要持续维护。
- 估算费用不是账单,不能替代平台最终结算。
- 某些数据依赖当前用户的本地登录状态;程序不会也不应该把账号凭据打包进安装文件。
- 测试覆盖率仍有提升空间,尤其是跨用户目录、网络超时、损坏数据库和高频切换场景。
- 后续可以继续完善数据源版本探测、更多离线诊断信息、导出报表以及更细的模型成本口径说明。
我希望 CodexBar 最终成为一个“可信的桌面状态面板”:它不承诺所有外部数据永远可用,但会明确告诉你当前看到的是什么、数据来自哪里、是否经过估算、是否仍在加载,以及什么时候需要重新获取。对于每天都在使用 AI 编程工具的人来说,这种可解释性比一个看起来很精确、却无法说明来源的数字更有价值。
本文基于 CodexBar 当前工程快照整理,界面截图中的额度和费用仅用于说明视觉结构,不代表读者自己的账户数据。