Skip to main content
P5V
  1. My Writings /Programming /

Display your Claude Code Token Usage on Your Mac's Toolbar

·5 mins

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).

By default, ccusage will display your token consumption for the last few days

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.”

The xbar menu showing Claude Code token usage - a clean display of my daily consumption

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