A couple of weeks ago, I wrote about my experiment with artificially slowing Claude Code down to prevent it from burning through tokens without productive output. While my hook speed bumps somewhat helped me get through a day of development without hitting Anthropic’s quota cap, I still lacked visibility into my actual token consumption.
Was I burning through 100K tokens or 1M? This is where having real-time visibility becomes invaluable. And does it matter if I am well below the quota? Like I said last time, it does big time. Not only because Anthropic is planning to tighten the belt on Claude Code usage even further. I still believe that what LLMs do at scale is dangerous to the environment. I know that it sounds hypocritical, but if we can’t reduce our usage of AI tooling, we can at least try to tame it to a level that is reasonable.
Enter ccusage—a brilliant third-party CLI tool created by ryoppippi that analyzes Claude Code usage by parsing the local JSONL files where Claude stores all your interaction data. While not an official Anthropic tool, it’s gained significant traction in the Claude community (4.8k GitHub stars and counting).

The tool gives you comprehensive insights into your token consumption patterns:
- Daily, weekly, and monthly breakdowns
- Session-based tracking
- Cost analysis in USD
- Model-specific usage (Opus vs Sonnet)
- Live dashboards and historical trends
All that’s amazing, but I wanted something way simpler - a glance at my macOS toolbar to see today’s token count. Without the need to open dashboards or remember commands.
Initially, I considered scraping the HTML tables from ccusage
’s web interface to get the data I needed. But then I discovered something much better: the tool supports JSON export via npx ccusage@latest -j
.
Meet xbar #
The missing piece was getting this data into my macOS toolbar. That’s where xbar comes in—a deceptively simple utility created by Mat Ryer, an influential figure in the early days of the Go community, and author of “Go Programming Blueprints.” Built as a Go application using Wails, xbar lets you “put anything in your macOS menu bar.”

