TokenBar:开源macOS菜单栏工具,实时监控AI编程成本与Token消耗
2026/5/15 15:36:49 网站建设 项目流程

1. 项目概述与核心价值

作为一名长期在Mac上折腾各种AI编程工具的开发者,我发现自己经常陷入一个困境:手头同时开着Cursor、GitHub Copilot,浏览器里还挂着ChatGPT Plus,偶尔还要调一下本地的Ollama模型。一个月下来,看着各家发来的账单邮件,心里总是一笔糊涂账——到底哪个工具用得最狠?哪个性价比最高?我的月度预算到底花在了哪里?这种“成本黑盒”的状态让我很不踏实。直到我遇到了TokenBar,这个开源、轻量、常驻菜单栏的小工具,才真正把AI编程的成本管理从“盲人摸象”变成了“数据驱动”。

简单来说,TokenBar是一个专为macOS设计的菜单栏应用,它的核心使命就是让你实时、一目了然地掌握所有AI编程工具的花销。它不是一个简单的记账软件,而是一个深度集成到开发者工作流的监控中心。它通过自动扫描你的系统,发现已安装的AI工具(如Cursor、Copilot),并连接到它们的API,实时拉取你的使用量、Token消耗和费用数据,最终以一个简洁的百分比或数字展示在菜单栏上。点击图标,你就能看到一个清晰的仪表盘,告诉你每个工具本月已使用的预算比例、具体的Token数或花费金额。

这个工具特别适合像我这样,同时使用多个AI服务的独立开发者、小团队负责人,或者任何对云服务成本敏感的技术人员。它解决了几个关键痛点:成本可视化(告别账单惊吓)、工具对比(帮你找到最高效的“副驾驶”)、以及预算控制(设置预警,防止超支)。更重要的是,它完全开源、本地运行,你的API密钥通过macOS钥匙串安全存储,数据不出本地,隐私和安全有保障。接下来,我将从设计思路、实操配置、核心实现到避坑经验,为你完整拆解这个精致而实用的工具。

2. 整体设计与架构思路拆解

在动手使用或参与贡献之前,理解TokenBar的设计哲学至关重要。它没有选择做一个功能庞杂的“全家桶”,而是极其克制地聚焦于单一痛点,并为此设计了一套优雅、可扩展的架构。

2.1 为什么是菜单栏应用?

这是TokenBar的第一个关键设计决策。作为一个成本监控工具,其核心价值在于“随时可看,无需打扰”。如果做成一个独立的Dock应用,你需要主动点击打开才能查看,监控行为就变成了一个“任务”,很容易被遗忘。而菜单栏应用是“常驻后台,信息前置”的典范。状态始终在视野边缘(屏幕右上角),只需眼角一瞥就能获取关键信息(比如“预算已用65%”),点击一下就能展开详情。这种零交互成本的设计,完美契合了监控类工具的需求。它借鉴了iStat Menus、Bartender这类系统监控工具的思路,将专业能力以最无感的方式融入用户环境。

2.2 核心架构:提供者(Provider)模式

TokenBar能支持众多AI服务,其奥秘在于它采用了一种高度模块化的“提供者”(Provider)模式。你可以把它想象成一个主板,而每一个AI服务(如Cursor、OpenAI)都是一块可以即插即用的扩展卡。

整个应用的核心是ProviderManager(提供者管理器)。它扮演着“总控中心”的角色,负责:

  1. 注册与发现:维护一个所有支持的服务列表(ProviderRegistry.all)。
  2. 生命周期管理:启动时自动扫描系统,根据规则判断哪些服务已安装,并初始化对应的提供者实例。
  3. 任务调度:以可配置的时间间隔(例如每5分钟)轮询那些需要API连接的提供者,获取最新数据。
  4. 数据聚合与展示:将从各个提供者收集到的数据(使用量、花费)汇总,并驱动菜单栏图标和弹出视图的更新。

