18.8 Simple Usage Monitor:Claude Code 实时用量监控工具
仓库地址: SrivathsanSivakumar/simple-usage-monitor版本: 0.1.0 许可证: MIT PyPI 包名: sumonitor
本节概览
- 理解 Claude Code 用量监控的痛点与解决方案
- 掌握 Simple Usage Monitor 的核心架构设计
- 学习如何通过 pexpect 实现终端代理模式
- 了解 Claude API 的定价机制与订阅计划限制
1. 项目背景
1.1 问题场景
使用 Claude Code 进行编程时,你是否有过这样的困扰:
- Token 用量盲区:不知道当前会话消耗了多少 Token
- 成本不透明:无法实时了解 API 调用的费用
- 订阅额度焦虑:不确定距离触发限流还有多少余量
- 频繁切换窗口:需要打开网页或其他工具查看用量
通俗比喻:这就像开车时没有油表——你知道车在跑,但不知道还能跑多远,只能等到"抛锚"(触发限流)才知道油箱空了。
1.2 Simple Usage Monitor 的解决方案
Simple Usage Monitor 提供了一个优雅的解决方案:在终端底部实时显示用量信息,让你在使用 Claude Code 的同时,随时掌握:
- 当前会话的 Token 消耗(输入/输出/缓存)
- 实时费用估算
- 订阅计划的使用进度
- 距离额度重置的剩余时间
┌─────────────────────────────────────────────────────────────────┐
│ 正常的 Claude Code 界面 │
│ │
│ > 你的代码和对话内容... │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 💰 $2.34 │ ⬆️ 12.5K │ ⬇️ 8.2K │ 📊 45/250 msgs │ ⏱️ 2h 15m │
└─────────────────────────────────────────────────────────────────┘2. 核心特性
2.1 实时监控指标
| 指标 | 说明 | 数据来源 |
|---|---|---|
| Token 用量 | 输入、输出、缓存读写 Token 数 | ~/.claude/projects/ JSONL 日志 |
| 费用估算 | 基于模型定价的实时成本 | 分层定价算法 |
| 消息计数 | 当前会话的消息数量 | 日志解析 |
| 额度进度 | 相对于订阅计划的使用百分比 | 计划限制配置 |
| 重置倒计时 | 距离 5 小时窗口重置的时间 | 时间戳计算 |
2.2 支持的模型与定价
python
# 定价配置($ per Million Tokens)
MODEL_PRICING = {
"claude-opus-4-5-20251101": {
"input": 5.00, # 输入 Token
"output": 25.00, # 输出 Token
"cache_write": 6.25,
"cache_read": 0.50
},
"claude-sonnet-4-5-20251101": {
"input": 3.00,
"output": 15.00,
"cache_write": 3.75,
"cache_read": 0.30,
"tiered": True # 200K Token 后分级提价
},
"claude-haiku-4-5-20251101": {
"input": 1.00,
"output": 5.00,
"cache_write": 1.25,
"cache_read": 0.10
}
}2.3 订阅计划限制
根据 Claude Code 的订阅计划,每 5 小时窗口有不同的限制:
| 计划 | 输出 Token 限制 | 成本上限 | 消息限制 |
|---|---|---|---|
| Pro | 19,000 | $18.00 | 250 条 |
| Max5 | 88,000 | $35.00 | 1,000 条 |
| Max20 | 220,000 | $140.00 | 2,000 条 |
3. 技术架构
3.1 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ Simple Usage Monitor │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ main.py │───▶│ Terminal │───▶│ Claude │ │
│ │ (入口点) │ │ Handler │ │ Code │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ argparse │ │ Overlay │ │ pexpect │ │
│ │ (命令行) │ │ (叠加层) │ │ (进程) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ LogReader │ │
│ │ (日志解析) │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ ~/.claude/ │ │
│ │ projects/ │ │
│ │ *.jsonl │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘3.2 核心模块说明
| 模块 | 路径 | 职责 |
|---|---|---|
| main.py | src/sumonitor/main.py | 程序入口,参数解析,进程编排 |
| TerminalHandler | src/sumonitor/terminal/ | 终端管理,叠加层渲染,窗口适配 |
| LogReader | src/sumonitor/data/ | JSONL 日志解析,Token 统计 |
| Pricing | src/sumonitor/data/pricing.py | 模型定价,费用计算 |
3.3 关键设计决策
设计决策 1:pexpect 代理模式
Simple Usage Monitor 采用了"代理模式"而非"日志轮询模式":
python
# 代理模式:通过 pexpect 启动 Claude Code
spawn = pexpect.spawn(
claude_path,
encoding='utf-8',
dimensions=(rows, cols)
)为什么选择代理模式?
- 无缝集成:用户像平常一样使用
sumonitor命令即可 - 实时交互:保持完整的终端交互体验
- 窗口适配:自动处理终端大小变化(SIGWINCH)
设计决策 2:ANSI 叠加层渲染
使用 ANSI 转义序列在终端底部绘制信息:
python
def draw_overlay(self, text):
# 保存光标位置
sys.stdout.write('\x1b[s')
# 移动到最后一行
sys.stdout.write(f'\x1b[{self.rows};1H')
# 清除当前行并写入叠加层
sys.stdout.write('\x1b[2K' + text)
# 恢复光标位置
sys.stdout.write('\x1b[u')
sys.stdout.flush()为什么这样设计?
- 非侵入性:不干扰正常的终端输出
- 实时更新:每秒刷新,无闪烁
- 光标保护:保存/恢复光标位置,用户输入不受影响
4. 代码解析
4.1 入口函数 main.py
python
def get_args_parser():
"""配置命令行参数解析器"""
parser = argparse.ArgumentParser(
description="Real-time token and cost monitoring for Claude Code"
)
parser.add_argument('--version', action='version', version='0.1.0')
parser.add_argument('--path', help='Custom Claude Code installation path')
parser.add_argument(
'--plan',
choices=['pro', 'max5', 'max20'],
default='pro',
help='Subscription plan for limit tracking'
)
return parser
def main():
"""主函数:编排整个监控流程"""
args = get_args_parser().parse_args()
# 1. 创建 pexpect 子进程启动 Claude Code
spawn = pexpect.spawn(
args.path or find_claude_binary(),
encoding='utf-8'
)
# 2. 初始化日志读取器
log_reader = LogReader()
# 3. 初始化终端处理器(传入日志读取器和 spawn 对象)
terminal = TerminalHandler(log_reader, spawn, args.plan)
# 4. 设置终端尺寸
terminal.set_dimensions(*terminal.get_terminal_size())
# 5. 注册窗口大小变化信号处理
signal.signal(signal.SIGWINCH, terminal.on_resize)
# 6. 进入交互模式
terminal.interact()代码解读:
- pexpect.spawn:启动 Claude Code 作为子进程,建立双向通信管道
- LogReader:监听
~/.claude/projects/目录下的 JSONL 日志文件 - TerminalHandler:管理终端显示,包括叠加层渲染
- SIGWINCH 处理:当用户调整终端窗口大小时,自动适配
4.2 日志读取器 LogReader
python
class UsageData:
"""用量数据结构"""
model: str
input_tokens: int
output_tokens: int
cache_read_tokens: int
cache_write_tokens: int
cost: float
timestamp: datetime
class LogReader:
def __init__(self):
self.processed_entries = set() # 去重已处理的条目
def get_jsonl_files(self) -> List[Path]:
"""获取当前项目相关的 JSONL 日志文件"""
claude_dir = Path.home() / '.claude' / 'projects'
# 返回与当前工作目录关联的日志文件
return list(claude_dir.glob('**/conversation*.jsonl'))
def parse_json_files(self) -> List[UsageData]:
"""解析 JSONL 文件,提取最近 120 小时的用量数据"""
usage_list = []
cutoff_time = datetime.now() - timedelta(hours=120)
for file in self.get_jsonl_files():
with open(file, 'r') as f:
for line in f:
try:
entry = json.loads(line)
# 检查时间戳和去重
if self._should_process(entry, cutoff_time):
usage = self._extract_usage(entry)
usage_list.append(usage)
except json.JSONDecodeError:
continue
return usage_list
def _calculate_total_cost(self, usage: UsageData) -> float:
"""计算费用(支持分层定价)"""
pricing = get_model_pricing(usage.model)
# 基础费用
cost = (
usage.input_tokens * pricing['input'] / 1_000_000 +
usage.output_tokens * pricing['output'] / 1_000_000
)
# 缓存费用
cost += (
usage.cache_read_tokens * pricing['cache_read'] / 1_000_000 +
usage.cache_write_tokens * pricing['cache_write'] / 1_000_000
)
# 分层定价(如 Sonnet 在 200K Token 后提价)
if pricing.get('tiered') and usage.output_tokens > 200_000:
# 应用分层费率...
pass
return cost代码解读:
- 去重机制:使用
processed_entriesSet 避免重复计算同一条日志 - 时间窗口:只处理最近 120 小时的数据(符合订阅周期逻辑)
- 优雅降级:遇到 JSON 解析错误时跳过,不中断整体流程
4.3 终端处理器 TerminalHandler
python
class TerminalHandler:
def __init__(self, log_reader, spawn, plan):
self.log_reader = log_reader
self.spawn = spawn
self.plan = plan
self.rows = 24
self.cols = 80
def get_terminal_size(self) -> Tuple[int, int]:
"""获取终端尺寸"""
import fcntl, termios, struct
result = fcntl.ioctl(0, termios.TIOCGWINSZ,
b'\x00\x00\x00\x00\x00\x00\x00\x00')
rows, cols = struct.unpack('hh', result[:4])
return rows, cols
def on_resize(self, signum, frame):
"""处理终端窗口大小变化"""
self.rows, self.cols = self.get_terminal_size()
self.spawn.setwinsize(self.rows, self.cols)
def get_overlay_data(self) -> str:
"""构建叠加层显示内容"""
usage_list = self.log_reader.parse_json_files()
# 聚合统计
total_input = sum(u.input_tokens for u in usage_list)
total_output = sum(u.output_tokens for u in usage_list)
total_cost = sum(u.cost for u in usage_list)
message_count = len(usage_list)
# 获取计划限制
limits = get_plan_limits(self.plan)
# 计算重置时间
time_to_reset = self._calculate_reset_time()
# 格式化显示
return (
f"💰 ${total_cost:.2f} │ "
f"⬆️ {total_input/1000:.1f}K │ "
f"⬇️ {total_output/1000:.1f}K │ "
f"📊 {message_count}/{limits['messages']} msgs │ "
f"⏱️ {time_to_reset}"
)
def draw_overlay(self, text):
"""在终端底部绘制叠加层"""
sys.stdout.write('\x1b[s') # 保存光标
sys.stdout.write(f'\x1b[{self.rows};1H') # 移动到最后一行
sys.stdout.write('\x1b[2K') # 清除当前行
sys.stdout.write(text) # 写入内容
sys.stdout.write('\x1b[u') # 恢复光标
sys.stdout.flush()代码解读:
- TIOCGWINSZ:通过 ioctl 系统调用获取终端的实际尺寸
- ANSI 转义序列:
\x1b[s(保存光标)、\x1b[u(恢复光标)、\x1b[2K(清行) - 非阻塞更新:叠加层在独立线程中每秒刷新,不影响主交互
5. 安装与使用
5.1 安装方法
方法 1:通过 PyPI 安装(推荐)
bash
pip install sumonitor方法 2:从源码安装
bash
git clone https://github.com/SrivathsanSivakumar/simple-usage-monitor.git
cd simple-usage-monitor
pip install .方法 3:使用 pipx(隔离环境)
bash
pipx install sumonitor5.2 使用示例
基础用法
bash
# 启动监控(使用默认 Pro 计划)
sumonitor指定订阅计划
bash
# Max5 订阅用户
sumonitor --plan max5
# Max20 订阅用户
sumonitor --plan max20自定义 Claude Code 路径
bash
sumonitor --path /custom/path/to/claude5.3 显示效果
启动后,你会看到正常的 Claude Code 界面,但在终端底部多了一行监控信息:
┌─────────────────────────────────────────────────────────────────┐
│ claude-code-os-x> 帮我写一个快速排序算法 │
│ │
│ 我来为你实现一个快速排序算法... │
│ │
│ ```python │
│ def quicksort(arr): │
│ if len(arr) <= 1: │
│ return arr │
│ ... │
├─────────────────────────────────────────────────────────────────┤
│ 💰 $0.42 │ ⬆️ 2.1K │ ⬇️ 1.8K │ 📊 12/250 msgs │ ⏱️ 4h 32m │
└─────────────────────────────────────────────────────────────────┘6. 技术亮点与局限性
6.1 技术亮点
| 特性 | 说明 |
|---|---|
| 零配置 | 自动检测 Claude Code 安装路径和项目目录 |
| 本地处理 | 所有数据在本地处理,保护隐私 |
| 实时更新 | 每秒刷新,无需手动触发 |
| 窗口自适应 | 自动响应终端大小变化 |
| 轻量依赖 | 仅依赖 pexpect(Python 标准库之外) |
6.2 局限性
| 局限 | 原因 |
|---|---|
| 仅支持 Claude Code | 依赖特定的日志格式和路径 |
| 定价可能过时 | 硬编码的定价需要手动更新 |
| 仅限终端 | 不支持 IDE 集成环境 |
| Unix-like 系统 | pexpect 在 Windows 上支持有限 |
6.3 与类似工具对比
| 工具 | Simple Usage Monitor | Claude Code Usage Monitor |
|---|---|---|
| 安装方式 | pip/PyPI | 源码 |
| 显示位置 | 终端内嵌 | 独立窗口 |
| 更新频率 | 实时(1s) | 定时刷新 |
| 订阅计划支持 | ✅ Pro/Max5/Max20 | 部分 |
7. 延伸学习
7.1 相关技术点
- pexpect 库:Python 的进程交互库,用于自动化和测试
- ANSI 转义序列:终端控制的标准协议,了解更多可查阅 ANSI escape code
- JSONL 格式:每行一个 JSON 对象,适合日志和流式处理
- Claude API 定价:官方定价页面
7.2 可以探索的改进方向
- 跨平台支持:使用
winpty或ConPTY支持 Windows - 历史趋势图表:集成
rich或textual显示用量趋势 - 配置文件支持:允许用户自定义显示格式和阈值告警
- IDE 插件:开发 VS Code 扩展版本
8. 小结
Simple Usage Monitor 是一个精巧的 CLI 工具,通过 pexpect 代理模式和 ANSI 叠加层技术,实现了在不干扰正常工作流的前提下,为 Claude Code 用户提供实时的用量监控能力。
核心收获:
- 代理模式设计:通过 pexpect 启动子进程,实现透明代理
- ANSI 终端控制:使用转义序列实现非侵入式的 UI 叠加
- 日志解析架构:JSONL 格式的流式处理与去重机制
- 订阅感知定价:支持多种订阅计划的差异化限制
这个项目展示了如何用约 500 行 Python 代码解决一个实际的开发者痛点,是学习终端工具开发和 Claude API 集成的优秀案例。