xbar works by running executable scripts at regular intervals and displaying their output in the menu bar. This means, you can simply write a script that outputs text, drop it in the plugins folder, and boom—you’ve got a custom menu bar widget. The codebase itself is worth exploring—it’s a great example of how Go can be used to build elegant desktop applications.
The naming convention is crucial. A script named myscript.5m.py
will run every 5 minutes, while otherstuff.1h.sh
runs hourly. This makes it perfect for our use case—we want fresh token data without hammering the ccusage
command constantly.
Setting It Up #
First, install xbar from their website or via Homebrew (brew install xbar
). Once installed, you’ll need to place the Python script in xbar’s plugins directory.
Once again, the naming is crucial here. I called mine claude_tokens.5m.py
, which tells xbar to run it every 5 minutes. The script goes in ~/Library/Application Support/xbar/plugins/
. Make sure it’s executable (chmod +x claude_tokens.5m.py
) or xbar won’t be able to run it.
The result is a clean, unobtrusive display of my daily token usage right in the menu bar. Clicking on it will show you the breakdown—input tokens, output tokens, cache usage, costs, and even all-time totals.
The code below should give you a basic idea of how to extract data out of ccusage
and display it on your Mac’s toolbar. However, it is definitely not the final version. I would like to see what else I can achieve here, so I’ve set up a Gist on GitHub, where you can track the changes.
The Code #
#!/usr/bin/env python3
# <xbar.title>Claude Token Usage</xbar.title>
# <xbar.version>v1.0</xbar.version>
# <xbar.author>Preslav Rachev</xbar.author>
# <xbar.desc>Shows today's Claude Code token usage in the Mac toolbar</xbar.desc>
import json
import subprocess
import os
import glob
from datetime import datetime
from typing import Any
def format_number(num):
"""Formats a number into a human-readable string with K/M suffixes."""
if num >= 1000000:
return f"{num / 1000000:.1f}M"
if num >= 1000:
return f"{num / 1000:.1f}K"
return str(num)
def get_ccusage_data() -> dict[str, Any]:
"""Fetches Claude Code usage statistics using the `npx ccusage@latest -j` command."""
try:
# Set up environment with common paths for xbar
env = os.environ.copy()
# Add common Node.js paths to ensure npx is found
common_paths = [
"/usr/local/bin",
"/usr/bin",
"/bin",
"/opt/homebrew/bin", # Homebrew on Apple Silicon
os.path.expanduser("~/.nvm/versions/node/*/bin"), # NVM paths
os.path.expanduser("~/node_modules/.bin"), # Local node modules
]
# Expand glob patterns and filter existing paths
expanded_paths = []
for path in common_paths:
if "*" in path:
expanded_paths.extend(glob.glob(path))
elif os.path.exists(path):
expanded_paths.append(path)
# Add to PATH if not already present
current_path = env.get("PATH", "")
for path in expanded_paths:
if path not in current_path:
current_path = f"{path}:{current_path}"
env["PATH"] = current_path
# Try running npx from PATH
result = subprocess.run(
["npx", "ccusage@latest", "-j"],
capture_output=True,
text=True,
timeout=30,
check=False,
env=env,
)
if result.returncode == 0:
return json.loads(result.stdout)
return {
"error": f"Command failed with code {result.returncode}",
"stderr": result.stderr,
"stdout": result.stdout,
}
except subprocess.TimeoutExpired:
return {"error": "Command timed out after 30 seconds"}
except json.JSONDecodeError as e:
return {"error": f"JSON decode error: {e}", "stdout": result.stdout}
except FileNotFoundError:
return {"error": "npx command not found - Node.js may not be installed"}
def main():
"""Main function to fetch and display Claude Code usage statistics."""
# Get today's date in YYYY-MM-DD format
today = datetime.now().strftime("%Y-%m-%d")
# Get usage data
data = get_ccusage_data()
if not data or (isinstance(data, dict) and "error" in data):
print("⚠️ Error")
print("---")
if isinstance(data, dict) and "error" in data:
print(f"Error: {data['error']}")
if "stderr" in data:
print(f"Stderr: {data['stderr']}")
if "stdout" in data:
print(f"Stdout: {data['stdout']}")
else:
print("Failed to fetch usage data")
return
# Find today's usage
today_usage = None
for day in data.get("daily", []):
if day.get("date") == today:
today_usage = day
break
if not today_usage:
print("📊 0 tokens")
print("---")
print("No usage today")
return
# Display today's token count in toolbar
total_tokens = today_usage.get("totalTokens", 0)
total_cost = today_usage.get("totalCost", 0)
print(f"📊 {format_number(total_tokens)}")
print("---")
print(f"Today's Usage ({today})")
print(f"Total Tokens: {format_number(total_tokens)}")
print(f"Input: {format_number(today_usage.get('inputTokens', 0))}")
print(f"Output: {format_number(today_usage.get('outputTokens', 0))}")
print(f"Cache Creation: {format_number(today_usage.get('cacheCreationTokens', 0))}")
print(f"Cache Read: {format_number(today_usage.get('cacheReadTokens', 0))}")
print(f"Cost: ${total_cost:.2f}")
print("---")
# Show total usage
totals = data.get("totals", {})
print("Total Usage (All Time)")
print(f"Total Tokens: {format_number(totals.get('totalTokens', 0))}")
print(f"Total Cost: ${totals.get('totalCost', 0):.2f}")
if __name__ == "__main__":
main()
Thank you for reaching this point! If you enjoyed this post or found it helpful, consider supporting my creative journey! Every coffee helps me keep writing and sharing new ideas.
Have something to say? Send me an email or ping me on social media 👇
Want to explore instead? Fly with the time capsule 🛸
You may also find these interesting
Hitting the Brakes on Claude Code
Prevent Claude Code from burning tokens aimlessly. Slow things down with a simple shell trick.
Introducing gomjml: MJML for Go Developers
gomjml is a native Go implementation of the MJML email framework, making responsive email design faster and easier for Go developers.
Boost Your Productivity on the iPad With Guided Access Sessions
The iPad can be a fantastic companion for productivity and creativity. It can also be your biggest source of distraction. Using the built-in Guided Access support will help you stay in focus.
How to Fix Stuck iCloud Syncing on macOS - Part 2
What to do when your Mac won’t sync important files to iCloud. A deeper investigation.
How to Fix Stuck iCloud Syncing on macOS
What to do when your Mac won’t sync important files to iCloud.