而具体的服务对接逻辑,则被封装在一个个独立的Provider中。每个Provider只需要关心两件事:

  1. 如何检测我是否存在?(Detection):通过检查特定的应用程序路径(如/Applications/Cursor.app)、命令行工具是否存在(如ollama命令)、或VS Code/Cursor的扩展目录,来判断用户是否安装了该服务。
  2. 如何获取我的使用数据?(Tracking,如果支持):实现一个UsageProvider协议,定义如何调用该服务的API(需要API Key),解析返回的JSON数据,并转换成TokenBar内部统一的UsageData模型(包含已用量、总量、单位等)。

这种架构带来了巨大的优势:

  • 可扩展性:添加一个新服务(比如新出的某个AI编码工具),只需要实现一个新的Provider类并注册到列表中即可,完全不影响其他部分。
  • 清晰的责任边界:代码结构清晰,易于维护和测试。
  • 灵活的部署:对于像Ollama这种纯本地、无计费API的服务,可以只实现“检测”功能,让用户知道它在运行;对于OpenAI这种有明确计费API的,则实现完整的“追踪”功能。

2.3 技术选型:纯Swift/SwiftUI与“零依赖”

项目采用纯Swift和SwiftUI构建,并自豪地宣称“Zero dependencies”。这是一个非常值得品味的选型。

为什么不用Electron或跨平台框架?像许多菜单栏工具一样,TokenBar完全可以选用Electron来开发,这样可以方便地使用Web技术栈并实现跨平台。但作者选择了原生路线。原因在于:

  1. 性能与资源占用:一个菜单栏工具应该像隐形一样轻量。Electron应用通常有较高的内存和CPU基础开销。而原生Swift编译的应用,启动速度极快,内存占用极小(TokenBar通常在20MB以下),这对于一个需要7x24小时常驻后台的工具来说是至关重要的用户体验。
  2. 原生集成体验:SwiftUI可以完美地实现macOS系统设置(System Settings)风格的侧边栏布局,动画流畅,符合平台设计规范。与钥匙串(Keychain)、用户默认设置(UserDefaults)等系统服务的集成也更为直接和稳定。
  3. 独立性与可靠性:不依赖Node.js运行时或复杂的包管理,最终产物就是一个独立的.app文件,分发和安装都极其简单,也减少了因依赖项版本冲突导致问题的可能性。

这个选择体现了开发者的“工匠精神”——用最合适的工具,为特定平台(macOS)打造最佳体验。虽然牺牲了跨平台能力,但换来了极致的高效和优雅。

3. 详细配置与核心功能实操

了解了设计思路,我们来看看如何把它用起来。TokenBar的配置过程充分体现了“开发者友好”的设计理念。

3.1 安装与首次运行

官方推荐从源码编译安装,这能确保你获得最新版本,并且过程非常简单。

# 1. 克隆仓库 git clone https://github.com/saphid/TokenBar.git cd TokenBar # 2. 编译并安装到应用程序文件夹 make install

执行make install后,一个名为TokenBar.app的应用就会被复制到你的/Applications目录下。首次打开时,macOS可能会提示“无法验证开发者”,你需要进入系统设置 -> 隐私与安全性,点击“仍要打开”来运行它。

注意:确保你的系统已安装Xcode命令行工具(xcode-select --install)和符合要求的Swift版本(5.10+)。make install命令背后其实执行了swift build -c release和一系列打包操作,如果失败,可以尝试手动执行这些步骤来排查问题。

首次启动后,你会立刻在菜单栏的右上角看到TokenBar的图标(可能显示为“--%”或一个默认图标)。同时,它的核心魔法——“自动检测”——已经开始运行了。

3.2 自动检测与提供者管理

TokenBar会在后台静默扫描你的系统。根据项目文档,它的检测逻辑非常细致:

  • 对于桌面应用:检查/Applications~/Applications目录下是否存在特定应用(如Cursor.app)。
  • 对于命令行工具:尝试在终端执行特定命令(如ollama --version),根据返回结果判断。
  • 对于编辑器扩展:扫描VS Code、Cursor、Windsurf等编辑器的扩展安装目录。

