Published on

CCBuddy - 构建原生 macOS 菜单栏应用监控 Claude Code 使用量

Authors
  • avatar
    Name
    Jack Qin
    Twitter

作为 Claude Code 的重度用户,我一直想知道自己消耗了多少 token 以及花费了多少钱。所以我构建了 CCBuddy - 一款原生 macOS 菜单栏应用来解决这个问题。

问题背景

Claude Code 是 Anthropic 推出的强大 AI 编程助手。无论你使用的是 Pro/Max 订阅还是按量付费的 API,了解你的使用情况都至关重要。

挑战在于:Claude Code 没有提供可视化的使用量仪表板。你所有的对话日志都静静地存储在 ~/.claude/projects/ 下的 JSONL 文件中,但分析它们需要手动操作。

我决定构建一款原生 macOS 应用,让 token 使用量和成本一目了然。

CCBuddy 是什么?

CCBuddy 是一款轻量级的 macOS 菜单栏应用,具有以下功能:

  • 实时监控 Claude Code 的 token 使用量和成本
  • 双模式支持:Pro/Max 订阅(5小时滚动窗口)和 API 按量付费模式
  • 成本预测:基于当前"燃烧率"估算总成本
  • 历史分析:通过图表查看按日/周/月的使用趋势
  • 完全离线:所有计算都在本地进行,零网络请求

它静静地待在你的菜单栏中,随时准备向你展示统计数据。

主要功能

双模式定价

Pro/Max 模式 - 适用于订阅用户:

  • 5小时滚动时间窗口
  • 实时使用百分比和剩余时间
  • 燃烧率和成本预测

API 模式 - 适用于按量付费用户:

  • 今日/本周/本月/全部累计成本
  • 无时间窗口限制

全面的成本计算

支持所有 Claude 模型的定价,包括最新的 Claude Opus 4.5:

模型输入输出缓存创建缓存读取
Opus 4.5$5/M$25/M$6.25/M$0.50/M
Opus 4$15/M$75/M$18.75/M$1.50/M
Sonnet 4/4.5$3/M$15/M$3.75/M$0.30/M
Haiku 4.5$1/M$5/M$1.25/M$0.10/M

值得注意的是,CCBuddy 完全支持 Prompt Caching 成本计算 - 这是许多工具忽略的细节。Claude 的缓存机制会产生额外的 cache_creation_input_tokenscache_read_input_tokens,它们的定价与常规 token 不同。

玻璃拟态 UI

作为一款原生 macOS 应用,我希望它能与系统无缝融合。使用 SwiftUI + AppKit 的 NSVisualEffectView,我实现了标志性的 macOS 玻璃拟态效果:

  • 可调节透明度
  • 多种材质样式(从超薄到超厚)
  • 流畅的动画和过渡效果

技术深入解析

架构

项目遵循 MVVM 架构,大约 3,000 行 Swift 代码:

CCBuddy/
├── Models/           # 数据模型
├── Services/         # 业务逻辑(解析、计算、文件监控)
├── ViewModels/       # UI 状态管理
├── Views/            # SwiftUI 视图
└── Utilities/        # 辅助工具和扩展

实时监控管道

这是应用的核心。我们如何高效地监控文件变化并更新 UI?

FileWatcher(0.5秒轮询)
检测到变化,标记为脏
定时器检查(默认10秒)
快速修改时间检查(避免不必要的解析)
JSONLParser 解析会话数据
UsageCalculator 计算统计数据
UsageViewModel 发布更新
SwiftUI 自动刷新 UI

关键优化:

  • 脏标记标记:仅在文件实际更改时重新解析
  • 修改时间检查:快速跳过未更改的文件
  • 去重:基于 requestId + messageId 避免重复计算

JSONL 解析

Claude Code 以 JSONL 格式存储数据(每行一个 JSON 对象):

{
  "type": "assistant",
  "sessionId": "xxx-xxx-xxx",
  "timestamp": "2025-12-02T13:03:29.591Z",
  "message": {
    "model": "claude-opus-4-5-20251101",
    "usage": {
      "input_tokens": 9,
      "cache_creation_input_tokens": 5095,
      "cache_read_input_tokens": 12610,
      "output_tokens": 5
    }
  }
}

解析注意事项:

  • 仅处理 type: "assistant" 的消息(用户消息不计费)
  • 正确累加所有四种 token 类型
  • 处理多会话、多项目数据聚合

Swift Charts 可视化

使用 Swift Charts 框架进行使用历史可视化:

Chart(dailyData) { item in
    BarMark(
        x: .value("Date", item.date),
        y: .value("Cost", item.cost)
    )
    .foregroundStyle(.blue.gradient)
}

支持日/周/月视图切换,直观分析趋势。

开发心得

菜单栏应用的注意事项

macOS 菜单栏应用(LSUIElement)有一些特殊之处:

  • 没有 Dock 图标
  • 没有主窗口
  • 需要手动管理 Popover 的显示/隐藏状态

NSStatusItem + NSPopover 组合是最佳实践。

SwiftUI 和 AppKit 的桥接

纯 SwiftUI 无法实现某些效果(如真正的玻璃拟态),需要使用 NSViewRepresentable 桥接 AppKit:

struct VisualEffectView: NSViewRepresentable {
    func makeNSView(context: Context) -> NSVisualEffectView {
        let view = NSVisualEffectView()
        view.material = .hudWindow
        view.blendingMode = .behindWindow
        return view
    }
}

自动化发布管道

我配置了完整的 GitHub Actions 工作流:

  • 自动编译发布版本
  • 打包成带有正确 Info.plist 的 .app 包
  • 使用 create-dmg 创建带自定义图标的 DMG 安装程序
  • 自动发布带有更新日志的 GitHub Release

这使得版本发布变得轻而易举。

用户体验

安装后,CCBuddy 驻留在你的菜单栏中。点击图标可以看到:

┌────────────────────────────────────┐
│ ▊▊▊  CCBuddy   Pro          10s   │
├────────────────────────────────────┤
│ 会话进度                      35%│ ████████████░░░░░░░░░░░░░░░░░░░░  │
│                                   │
│ 📄 已用 Token              7.4M   │
│ 💲 会话成本            $3.1725│ 🕐 剩余时间              3:58│ 📈 预计成本            $22.10│ 🔥 燃烧率          186.2K/分钟   │
└────────────────────────────────────┘

一目了然,你可以看到:

  • 当前周期的 token 消耗
  • 当前支出
  • 窗口剩余时间
  • 按当前速率的预计成本

再也不用担心不知不觉中超支了!

技术栈

  • 语言:Swift 5.9+
  • UI 框架:SwiftUI + AppKit
  • 图表:Swift Charts
  • 最低系统要求:macOS 14.0 (Sonoma)
  • 构建系统:Swift Package Manager
  • CI/CD:GitHub Actions

路线图

  • 使用量阈值警报(50%/75%/90%)
  • 开机自启动
  • 历史数据导出
  • 键盘快捷键
  • Sparkle 自动更新集成

总结

CCBuddy 源于真实需求。作为 Claude Code 的重度用户,我需要一个工具来追踪我的使用量,所以我自己构建了一个。

整个项目大约 3,000 行 Swift 代码,遵循 MVVM 架构,完全开源。如果你也是 Claude Code 用户,试试看吧!


这篇博客文章是在 Claude Code 的协助下撰写的,而 CCBuddy 则在菜单栏中静静地监控着 token 消耗。