ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Claude Code用量监控:打造macOS菜单栏Token小工具

Claude Code用量监控:打造macOS菜单栏Token小工具 经常写 Claude Code 的开发者应该都有过这种经历代码正改到一半突然收到用量超限的提示只能停下来等窗口重置。网络上有人把这个场景做成了一个很轻量的解决方案——一个体积很小、挂在菜单栏上的 Claude 用量小工具小到你不用打开任何面板扫一眼菜单栏就能决定“现在还能不能继续跑”。这篇文章会沿着这个思路从零拆解这样一款工具的设计与实现包含数据来源分析、代码示例、开机自启配置和排错清单。1. 背景Claude Code 用量监控为什么是刚需1.1 Claude Code 是什么Claude Code 是 Anthropic 推出的终端 AI 编程代理工具。和网页版 Claude 不同你可以在终端里通过命令行直接和它交互让它读取项目文件、分析报错、生成代码、执行测试甚至做多文件的重构。对很多把 Claude 当主力编程助手的开发者来说Claude Code 已经是日常开发流程的一部分。不过Claude Code 的底层依然要调用大模型接口也就意味着有“用量”这个概念。不同订阅档位、不同模型的调用都有额度限制一旦长时间高密度使用很可能触发限流导致一段时间内无法继续调用。1.2 用量的不确定性带来的问题使用 Claude 或 Claude Code 时用量消耗并不总是线性的。你可能会遇到下面几种情况一个包含大量上下文的重构任务可能一次对话就消耗大量 token。Claude Code 自动读取项目文件后输入 token 会快速增长。频繁使用技能、多轮对话、长日志分析都会明显拉高用量。代码助手用起来太顺手往往“一不小心”就连续跑了几个小时。如果等到请求被拒绝时才意识到用量超了开发节奏已经被打断了。此时还需要再切换工具、等窗口重置效率影响很大。1.3 菜单栏小工具的价值菜单栏小工具的核心价值是把“用量数据”从后台搬到前台。你不需要打开浏览器不需要敲一个命令也不需要切换到另一个窗口。只要眼睛扫一下屏幕右上角就能看到当前大概消耗了多少 token。设计这种工具的难点不是“显示数据”而是“让数据足够直观”这也是标题里提到的“small enough to read before you run it”——在你决定运行下一步操作之前就能判断要不要继续。与其等超限报错不如提前从数据上预判。这也正是这类工具吸引人的地方把看不见的后台消耗变成了菜单栏上一个可读的指标。2. 整体设计思路2.1 工具要解决的核心问题我们要实现的是一个跑在 macOS 菜单栏上的小应用它需要满足几个关键要求常驻在菜单栏不占用 Dock 位置。能以极简文字展示用量比如只显示 token 总量的缩写如“C 128K”。点击菜单栏图标后可以查看近期会话的详细统计。支持定时刷新最好是可配置刷新间隔。足够轻量不依赖较大的运行时。在动手前先要把数据来源想清楚。没有稳定可靠的数据来源后面的展示再好看也没有意义。2.2 数据来源选型Claude 的用量数据目前没有一个完全统一的本地接口但从 Claude Code 的产品形态来看主要有三种思路方案说明优点局限解析本地会话日志Claude Code 会在本地目录记录会话 JSONL 文件里面包含每次调用的 usage 字段不依赖额外接口反映的是当前机器上的真实调用量只能统计当前机器上的会话跨设备数据无法覆盖调用 Anthropic API 的模型用量信息API 响应中带有 usage 字段可在自己写的调用逻辑里累积数据精确适合自己开发应用时统计对 Claude Code 这类现成工具拿不到它的运行密钥官方后台或账户页面手动查询在账户设置中查看订阅用量最接近官方真实额度无法自动化集成到菜单栏对菜单栏小工具来说最可行的方案是“解析本地会话日志”。这是最稳妥也最容易实现的方式不需要额外权限也不需要把 API Key 交给第三方小工具。2.3 技术选型macOS 菜单栏应用有几种常见实现方式方案运行方式优点缺点Python rumpsPython 脚本启动后创建一个菜单栏 App代码短、开发快、容易改需要本机有 Python 环境Swift NSStatusItem编译为原生 macOS 应用体积小、性能好、无外部依赖代码量比 Python 多一些Electron Tray基于 Node.js / Web 技术前端能力强、跨平台打包体积大、内存占用偏高Node.js menubar基于 Electron 的轻量封装对有 JS 经验的开发者友好同样存在 Electron 体积问题如果你追求轻量和可维护性Python rumps 是最合适的。rumps 是一个专门用来开发 macOS 菜单栏应用的 Python 库接口非常简洁几分钟就能写出来一个小工具。下面我以它为例展开完整实现。3. 环境准备与项目结构3.1 环境要求在开始写代码前先确认以下环境项目建议操作系统macOS 12 或更高版本Python3.9 或更高版本rumps0.4.0 或更高版本Claude Code已在本机安装并使用过有会话日志生成版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你还没有安装过 Claude Code需要先安装并完成一次登录与对话这样本地才会生成可分析的会话数据。3.2 安装 rumps在终端中创建项目目录并安装依赖mkdir -p ~/claude-usage-menu cd ~/claude-usage-menu python3 -m venv venv source venv/bin/activate pip install rumps如果你希望全系统都能直接运行也可以使用pip install --user rumps。不过在 macOS 上更推荐用虚拟环境避免影响系统自带的 Python 环境。3.3 项目结构建议按下面的方式组织文件claude-usage-menu/ ├── claude_usage_menu.py # 主脚本 ├── requirements.txt # 依赖清单 └── README.md # 使用说明其中requirements.txt内容很简单rumps0.4.0这样在换电脑或重新部署时只需要pip install -r requirements.txt就能恢复环境。4. 解析 Claude 用量数据4.1 理解 usage 数据格式要解析数据首先要知道数据长什么样。Claude Code 的会话记录以 JSONL 格式保存在本地默认目录是~/.claude/projects/。目录下的每个.jsonl文件对应一次项目会话文件名通常是项目路径经过编码后生成的字符串。这些 JSONL 文件中的每一行都是一个 JSON 对象记录了会话过程中的一次事件。在 assistant 类型的事件里通常包含一个message字段而message.usage就是一次 API 调用的 token 消耗情况。典型的 usage 结构如下{ usage: { input_tokens: 1250, output_tokens: 418, cache_creation_input_tokens: 0, cache_read_input_tokens: 5120 } }各字段含义input_tokens本次请求的输入 token 数。output_tokens本次响应的输出 token 数。cache_creation_input_tokens写入缓存的输入 token 数。cache_read_input_tokens命中缓存后读取的 token 数。其中缓存 token 在长上下文中很常见也是用量占比很高的一部分。如果只统计 input 和 output会明显低估实际消耗。4.2 从会话日志中统计 token在写代码之前建议先手动确认一下日志路径ls -lh ~/.claude/projects/ | head -20如果能看到一堆.jsonl文件说明日志路径正确。接下来可以统计日志中的 usagegrep -o input_tokens:[0-9]* ~/.claude/projects/*.jsonl | awk -F: {s$2} END {print s}这个命令只是用来快速验证数据是否存在。正式脚本建议使用 Python 来处理因为 JSONL 解析更健壮还能顺便统计各类 token 的分布。4.3 获取精确剩余额度的限制这里需要提醒你一下本地日志只能统计“当前这台机器、当前登录账号在本地产生的会话消耗”。它不能直接拿到官方账户的精确剩余额度。不同订阅档位的额度策略由官方后台控制可能会按时间段滚动重置也可能与套餐档位有关。如果你需要精确的剩余额度数据最可靠的方式是登录官方账户页面查看。本地小工具更适合用来做“消耗趋势提醒”而不是“精确额度仪表盘”。明白了这个边界后我们的统计目标就是把本地会话日志中的 token 消耗读出来按分类累加再展示到菜单栏。5. 实现菜单栏展示5.1 用 rumps 创建菜单栏应用rumps 的核心是App类和Timer类。我们先创建主脚本的骨架import json from pathlib import Path import rumps CLAUDE_DIR Path.home() / .claude / projects REFRESH_SECONDS 60这里的CLAUDE_DIR是日志目录REFRESH_SECONDS是菜单栏的刷新间隔。建议不要设置太短比如 10 秒刷新一次因为每次刷新都要读取并解析一批 JSONL 文件间隔太短会白白消耗 CPU。5.2 统计函数编写一个函数用来扫描目录下的所有.jsonl文件解析其中的 usage 字段并累加def collect_usage_stats(): stats { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0, message_count: 0, found: False, } if not CLAUDE_DIR.exists(): return stats for jsonl_file in CLAUDE_DIR.glob(*.jsonl): try: with open(jsonl_file, r, encodingutf-8) as fp: for line in fp: line line.strip() if not line: continue record json.loads(line) usage record.get(usage) if not usage: message record.get(message) if message: usage message.get(usage) if not usage: continue stats[found] True stats[input_tokens] int(usage.get(input_tokens, 0)) stats[output_tokens] int(usage.get(output_tokens, 0)) stats[cache_read_tokens] int(usage.get(cache_read_input_tokens, 0)) stats[cache_creation_tokens] int(usage.get(cache_creation_input_tokens, 0)) stats[message_count] 1 except (json.JSONDecodeError, OSError): continue return stats这段代码做了几件事先判断日志目录是否存在不存在就直接返回空统计。遍历所有.jsonl文件。逐行解析 JSON同时兼容 usage 在顶层或嵌套在 message 里的两种结构。分别累加输入、输出、缓存读、缓存写 token。用found标记本地是否真的有可用日志。如果某个文件损坏或包含非法 JSON脚本会跳过该文件不会因为单条坏数据导致整个程序崩溃。5.3 格式化显示数字菜单栏空间有限直接显示“123456789”这种长数字并不友好。我们需要一个格式化函数把大数字转换为缩写形式def format_tokens(count): if count 1_000_000: return f{count / 1_000_000:.1f}M if count 1_000: return f{count / 1_000:.0f}K return str(count)运行结果示例输入 tokens1.2M 输出 tokens850K 缓存读取560K 缓存写入128K这种缩写形式正是“小到能看清”的关键在菜单栏上显示C 2.1M比显示完整数字更易读。5.4 创建菜单栏应用接下来创建App子类把统计函数接入菜单栏class ClaudeUsageApp(rumps.App): def __init__(self): super().__init__(ClaudeUsage, titleC ...) self.menu [ rumps.MenuItem(正在读取用量请稍候...), None, rumps.MenuItem(立即刷新, callbackself.refresh), rumps.MenuItem(退出, callbackself.quit), ] self.refresh() self.timer rumps.Timer(self.refresh, REFRESH_SECONDS) self.timer.start() def refresh(self, _None): stats collect_usage_stats() total_tokens ( stats[input_tokens] stats[output_tokens] stats[cache_read_tokens] stats[cache_creation_tokens] ) self.title fC {format_tokens(total_tokens)} self.menu.clear() self.menu.add(rumps.MenuItem(f输入 tokens{format_tokens(stats[input_tokens])})) self.menu.add(rumps.MenuItem(f输出 tokens{format_tokens(stats[output_tokens])})) self.menu.add(rumps.MenuItem(f缓存读取{format_tokens(stats[cache_read_tokens])})) self.menu.add(rumps.MenuItem(f缓存写入{format_tokens(stats[cache_creation_tokens])})) self.menu.add(rumps.MenuItem(f消息条数{stats[message_count]})) self.menu.add(None) self.menu.add(rumps.MenuItem(立即刷新, callbackself.refresh)) self.menu.add(rumps.MenuItem(退出, callbackself.quit))这里需要注意几点self.title是菜单栏上显示的标题我把它设置成类似C 1.8M的短文本。self.menu.clear()会清空旧菜单避免重复添加菜单项。rumps.MenuItem的callback参数指定点击该菜单项时触发的函数。rumps.Timer(self.refresh, REFRESH_SECONDS)会每隔 60 秒调用一次refresh。None在菜单列表中表示分隔线。最后添加启动入口if __name__ __main__: ClaudeUsageApp().run()到这里一个最基本的 Claude 用量菜单栏工具就完成了。5.5 运行与验证在项目目录下运行python claude_usage_menu.py正常情况下菜单栏右上角会立刻出现一个C 0或C 0.9M类似的文字。点击它会展开一个菜单展示各类 token 的统计信息。如果你在菜单栏中看不到任何内容可以查看终端输出。rumps 在创建菜单栏应用时如果权限不足或运行环境异常通常会打印对应的错误信息。6. 完整代码整合6.1 完整脚本把上面的代码整合到一个文件里完整版如下# 文件路径claude-usage-menu/claude_usage_menu.py import json from pathlib import Path import rumps CLAUDE_DIR Path.home() / .claude / projects REFRESH_SECONDS 60 def collect_usage_stats(): stats { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0, message_count: 0, found: False, } if not CLAUDE_DIR.exists(): return stats for jsonl_file in CLAUDE_DIR.glob(*.jsonl): try: with open(jsonl_file, r, encodingutf-8) as fp: for line in fp: line line.strip() if not line: continue record json.loads(line) usage record.get(usage) if not usage: message record.get(message) if message: usage message.get(usage) if not usage: continue stats[found] True stats[input_tokens] int(usage.get(input_tokens, 0)) stats[output_tokens] int(usage.get(output_tokens, 0)) stats[cache_read_tokens] int(usage.get(cache_read_input_tokens, 0)) stats[cache_creation_tokens] int(usage.get(cache_creation_input_tokens, 0)) stats[message_count] 1 except (json.JSONDecodeError, OSError): continue return stats def format_tokens(count): if count 1_000_000: return f{count / 1_000_000:.1f}M if count 1_000: return f{count / 1_000:.0f}K return str(count) class ClaudeUsageApp(rumps.App): def __init__(self): super().__init__(ClaudeUsage, titleC ...) self.menu [ rumps.MenuItem(正在读取用量请稍候...), None, rumps.MenuItem(立即刷新, callbackself.refresh), rumps.MenuItem(退出, callbackself.quit), ] self.refresh() self.timer rumps.Timer(self.refresh, REFRESH_SECONDS) self.timer.start() def refresh(self, _None): stats collect_usage_stats() total_tokens ( stats[input_tokens] stats[output_tokens] stats[cache_read_tokens] stats[cache_creation_tokens] ) self.title fC {format_tokens(total_tokens)} self.menu.clear() self.menu.add(rumps.MenuItem(f输入 tokens{format_tokens(stats[input_tokens])})) self.menu.add(rumps.MenuItem(f输出 tokens{format_tokens(stats[output_tokens])})) self.menu.add(rumps.MenuItem(f缓存读取{format_tokens(stats[cache_read_tokens])})) self.menu.add(rumps.MenuItem(f缓存写入{format_tokens(stats[cache_creation_tokens])})) self.menu.add(rumps.MenuItem(f消息条数{stats[message_count]})) self.menu.add(None) self.menu.add(rumps.MenuItem(立即刷新, callbackself.refresh)) self.menu.add(rumps.MenuItem(退出, callbackself.quit)) if __name__ __main__: ClaudeUsageApp().run()这段代码的核心逻辑并不复杂但已经覆盖了从数据采集到菜单栏展示的完整链路。如果你使用的是较新版本的 rumps部分内部 API 可能会略有调整建议以官方文档中的类方法为准。6.2 配置为开机启动如果希望菜单栏工具在系统启动后自动运行可以把脚本配置为一个 LaunchAgent。在~/Library/LaunchAgents/下创建一个 plist 文件?xml version1.0 encodingUTF-8? !DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd plist version1.0 dict keyLabel/key stringcom.example.claudeusage/string keyProgramArguments/key array string/Users/yourname/claude-usage-menu/venv/bin/python/string string/Users/yourname/claude-usage-menu/claude_usage_menu.py/string /array keyRunAtLoad/key true/ keyKeepAlive/key true/ /dict /plist注意把/Users/yourname/替换成你的真实用户路径。使用虚拟环境中的 Python 可以避免依赖系统 Python 的包环境。然后执行加载命令launchctl load ~/Library/LaunchAgents/com.example.claudeusage.plist在新版 macOS 中系统可能会提示load已废弃推荐使用launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.claudeusage.plist两种命令都可以让 LaunchAgent 生效你可以根据自己系统的提示选择合适的方式。6.3 结果说明运行脚本并让 Claude Code 继续正常工作一段时间后你会看到菜单栏数字会随着会话量上升而变化。举例说明我的展示方式如果菜单栏显示C 0说明本地没有找到可用的 usage 日志或者所有日志中的 usage 字段都为空。如果菜单栏显示C 1.2M说明本地累计消耗约 120 万 token。点击菜单栏后可以看到输入、输出、缓存读取、缓存写入四项的详细拆分。这个数字只是一个相对参考目的是让你形成“用量直觉”。当数字快速上涨时你至少会意识到当前会话消耗明显偏大。7. 常见问题与排查7.1 菜单栏不显示内容问题现象常见原因解决思路菜单栏没有任何图标或文字Python 环境缺少 rumpspip install rumps后重新运行终端直接退出脚本语法错误检查 Python 版本和代码缩进消息显示权限失败macOS 辅助功能或通知权限未授予在系统设置中检查终端/脚本的权限如果菜单栏上没有出现内容先确认终端里是否打印了异常堆栈。最常见的异常是ModuleNotFoundError: No module named rumps说明当前 Python 环境没有安装 rumps。记得先激活虚拟环境再运行脚本。7.2 读取不到会话日志问题现象常见原因解决思路显示 C 0本地没有~/.claude/projects目录先正常使用一次 Claude Code显示 C 0日志路径发生改变在终端执行ls -lh ~/.claude/projects/确认显示 C 0日志文件为空新开一个会话再观察解决这个问题的第一步是先确认日志目录是否存在。如果 Claude Code 版本更新后路径有变化需要同步调整CLAUDE_DIR常量。7.3 usage 统计为 0问题现象常见原因解决思路统计结果始终为 0JSONL 中的字段结构不兼容手动查看一行日志确认 usage 位置统计结果始终为 0日志文件编码异常使用 Python 脚本逐行调试统计结果偏小只统计了输入输出没有统计缓存确认解析逻辑包含 cache 字段可以手动查看一条日志来判断结构head -5 ~/.claude/projects/*.jsonl如果日志中的 usage 字段嵌套层级和示例不同需要调整collect_usage_stats中的取值逻辑。7.4 请求被限流与 529 问题使用 Claude Code 时如果用量达到限制终端里可能会返回类似529或配额相关提示。这类错误通常不是本地文件导致的而是账户配额或服务端限流导致。如果菜单栏显示用量并不高但 Claude Code 依然报错说明本机日志只反映了部分会话或配额策略涉及跨设备账户维度。此时应优先查看官方账户页面的用量说明而不是继续依赖本地统计。8. 最佳实践与工程建议8.1 数据统计单位与显示策略菜单栏空间很有限不要堆砌完整数字。建议遵循以下原则只显示一个总览值例如C 1.8M。把详细分类放到下拉菜单中。设置合理的刷新间隔建议 30 到 120 秒。在版本更新时留意 Claude Code 是否会改变日志路径或字段结构。小工具的核心是“一眼可知”不是“信息大全”。把最关键的判断依据放到最显眼的位置其余细节收起来。8.2 隐私与安全Claude 会话日志中通常包含项目路径、代码上下文、文件内容等信息属于敏感数据。在设计工具时要注意几点不要在工具中上传会话日志到任何第三方服务。不要图方便把日志文件提交到公开仓库。本地处理即可避免引入不必要的网络请求。如果需要多人使用或发布到 GitHub建议把日志目录路径做成配置项避免硬编码个人目录。不要为了追求“好看”而把整个会话内容展示在菜单栏里这既没有必要也容易造成信息泄露。8.3 从脚本到正式应用的演进建议如果只是自己使用上面的脚本已经足够。但如果你希望把它打磨成一个更完善的小工具可以考虑以下方向方向说明图标化在菜单栏中显示一个小图标用量接近阈值时图标变色阈值提醒用量超过一定数值时弹出系统通知跨设备统计通过自建的日志同步服务汇总多台设备的用量配置化把日志目录、刷新间隔、显示格式放到配置文件中原生 Swift 重写去掉 Python 依赖打包成独立 app其中“阈值提醒”是最值得优先实现的功能当本地累计 token 超过你设定的警戒线时用rumps.notification弹一个提示这样你不需要一直盯着菜单栏也能在关键时刻收到提醒。9. 结语与可继续优化的方向本文从 Claude Code 的用量管理痛点出发带大家完整实现了一个菜单栏用量小工具。核心知识点可以归纳为几条Claude Code 的会话数据会以 JSONL 形式留在本地usage 字段包含输入、输出和缓存三类 tokenPython 的 rumps 库可以快速创建菜单栏应用LaunchAgent 可以让脚本开机自启。如果你正在高频使用 Claude Code建议先把这个小工具跑起来观察一两天的数据变化你会对自己的真实消耗速度有一个更具体的感知。在此基础上再决定是否需要做阈值提醒、跨设备统计或原生应用封装。下一步值得探索的方向是了解 Claude 官方接口中的 usage 字段在不同模型下的差异以及如何在更长的时间维度上做用量趋势可视化。把“能看到今天的用量”升级为“能预测未来几小时会不会超限”才是这类小工具真正的进阶价值。
返回列表