A few months ago I shipped the first version of BurnRate (and another version after that). It’s a macOS menu bar app that showed your Claude Code usage limits at a glance.

It solved a real problem: I didn’t want to stop what I was doing to type /usage every time I wondered how close I was to hitting my limits.

But Claude Code solved it better.


The Status Line

Claude Code now supports a customizable status line which is a persistent bar at the bottom of your terminal that updates in real time. You can configure it to show anything the session exposes: model, version, context window usage, token counts, git branch, and, like BurnRate, your 5-hour and 7-day rate limits.

Here’s what mine looks like:

Opus 4.7 v2.1.141  main *
01-acme/acme-gms   12%/1M  in:118.1K out:1.0K  5h:0%

Two lines. Always visible. No separate app, no menu bar icon, no Keychain prompts, no OAuth dance. It’s right there in the tool I’m already using.

My Set Up

If you want to do the same, open ~/.claude/settings.json, and add a statusLine entry that points to a shell script:

{
  "statusLine": {
    "type": "command",
    "command": "/path/to/your/statusline.sh"
  }
}

Claude Code pipes a JSON blob to your script’s stdin on every update. Your script parses it and prints whatever you want displayed. Here’s a simplified version of mine that shows the essentials:

#!/bin/bash

input=$(cat)

# Parse the fields you care about
model=$(echo "$input" | jq -r '.model.display_name // "unknown"')

context_used=$(echo "$input" | jq -r '.context_window.used_percentage // "0"')

rate_5h=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')

rate_7d=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')

# Print it
echo "${model} ctx:${context_used}% 5h:${rate_5h}% 7d:${rate_7d}%"

That’s the minimum. You can add color codes, git info, token counts, directory context and/or whatever you find useful. The full JSON payload includes everything from the model name to your working directory to the current git branch.

This is the full version I use:

#!/bin/bash
input=$(cat)

# Colors
G=$(printf '\033[32m')
B=$(printf '\033[34m')
C=$(printf '\033[36m')
Y=$(printf '\033[33m')
M=$(printf '\033[35m')
W=$(printf '\033[37m')
DIM=$(printf '\033[2m')
R=$(printf '\033[0m')
BOLD=$(printf '\033[1m')

# Parse JSON
model=$(echo "$input" | jq -r '.model.display_name // "unknown"')
version=$(echo "$input" | jq -r '.version // "?"')
cwd=$(echo "$input" | jq -r '.workspace.current_dir // ""')
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""')
branch=$(echo "$input" | jq -r '.worktree.branch // empty')
context_used=$(echo "$input" | jq -r '.context_window.used_percentage // "0"')
context_size=$(echo "$input" | jq -r '.context_window.context_window_size // "0"')
total_in=$(echo "$input" | jq -r '.context_window.total_input_tokens // "0"')
total_out=$(echo "$input" | jq -r '.context_window.total_output_tokens // "0"')
rate_5h=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
rate_7d=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')

# Format context size (e.g., 1000000 -> 1M)
if [ "$context_size" -ge 1000000 ] 2>/dev/null; then
  ctx_fmt="$((context_size / 1000000))M"
elif [ "$context_size" -ge 1000 ] 2>/dev/null; then
  ctx_fmt="$((context_size / 1000))K"
else
  ctx_fmt="$context_size"
fi

# Format token counts (e.g., 45230 -> 45.2K)
fmt_tokens() {
  local n=$1
  if [ "$n" -ge 1000000 ] 2>/dev/null; then
    printf "%.1fM" "$(echo "scale=1; $n / 1000000" | bc)"
  elif [ "$n" -ge 1000 ] 2>/dev/null; then
    printf "%.1fK" "$(echo "scale=1; $n / 1000" | bc)"
  else
    echo "$n"
  fi
}

in_fmt=$(fmt_tokens "$total_in")
out_fmt=$(fmt_tokens "$total_out")

# Git info — use worktree.branch if available, else detect from cwd
if [ -z "$branch" ] && [ -n "$cwd" ]; then
  if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then
    branch=$(git -C "$cwd" --no-optional-locks symbolic-ref --quiet --short HEAD 2>/dev/null || git -C "$cwd" --no-optional-locks rev-parse --short HEAD 2>/dev/null)
  fi
fi

git_info=""
if [ -n "$branch" ]; then
  if [ -n "$cwd" ]; then
    git_status=$(git -C "$cwd" --no-optional-locks status --porcelain 2>/dev/null)
    if [ -n "$git_status" ]; then
      git_info="${C}${branch} ${Y}*${R}"
    else
      git_info="${C}${branch} ${G}ok${R}"
    fi
  else
    git_info="${C}${branch}${R}"
  fi
fi

# Directory display — show project name, then relative cwd if different
dir_display=""
if [ -n "$project_dir" ]; then
  proj_name=$(basename "$project_dir")
  if [ "$cwd" = "$project_dir" ]; then
    dir_display="${B}${BOLD}${proj_name}${R}"
  else
    rel_path="${cwd#$project_dir/}"
    dir_display="${B}${BOLD}${proj_name}${R}${DIM}/${rel_path}${R}"
  fi
else
  dir_display="${B}${BOLD}$(basename "$cwd")${R}"
fi

# Context color based on usage
ctx_color="$G"
if [ "${context_used%.*}" -ge 75 ] 2>/dev/null; then
  ctx_color="$Y"
fi
if [ "${context_used%.*}" -ge 90 ] 2>/dev/null; then
  ctx_color="$M"
fi

# Rate limits
rate_info=""
if [ -n "$rate_5h" ]; then
  rate_info="${DIM}5h:${R}${rate_5h}%"
  if [ -n "$rate_7d" ]; then
    rate_info="${rate_info} ${DIM}7d:${R}${rate_7d}%"
  fi
fi

# Line 1: model + version + git
line1="${W}${BOLD}${model}${R} ${DIM}v${version}${R}"
if [ -n "$git_info" ]; then
  line1="${line1}  ${git_info}"
fi

# Line 2: directory + context + tokens
line2="${dir_display}  ${ctx_color}${context_used}%${R}${DIM}/${ctx_fmt}${R}  ${DIM}in:${R}${in_fmt} ${DIM}out:${R}${out_fmt}"
if [ -n "$rate_info" ]; then
  line2="${line2}  ${rate_info}"
fi

echo "$line1"
echo "$line2"


Drop that into ~/.claude/statusline.sh, make it executable via chmod +x, and point your settings at it. Requires jq which you can install via brew install jq if you don’t have it.

Why This Beats a Separate App

BurnRate worked. I used it daily. But there were inherent friction points that no amount of polish could fix:

  • Keychain prompts. BurnRate needed OAuth credentials from macOS Keychain. That meant system dialogs, and if you missed one, they’d stack up. I fixed the stacking problem, but the prompts themselves were unavoidable.
  • Separate process. Another app running, another thing to update, another thing that could break if Anthropic changed their auth flow or API.
  • Lag. BurnRate polled every five minutes. The status line updates live.

The status line has none of these problems. It runs inside Claude Code’s own process, uses the session’s own auth, and updates as the data changes. And if you’re running multiple sessions (like one for work and one for personal), you can see data respective to each session.

Further, there’s nothing to install, nothing to maintain, and nothing that depends on undocumented endpoints.

The Takeaway

I don’t regret building BurnRate. It scratched an itch at the right time, and building it taught me a lot about Swift, macOS menu bar apps, and shipping a real product with update infrastructure.

The AI economy moves fast and if the native tool provides exactly what I need in a more comprehensive form, I’m happy to default to it.

To that end, I’m retiring BurnRate in favor of the status line.