1. 项目概述:一个为AI编程助手打造的用量监控器
如果你和我一样,日常开发重度依赖像Claude Code、Cursor这类AI编程助手,那你肯定也经历过那种“额度焦虑”——不知道今天还剩多少额度,生怕在关键时刻突然被限流。每次都要打开终端敲命令,或者去网页后台翻找,既打断了思路,也相当麻烦。MeterBar这个开源项目,就是为了解决这个痛点而生的。它是一个纯粹的macOS菜单栏应用,核心目标就一个:把你所有AI编程助手的用量信息,集中、实时地展示在屏幕右上角,让你一目了然。
简单来说,它就像一个为AI编程工具量身定做的“流量监控仪表盘”。目前,它原生支持三大主流工具:Anthropic的Claude Code、OpenAI的Codex CLI(也就是ChatGPT的官方命令行工具),以及风头正劲的Cursor IDE。最让我欣赏的设计是它的“零配置”理念。它不会要求你输入任何新的API密钥,而是巧妙地读取这些工具官方CLI登录后在你本地生成的认证文件(比如OAuth令牌)。这意味着,只要你已经用claude login或codex login登录过,MeterBar就能自动获取权限,安全地帮你拉取用量数据。整个应用采用SwiftUI构建,体积轻巧,运行在macOS的App Sandbox沙盒环境中,隐私和安全有保障。
2. 核心设计思路与架构解析
2.1 解决的核心痛点与设计哲学
在深入代码之前,理解MeterBar要解决什么问题至关重要。现代AI编程助手通常采用配额制,比如Claude Code的“5小时会话时长+7天总时长”,或者Cursor的月度使用限制。开发者面临几个麻烦:第一,信息分散。每个工具都有自己的查询方式,没有统一入口。第二,查询过程繁琐。要么是命令行,要么是网页,无法实现“瞟一眼就知道”的便捷。第三,缺乏预警。往往是用超了才发现,影响工作流。
MeterBar的设计哲学非常明确:无感监控与极简呈现。它将自己定位为一个纯粹的“观察者”和“展示器”,而非“控制器”。它不管理你的账户,不存储你的密钥,只做两件事:1. 从可信的本地源(官方CLI的缓存)安全读取认证信息;2. 定期向官方API请求用量数据并格式化展示。这种设计最大限度地减少了安全风险,也降低了用户的使用门槛——你只需要像平常一样登录一次CLI,剩下的就交给MeterBar。
2.2 技术栈选型背后的考量
项目选择了苹果生态最新的技术栈,这并非盲目追新,而是有着充分的实践理由:
- SwiftUI + Combine:这是构建现代macOS菜单栏应用的黄金组合。SwiftUI的声明式语法让构建复杂的、状态驱动的UI(如可折叠的卡片、动态进度条)变得异常清晰。Combine框架则完美处理了异步数据流,例如定时刷新、多个API请求的并发与合并。当从三个不同的服务获取数据时,Combine的
Publishers.Merge或Publishers.Zip可以优雅地处理这些异步操作,并将结果统一推送给UI更新。 - macOS App Sandbox:对于需要访问特定用户目录(如
~/.claude.ai/)的应用,沙盒机制是双刃剑。MeterBar通过声明精确的“文件访问”权限(com.apple.security.files.user-selected.read-only和com.apple.security.files.bookmarks.app-scope),在保障系统安全的前提下,获得了读取这些特定路径的资格。这是开发类似工具时必须仔细处理的一环,否则应用在发布后根本无法运行。 - 原生网络与存储:使用
URLSession进行网络请求,使用Core Data或简单的UserDefaults进行本地缓存(如上次刷新时间、窗口状态),确保了应用的性能和稳定性,避免了引入第三方库可能带来的依赖冲突和包体积膨胀。
2.3 数据流与模块化架构
从代码结构看,MeterBar采用了清晰的MV(Model-View)模式,数据流是单向的:
数据层(Model):这是应用的核心。为每个支持的服务(Claude、Codex、Cursor)定义了独立的
Provider协议和实现类。每个Provider负责三件事:- 凭证发现:在预定义的路径(如
~/.codex/auth.json)查找并解析令牌。 - API交互:构造带有认证头的HTTP请求,调用对应的用量查询接口。
- 数据解析:将API返回的JSON映射为统一的内部
UsageData模型,包含用量、限额、重置时间等。
- 凭证发现:在预定义的路径(如
视图层(View):由SwiftUI组件构成。主视图是一个
MenuBarExtra,点击后弹出包含多个ServiceCardView的窗口。每个卡片视图订阅对应的Provider提供的数据,并根据用量百分比动态计算进度条宽度和颜色(绿/黄/红)。协调层(ViewModel/Manager):一个中央的
UsageMonitor类(或类似角色)使用Combine定时器,每隔15分钟触发一次所有Provider的数据获取任务,并将结果聚合后分发给各个视图。这里还处理了错误状态(如“未配置”、“网络错误”),并提供了手动刷新按钮的触发逻辑。
这种模块化设计的好处是扩展性极强。如果要新增对另一个AI服务(比如GitHub Copilot)的支持,开发者基本上只需要做两件事:实现一个新的Provider,以及在UI列表中添加一个对应的卡片视图。核心的监控逻辑和UI框架完全复用。
3. 核心功能深度解析与实操要点
3.1 多服务凭证的自动发现机制
这是MeterBar“零配置”魔法的关键。我们来看看它是如何在不询问用户密码的情况下,获得访问权限的。
Claude Code:当你运行claude login时,Anthropic的CLI工具会在~/.claude.ai/目录下创建一个包含OAuth刷新令牌的文件。MeterBar的Claude Provider会尝试读取这个目录。它并不是直接读取原始令牌文件,更安全的方式是模拟CLI的行为,使用系统钥匙串或该工具提供的特定令牌管理方式。在实际代码中,可能需要解析config.json或调用CLI的某个内部命令来获取当前有效的访问令牌。
Codex CLI:OpenAI的Codex CLI将认证信息存储在~/.codex/auth.json中。这个JSON文件结构相对清晰,通常包含access_token、refresh_token和expiry等字段。MeterBar的Codex Provider读取这个文件,提取access_token,并将其作为Bearer Token添加到请求头中,用于调用OpenAI的用量查询接口。
Cursor:Cursor的情况比较特殊,因为它是一个桌面IDE,数据存储在本地SQLite数据库中。路径通常在~/Library/Application Support/Cursor/下。MeterBar需要获得沙盒权限来读取这个目录,并执行SQL查询来获取月度使用数据。这里需要注意数据库 schema 可能随Cursor版本更新而变化,因此代码中需要做一定的版本兼容性处理或提供明确的错误提示。
注意:这种自动发现机制依赖于这些工具CLI的默认行为和数据存储路径。如果未来某个工具改变了其认证文件的存储位置或格式,MeterBar可能需要相应更新。这也是开源项目的一个优势,社区可以快速响应这类变化。
3.2 实时监控与状态可视化
MeterBar的UI设计充分考虑了信息密度和可读性:
- 菜单栏图标:通常显示一个聚合状态,比如如果任一服务用量超过80%,图标可能显示为橙色或红色圆点,提供最高级别的警报。
- 可折叠卡片:这是UI的精华。默认折叠状态只显示服务名称和一个细长的水平进度条,颜色代表状态。这让你在0.5秒内就能评估所有服务的“健康度”。点击卡片头部,展开查看详情:具体的使用量/限额数字、精确的百分比、配额重置的倒计时(例如“3天12小时后重置”),以及订阅类型(如“Team”、“Pro”)。
- 颜色编码系统:这是一个非常直观的反馈机制。
- 绿色(<50%):放心使用。
- 黄色(50%-80%):需要开始留意,考虑优化使用方式,比如对非关键性的代码生成可以降低使用频率。
- 红色(>80%):危险区域,应避免进行大型或复杂的生成任务,优先使用本地智能补全或其他工具。
实操心得:这个颜色阈值是可以根据个人偏好调整的。比如,如果你对某个服务依赖度极高,可能希望把黄色预警线提前到40%。在MeterBar的代码中,这些阈值很可能被定义为常量,你可以通过修改源码并自行编译来定制。
3.3 内置CLI工具的应用场景
除了图形界面,MeterBar还附带了一个命令行工具,这个设计非常贴心,拓宽了它的使用场景:
- 自动化脚本集成:你可以写一个Shell脚本,每天上午通过
meterbar usage --json获取用量数据,如果某个服务超过70%,就自动发送一个通知到Slack或钉钉。 - 无头服务器监控:如果你在远程服务器(也是macOS)上使用Codex CLI进行一些自动化任务,你可以通过CLI工具定期检查额度,防止任务中途失败。
- 快速查询:对于习惯终端的开发者,直接敲命令比用鼠标点开菜单栏更快。
- 成本追踪:
meterbar cost命令可以统计近期的token消耗,对于需要向公司报销或控制预算的团队非常有用。
CLI工具的实现,通常是主应用的一个Helper,或者共享了核心的数据获取模块。通过Process或XPC与主应用通信,获取数据后再格式化输出到标准输出。
4. 从零到一的构建与部署实践
4.1 开发环境搭建与项目初始化
假设你是一名macOS开发者,想基于MeterBar的思路为自己监控的工具定制一个类似应用,或者想为MeterBar贡献代码,以下是详细的起步指南:
首先,确保你的环境符合要求:
# 检查系统版本 sw_vers -productVersion # 确保 >= 13.0 (Ventura) # 检查Xcode命令行工具 xcode-select -p # 如果没有,安装:xcode-select --install # 克隆项目代码 git clone https://github.com/shipshitdev/meterbar.app.git cd meterbar.app打开项目最直接的方式是使用Xcode:
open MeterBar.xcodeproj或者,如果你更喜欢纯命令行工作流,也可以用xcodebuild:
# 解析项目依赖(如果使用了Swift Package Manager) xcodebuild -resolvePackageDependencies # 构建项目 xcodebuild -project MeterBar.xcodeproj -scheme MeterBar -configuration Release build项目成功打开后,花点时间浏览一下主要目录结构:
MeterBar/:主应用代码,包含App入口、视图、ViewModel。MeterBarCore/或Shared/:核心数据模型、网络管理器、Provider协议和实现。这里是业务的灵魂。MeterBarCLI/:命令行工具靶子(Target)。Resources/:图标、资产文件。MeterBar.entitlements:沙盒权限配置文件,务必仔细检查,它决定了应用能访问哪些文件。
4.2 关键代码模块剖析与定制
让我们深入两个最关键的模块,看看如何工作以及如何修改。
1. Provider协议的实现示例(以Codex为例):
在Swift中,Provider通常会定义一个协议,确保所有服务提供者都有相同的行为接口。
protocol UsageProvider { var serviceName: String { get } var isConfigured: Bool { get } func fetchUsage() async throws -> UsageData } struct UsageData { let used: Int let limit: Int let resetDate: Date? let subscriptionTier: String }CodexProvider会实现这个协议。它的fetchUsage方法大致会做以下几件事:
class CodexProvider: UsageProvider { private let authFileURL = FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent(".codex") .appendingPathComponent("auth.json") var isConfigured: Bool { // 检查认证文件是否存在且可读 FileManager.default.fileExists(atPath: authFileURL.path) } func fetchUsage() async throws -> UsageData { guard let authData = try? Data(contentsOf: authFileURL), let authJson = try? JSONSerialization.jsonObject(with: authData) as? [String: Any], let accessToken = authJson["access_token"] as? String else { throw ProviderError.notConfigured } var request = URLRequest(url: URL(string: "https://chatgpt.com/backend-api/wham/usage")!) request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") let (data, _) = try await URLSession.shared.data(for: request) let apiResponse = try JSONDecoder().decode(CodexAPIResponse.self, from: data) // 将API响应映射为统一的UsageData return UsageData( used: apiResponse.current_usage, limit: apiResponse.usage_limit, resetDate: Date(timeIntervalSince1970: TimeInterval(apiResponse.reset_date)), subscriptionTier: apiResponse.plan_name ) } }2. 主监控循环与状态管理:
在SwiftUI中,通常会用一个ObservableObject(例如UsageMonitor)来管理所有Provider的状态。
@MainActor class UsageMonitor: ObservableObject { @Published var providers: [any UsageProvider] = [] @Published var lastUpdated: Date? @Published var isLoading = false private var timer: Timer? init() { self.providers = [ClaudeProvider(), CodexProvider(), CursorProvider()] startMonitoring() } func startMonitoring(interval: TimeInterval = 900) { // 默认15分钟 timer?.invalidate() fetchAllUsage() // 立即获取一次 timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in self?.fetchAllUsage() } } func fetchAllUsage() { Task { isLoading = true // 并发获取所有Provider的数据 await withTaskGroup(of: Void.self) { group in for provider in providers where provider.isConfigured { group.addTask { do { let usage = try await provider.fetchUsage() // 更新对应Provider的@Published状态,触发UI更新 await self.updateUsage(for: provider, data: usage) } catch { // 处理错误,例如更新为错误状态 await self.handleError(for: provider, error: error) } } } } lastUpdated = Date() isLoading = false } } }定制建议:如果你想添加对新服务的支持,比如GitHub Copilot,你需要:1. 研究Copilot CLI或API如何获取用量(可能需要gh copilot status命令或调用GitHub API)。2. 创建一个新的CopilotProvider类实现UsageProvider协议。3. 将这个Provider实例添加到UsageMonitor的providers数组中。
4.3 打包、签名与分发
开发完成后,你需要将应用分发给用户。对于个人使用或小范围分享,直接归档(Archive)导出即可。但对于正式分发,尤其是通过Homebrew Cask,有几个关键步骤:
代码签名与公证:这是macOS应用分发的必经之路。你需要苹果开发者账号,在Xcode的“Signing & Capabilities”中配置Team和Bundle Identifier。构建Release版本后,通过Xcode的“Distribute App”流程,将应用上传到App Store Connect进行公证(Notarization)。公证成功后,你会得到一个票据(ticket),它会被附加到应用上,让Gatekeeper放行。
创建Homebrew Cask:这是MeterBar推荐的安装方式。Homebrew Cask本质上是一个描述如何下载和安装应用的Ruby公式。你需要:
- 将公证后的
.app文件打包成.zip或.dmg。 - 将安装包上传到一个稳定的托管服务(如GitHub Releases)。
- 在你的Homebrew Tap仓库中创建一个Cask文件(如
meterbar.rb),其中指定下载URL、校验和(sha256)、应用名称等信息。
cask "meterbar" do version "1.0.0" sha256 "abc123..." # 替换为实际zip文件的sha256 url "https://github.com/shipshitdev/meterbar.app/releases/download/v#{version}/MeterBar.zip" name "MeterBar" desc "Monitor AI coding assistant usage from the menu bar" homepage "https://github.com/shipshitdev/meterbar.app" app "MeterBar.app" end用户就可以通过
brew install --cask shipshitdev/tap/meterbar来安装了。- 将公证后的
处理沙盒权限:这是最容易出错的地方。如果你的应用需要访问像
~/Library/Application Support/Cursor/这样的特定路径,必须在entitlements文件中声明。即使如此,对于从网上下载的非App Store应用,用户首次运行时可能仍会遇到权限阻拦。这就是为什么项目README中提到了xattr -cr命令,它可以清除某些可能引起Gatekeeper警告的扩展属性。更规范的做法是确保应用经过苹果公证。
5. 常见问题排查与实战经验分享
在实际使用和开发类似工具的过程中,你肯定会遇到一些坑。以下是我总结的一些典型问题及其解决方案。
5.1 服务状态显示“未配置”或“无法读取”
这是最常见的问题,根本原因在于应用找不到或无法解析认证文件。
检查步骤:
- 确认CLI已登录:打开终端,运行
claude whoami或codex whoami(如果该CLI支持),确认你处于登录状态。 - 检查文件路径与权限:在终端中使用
ls -la ~/.claude.ai/或cat ~/.codex/auth.json查看文件是否存在、内容是否正常。确保当前用户有读取权限。 - 检查沙盒权限:如果是从源码编译运行,确保
MeterBar.entitlements文件包含了必要的文件访问权限。如果是从网上下载的预编译版本,尝试按照提示在首次运行时右键点击“打开”,或在终端执行xattr -cr /Applications/MeterBar.app。 - 查看应用日志:在macOS的“控制台”App中,筛选进程名为“MeterBar”的日志,里面通常会有更详细的错误信息,比如“文件读取错误”或“JSON解析失败”。
- 确认CLI已登录:打开终端,运行
一个典型陷阱:某些CLI工具(如早期版本的Codex)可能会将令牌存储在macOS的钥匙串中,而不是明文文件中。这时,你的Provider需要调用Security框架的API(如
SecItemCopyMatching)来从钥匙串中读取,这需要不同的entitlement(keychain-access-groups),并且处理起来更复杂。
5.2 数据刷新不及时或显示异常
- 手动刷新无效:首先点击菜单栏的刷新按钮。如果无效,检查网络连接。这些用量API通常需要稳定的网络环境。
- 自动刷新失败:MeterBar的定时器可能在系统休眠或应用进入后台时被暂停。一个更健壮的实现是使用
Timer.publish(every:on:in:)配合.autoconnect(),并监听应用生命周期事件(scenePhase),在应用回到前台时强制刷新一次。 - 数据显示为0或NaN:这通常是API响应格式发生变化导致的。每个AI服务的API都不是完全稳定的。你需要:
- 使用像
curl或Postman的工具,手动模拟MeterBar的请求,查看原始API返回了什么。
# 例如,获取Codex的令牌后手动调用(令牌已脱敏) curl -H "Authorization: Bearer sk-xxx" https://chatgpt.com/backend-api/wham/usage- 对比返回的JSON结构和Provider代码中的
Decodable模型(如CodexAPIResponse)是否匹配。很可能多了一层嵌套,或者字段名变了。 - 更新你的数据模型,并考虑在代码中添加更灵活的解析逻辑或版本检测。
- 使用像
5.3 扩展支持其他AI服务
如果你想为MeterBar添加对另一个工具(如GitHub Copilot)的支持,可以遵循以下路径:
调研阶段:
- 目标工具是否有官方CLI?(
gh copilot) - CLI如何认证?(
gh auth login) - 认证信息存储在哪里?(可能是
~/.config/gh/hosts.yml或GitHub CLI的缓存) - 如何查询用量?(
gh copilot status命令的输出,或者是否存在一个未公开的REST API?)
- 目标工具是否有官方CLI?(
实现阶段:
- 在项目中创建
CopilotProvider.swift。 - 实现
UsageProvider协议。在fetchUsage()方法中,你可能需要:- 解析
gh的配置文件获取令牌。 - 或者,更简单但脆弱的方式:用
Process执行gh copilot status命令,然后解析其标准输出(字符串),提取用量信息。这种方式依赖于命令输出的格式稳定性。
- 解析
- 将新的Provider添加到
UsageMonitor的提供商列表中。 - 设计并添加对应的
CopilotServiceCardView。
- 在项目中创建
测试与贡献:
- 充分测试你的Provider在各种状态下的表现(未登录、已登录、额度用尽等)。
- 如果项目活跃,可以向原仓库提交Pull Request。记得编写清晰的文档说明你的新增功能。
5.4 性能与资源占用优化
作为一个常驻菜单栏的应用,资源占用必须极低。
- 网络请求优化:不要同时发起所有API请求,可以使用
TaskGroup进行有限的并发控制(如最多同时2个),并为每个请求设置合理的超时时间(如10秒)。 - 数据缓存:在
UserDefaults或一个小型的Core Data/SQLite数据库中缓存上一次成功获取的数据。这样即使网络暂时不可用,UI也能显示最近的数据,而不是一片空白。定时刷新时,可以先显示缓存数据,然后静默更新。 - CPU/内存:使用Instruments工具监控应用在后台定时唤醒和刷新时的CPU和内存峰值。确保没有内存泄漏(Combine的订阅在适当时候取消)。菜单栏应用的内存占用最好能长期稳定在50MB以下。
开发这样一款工具,最深的体会是“细节决定体验”。一个准确的进度条颜色、一个清晰的重置倒计时、一个安静稳定的后台刷新机制,这些看似微小的点,共同构成了用户“无感”却又离不开的体验。它不应该是一个需要你经常操心、调试的东西,而应该像呼吸一样自然存在,只在需要时给你关键信息。MeterBar在这个方向上做了一个非常棒的示范,它的开源代码也为想要进入macOS实用工具开发领域的开发者提供了绝佳的学习范本。