扫描完成后,点击菜单栏图标,选择Settings(或按Cmd + ,快捷键),你就会进入设置界面。这里采用了类似macOS系统设置的侧边栏布局,左侧是功能分类,右侧是详细设置。

“Providers”标签页下,你会看到两个列表:

  1. Usage Tracking:这里列出的是已检测到、且支持通过API获取详细用量的服务。例如,如果你的机器上安装了Cursor,它就会出现在这里,但状态可能是“未配置”,因为它需要你的API密钥来获取数据。
  2. Auto-Detection:这里列出的是仅被检测到存在的服务,如Ollama、Aider等。它们没有(或不需要)详细的用量追踪,但TokenBar让你知道它们正在被使用。

这个自动检测功能极大地简化了初始化配置。你不需要手动去添加几十个可能用到的服务,工具已经帮你筛选好了。

3.3 配置API密钥与预算

对于支持用量追踪的服务,你需要配置API密钥才能获取数据。这是最关键的一步。

以配置OpenAI为例:

  1. 在Settings的Providers列表中找到“OpenAI / ChatGPT”,点击它。
  2. 在右侧详情面板中,你会找到输入API密钥的字段。
  3. 前往 OpenAI平台 ,创建一个新的API密钥(建议权限最小化,仅保留“读取”用量权限的密钥)。
  4. 将密钥粘贴到TokenBar中。

这里有一个至关重要的安全设计:TokenBar使用macOS钥匙串(Keychain)来存储你的API密钥。当你点击“Save”保存时,密钥并没有被明文保存在应用的配置文件或数据库里,而是被安全地加密存储在了系统的钥匙串中。这意味着:

  • 即使有人拿到了你的电脑上的TokenBar配置文件,他们也拿不到你的API密钥。
  • 密钥的存取由操作系统级别的安全机制保障。
  • 这符合安全开发的最佳实践,也是我强烈推荐TokenBar的原因之一。

配置好API密钥后,TokenBar会立即开始第一次轮询。稍等片刻,菜单栏上的数字就会更新,显示该服务本月已使用的预算百分比。

设置月度预算: 在提供者的设置详情里,你还可以设置“Monthly Budget”。例如,为OpenAI设置50美元。此后,TokenBar从API获取到你的总消费额(比如15美元)后,会自动计算出使用比例(30%),并显示在菜单栏。这个功能对于成本控制非常直观有效。

3.4 界面解读与自定义

配置完成后,你的菜单栏就变成了一个AI成本仪表盘。

  • 菜单栏图标显示:默认显示当前“最贵”或使用比例最高的那个服务的百分比。你也可以在Settings的“Display”标签页中自定义显示内容,比如改为显示总花费、或指定显示某个特定服务的数据。
  • 点击弹出菜单:这里会列出所有已配置的追踪型服务,显示每个服务的名称、本月用量/预算、以及一个进度条。一目了然。
  • 数据更新频率:在Settings的“General”里,可以调整轮询API的频率。默认可能是15分钟一次。出于对API调用限额的尊重以及避免过度请求,不建议设置得过短(如低于5分钟)。对于个人使用,15-30分钟的间隔完全足够。

整个设置过程流畅直观,几乎不需要查阅文档。这正是优秀工具的标志:将复杂的能力隐藏在简单的界面之后。

4. 核心实现细节与扩展指南

对于想深入了解其工作原理,甚至想为其贡献新提供者的开发者来说,这部分是精髓。我们深入到代码层面,看看一个提供者是如何从无到有被创建和集成的。

4.1 代码结构深度游

让我们回顾并细化一下项目的目录结构:

Sources/TokenBarLib/ ├── Domain/ │ ├── Models/ # 核心数据模型,如 UsageData, ProviderConfig │ ├── Config/ # 应用配置、偏好设置的Schema定义 │ └── Registry/ # ProviderRegistry,所有提供者的注册中心 ├── Providers/ │ ├── Trackable/ # 核心:所有支持API追踪的提供者实现 │ │ ├── CursorProvider.swift │ │ ├── OpenAIProvider.swift │ │ ├── GitHubCopilotProvider.swift │ │ └── ... # 每个提供者一个文件 │ ├── DetectionOnly/ # 仅用于检测存在的提供者 │ │ ├── OllamaProvider.swift │ │ ├── AiderProvider.swift │ │ └── ... │ └── Common/ # 提供者基类、协议定义 │ ├── RegisteredProvider.swift # 所有提供者必须遵守的协议 │ └── UsageProvider.swift # 可追踪提供者必须遵守的协议 └── Views/ ├── Settings/ # 设置窗口的所有SwiftUI视图 └── MenuBar/ # 菜单栏图标和弹出视图

关键协议解析:

  1. RegisteredProvider协议:这是所有提供者(无论是否可追踪)的身份证。它定义了提供者的基本元信息:
    protocol RegisteredProvider { var id: String { get } // 唯一标识符,如 "com.saphid.TokenBar.Provider.OpenAI" var name: String { get } // 显示名称,如 "OpenAI / ChatGPT" var description: String { get } // 简短描述 var isDetected: Bool { get async } // 异步方法,检查该提供者是否存在于系统中 // ... 可能还有图标、配置视图等 }
  2. UsageProvider协议:继承自RegisteredProvider,为可追踪的提供者增加能力。
    protocol UsageProvider: RegisteredProvider { var configuration: ProviderConfiguration { get set } // 存储API Key等配置 func fetchUsage() async throws -> UsageData // 核心方法:调用API并返回用量数据 var pollingInterval: TimeInterval { get } // 建议的轮询间隔 }

一个具体的提供者,比如OpenAIProvider,就是UsageProvider协议的一个实现。它需要:

  • 实现isDetected:可能检查是否安装了ChatGPT桌面端,或者简单地总是返回true(因为通过网页也能使用)。
  • 实现fetchUsage():构造请求,调用OpenAI的用量API(通常是https://api.openai.com/v1/usage),并附带Bearer Token认证,然后解析返回的JSON,计算出本月至今的总花费。
  • ProviderRegistry.all这个静态数组里注册自己,这样ProviderManager启动时就能发现它。

4.2 动手添加一个新的可追踪提供者

假设我们要添加一个对新服务“DeepSeek Coder”的支持。

第一步:创建提供者文件Sources/TokenBarLib/Providers/Trackable/目录下创建DeepSeekCoderProvider.swift

第二步:实现协议

import Foundation struct DeepSeekCoderProvider: UsageProvider { let id = "com.saphid.TokenBar.Provider.DeepSeekCoder" let name = "DeepSeek Coder" let description = "Tracks usage and spending for DeepSeek Coder API" let pollingInterval: TimeInterval = 900 // 15分钟,单位秒 @Published var configuration: ProviderConfiguration init(configuration: ProviderConfiguration) { self.configuration = configuration } // 检测逻辑:检查是否有相关应用或配置存在 var isDetected: Bool { get async { // 示例:检查是否安装了某个已知的客户端,或者检查环境变量 // 如果无法检测,可以默认返回true,让用户手动配置 let fileManager = FileManager.default return fileManager.fileExists(atPath: "/Applications/DeepSeek Coder.app") // 假设路径 } } // 核心:获取用量数据 func fetchUsage() async throws -> UsageData { guard let apiKey = configuration.apiKey, !apiKey.isEmpty else { throw ProviderError.missingAPIKey } // 1. 构造API请求 let url = URL(string: "https://api.deepseek.com/v1/usage")! // 假设的API端点 var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Accept") // 2. 发起网络请求 let (data, response) = try await URLSession.shared.data(for: request) // 3. 检查HTTP状态码 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { // 处理错误,例如API密钥无效、额度不足等 throw ProviderError.networkError(statusCode: (response as? HTTPURLResponse)?.statusCode ?? -1) } // 4. 解析JSON响应 struct DeepSeekResponse: Codable { let totalUsage: Double // 假设返回的是总花费,单位美元 let monthlyLimit: Double? // 假设的月度限额 } let apiResponse = try JSONDecoder().decode(DeepSeekResponse.self, from: data) // 5. 转换为TokenBar内部模型 return UsageData( used: apiResponse.totalUsage, total: configuration.monthlyBudget ?? apiResponse.monthlyLimit ?? 0, // 优先使用用户设置的预算 unit: .dollars, updatedAt: Date() ) } }

第三步:注册提供者找到ProviderRegistry.swift文件,在all这个静态属性数组中添加你的新提供者:

static let all: [any RegisteredProvider.Type] = [ CursorProvider.self, OpenAIProvider.self, GitHubCopilotProvider.self, // ... 其他现有提供者 DeepSeekCoderProvider.self // <- 添加这一行 ]

第四步:提供配置界面(可选但推荐)为了让用户能输入API密钥和预算,你需要在Views/Settings/下创建或扩展现有的SwiftUI视图,为DeepSeekCoderProvider添加一个配置面板。这通常包括一个安全文本输入框用于API Key,和一个数字输入框用于月度预算。

完成以上步骤后,重新编译运行TokenBar (make run),它就能自动检测(如果实现了isDetected)并支持配置和追踪DeepSeek Coder的用量了。

4.3 数据流与状态管理

TokenBar采用SwiftUI和Combine框架进行响应式状态管理,这是现代SwiftUI应用的典型模式。

  1. 数据流ProviderManager作为中枢,持有所有已激活Provider的实例。它通过一个定时器(Timer)定期触发fetchUsage()调用。每个Provider异步地调用其API。
  2. 状态更新:API返回的数据被解析成UsageData模型。这个模型是ObservableObject或使用了@Published属性包装器。当数据更新时,SwiftUI会自动感知到变化。
  3. 视图刷新:菜单栏的视图(MenuBarView)和设置窗口的视图都“观察”(Observe)着这些数据。当底层数据变化时,UI会自动更新,菜单栏上的百分比数字也随之改变。
  4. 持久化:用户的配置(哪些提供者被启用、API密钥的引用、预算设置)通过@AppStorage或自定义的UserDefaults存储。再次强调,API密钥本身存于钥匙串,这里存的只是一个用于查找的标识符。

这套架构保证了应用的响应速度和数据一致性,是构建可靠macOS菜单栏应用的优秀范例。

5. 常见问题、排查技巧与实战心得

在实际使用和开发过程中,你可能会遇到一些问题。下面是我总结的一些常见情况及解决方法。

5.1 使用中的常见问题

问题1:菜单栏图标显示“--%”或没有数据。

  • 可能原因A:API密钥未配置或无效。
    • 排查:点击菜单栏图标 -> Settings,找到对应的Provider,检查API密钥是否已填写且正确。可以尝试去该服务的官网验证密钥是否有效、是否有余额、是否授予了正确的权限(通常需要“只读”用量权限)。
    • 技巧:对于OpenAI,你可以在终端用curl命令快速测试:curl https://api.openai.com/v1/usage -H "Authorization: Bearer YOUR_API_KEY"。如果返回401错误,说明密钥有问题。
  • 可能原因B:网络问题或API端点变更。
    • 排查:TokenBar的网络请求如果超时或失败,会静默记录错误。可以查看macOS的控制台(Console.app)日志,筛选TokenBar进程,看是否有网络错误信息。有时服务商可能会更新API端点地址。
    • 技巧:对于开源Provider,你可以直接查看其源代码中的API URL,确认是否最新。

问题2:检测不到我已安装的工具(如Cursor)。

  • 可能原因:TokenBar的检测路径可能与你的安装路径不符。例如,你可能把Cursor安装在了~/Downloads或自定义位置。
  • 解决:目前TokenBar的检测逻辑是硬编码的。作为用户,你可以手动在Settings中启用该Provider(即使它显示“未检测到”),然后配置API密钥。作为贡献者,你可以修改对应Provider的isDetected实现,增加更多的常见安装路径。

问题3:数据更新不及时。

  • 可能原因:默认的轮询间隔较长(如15分钟),或者上次请求失败后进入了退避状态。
  • 解决:在Settings -> General中调短“Update Interval”。但请务必谨慎,过于频繁的请求可能触发服务的API速率限制。更好的方式是等待下一个轮询周期,或手动重启TokenBar应用。

问题4:CPU或内存占用偶尔偏高。

  • 可能原因:在同时轮询多个Provider时,特别是某个Provider的API响应慢或出错时,可能会引起短暂的资源占用上升。
  • 排查与解决:这是轻量级后台应用的正常现象,通常瞬时高峰后会回落。如果持续占用很高,可以通过活动监视器(Activity Monitor)查看,并考虑在Settings中暂时禁用不常用的Provider。

5.2 开发与贡献避坑指南

如果你打算编译源码或参与贡献,以下几点经验可能对你有帮助:

1. 环境搭建一步到位:确保你的开发环境纯净。除了Xcode命令行工具,最好使用 swiftenv 或asdf来管理Swift版本,确保与项目要求的5.10+一致。直接使用Xcode打开Package.swift文件是最简单的方式,它会自动解决依赖(虽然本项目零依赖,但包括Swift Package Manager本身)。

2. 理解异步与主线程安全:TokenBar大量使用Swift的async/await进行网络请求。牢记:更新UI必须在@MainActor上执行。在Provider中获取到数据后,通常通过@Published属性包装器或ObservableObjectobjectWillChange来通知更新,这些机制会自动处理线程切换。如果你在添加新Provider时遇到UI不更新的问题,首先检查数据更新是否发生在主线程。

3. 钥匙串访问的正确姿势:在代码中访问钥匙串,推荐使用苹果的Security框架,但更推荐使用开源封装库如 KeychainAccess 。虽然TokenBar宣称“零依赖”,但为了安全稳定地处理钥匙串,在开发时引入这样一个经过千锤百炼的库是值得的,它简化了读写、搜索和错误处理。切记,在保存API密钥时,要设置合适的访问控制策略(如kSecAttrAccessibleWhenUnlockedThisDeviceOnly),确保密钥只在设备解锁时可访问,且不与iCloud同步。

4. 为Provider编写单元测试:Tests/TokenBarTests目录下为你的新Provider添加测试。至少应覆盖:

  • isDetected逻辑的测试(模拟文件存在/不存在的情况)。
  • fetchUsage()的成功路径测试(使用模拟的JSON数据)。
  • fetchUsage()的失败路径测试(模拟网络错误、无效密钥、畸形响应等)。 使用XCTest框架,并利用URLProtocol来模拟网络请求,避免在测试中调用真实API。

5. 处理API的变化与兼容性:AI服务的API迭代很快。你的Provider需要有一定的健壮性。在解析JSON时,使用可选类型(?)和try?来优雅地处理字段缺失或类型不匹配,避免整个应用因一个Provider的API变化而崩溃。可以考虑为UsageData设置一个默认值或错误状态,并在UI上友好地显示“暂时无法获取数据”。

6. 关于“零依赖”的权衡:坚持“零依赖”使得项目构建简单、二进制尺寸小。但这也意味着你需要自己实现一些轮子,比如更复杂的网络请求重试机制、更优雅的钥匙串操作。在贡献时,如果你的功能增强需要引入依赖,务必先与项目维护者讨论,因为这违背了项目的核心原则之一。很多时候,可以用更简洁的原生代码来实现核心需求。

TokenBar是一个体现了macOS原生开发美学和实用主义精神的优秀作品。它从一个具体的痛点出发,用精巧的架构和克制的实现,提供了一个近乎完美的解决方案。无论是作为终端用户来管理你的AI开支,还是作为开发者学习SwiftUI和macOS应用架构,它都是一个值得深入研究和使用的项目。我最欣赏的一点是,它在提供强大功能的同时,保持了极致的简洁和专注,这恰恰是很多工具软件所缺乏的品质。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询