I built a statusline because /usage is a distraction
7 min read

I built a statusline because /usage is a distraction

I caught myself typing /usage for the fourth time in an hour. Not because I was close to any limit. Just because I didn’t know if I was close to a limit. The uncertainty was doing the damage, not the actual usage.

Then /context right after it to check how much of the window I’d burned through. Two commands, both breaking my flow, both giving me a number I’d glance at and immediately forget. Repeat in fifteen minutes.

So I did what any developer would do when something starts annoying them enough: I spent an evening automating it away.

The always-visible dashboard

Claude Code has a statusline feature. A small bar at the bottom of your terminal that runs a shell script you configure. It refreshes after every assistant message, so the numbers stay current without you lifting a finger. No commands to type, no flow to break.

Here’s what mine shows:

Context window usage with color coding. A progress bar that goes from green to yellow to red as you eat through your context. The number is always there. I never have to ask for it.

5-hour session limit with countdown. How much of my current rate limit window I’ve used, plus a timer showing when it resets. No more guessing whether I should pace myself or push harder.

7-day weekly limit with budget tracking. This is the one I’m most proud of. More on this below.

Reasoning effort and git branch. Small details, but useful. I can see at a glance if I accidentally switched effort levels or if I’m on the wrong branch before I start a long task.

The budget indicator

The 7-day limit is the tricky one. Knowing you’ve used 41% of your weekly limit means nothing without context. 41% on a Monday? You’re burning through it. 41% on a Thursday? You’re doing great.

My statusline does the math. It calculates what your usage should be if you spread it evenly across the full seven days, then compares that to your actual usage.

β€œ-14.0% vs budget” means I’m 14 percentage points below my expected daily pace. I can push harder. If the number turns positive, I know I’m eating into tomorrow’s share and should maybe ease off β€” or at least make a conscious choice about it rather than finding out when I hit a wall.

It’s a tiny thing. But it turns a vague anxiety β€” am I using too much? should I slow down? β€” into a concrete number. And concrete numbers don’t interrupt your work the way uncertainty does.

How to build your own

The setup takes about five minutes. Add this to your ~/.claude/settings.json:

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "padding": 2
  }
}

Then create the script at ~/.claude/statusline.sh. Here’s a minimal version to get you started:

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

MODEL=$(echo "$input" | jq -r '.model.display_name')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
DIR=$(echo "$input" | jq -r '.workspace.current_dir')

echo "[$MODEL] ${DIR##*/} | ${PCT}% ctx"

Make it executable with chmod +x ~/.claude/statusline.sh and you’re done.

The script receives JSON on stdin with everything you’d want to display: model name, context window percentage, working directory, session cost, rate limit usage for both the 5-hour and 7-day windows. You can make it as minimal or as detailed as you want.

My full version adds color-coded progress bars, the budget calculation, git branch detection, and lines changed β€” all in a two-line display. Here’s the exact script I use daily:

#!/usr/bin/env bash
# Claude Code status line β€” focused on context, limits, and effort
input=$(cat)

# ── Extract all JSON fields ──────────────────────────────────────────────────
model=$(echo "$input" | jq -r '.model.display_name // ""')
used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""')
five_h=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
five_h_resets=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
seven_d=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
seven_d_resets=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')

# ── Effort level from settings ───────────────────────────────────────────────
effort=$(jq -r '.effortLevel // "default"' ~/.claude/settings.json 2>/dev/null)

# ── Git branch (cached 5s) ───────────────────────────────────────────────────
CACHE_FILE="/tmp/claude-sl-git-$(echo "$cwd" | tr '/' '_')"
now=$(date +%s)
branch=""
if [ -f "$CACHE_FILE" ] && [ $(( now - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) )) -lt 5 ]; then
    branch=$(cat "$CACHE_FILE")
else
    git -C "$cwd" rev-parse --git-dir >/dev/null 2>&1 && branch=$(git -C "$cwd" branch --show-current 2>/dev/null)
    echo "$branch" > "$CACHE_FILE"
fi

# ── Context progress bar ─────────────────────────────────────────────────────
pct=$(printf '%.0f' "${used_pct:-0}" 2>/dev/null || echo 0)
[ "$pct" -gt 100 ] && pct=100
bar_w=20
filled=$(( pct * bar_w / 100 ))
empty=$(( bar_w - filled ))

if   [ "$pct" -ge 90 ]; then c="\033[31m"   # red
elif [ "$pct" -ge 70 ]; then c="\033[33m"    # yellow
else c="\033[32m"; fi                         # green

bar=""; i=0; while [ $i -lt $filled ]; do bar+="β–ˆ"; i=$(( i + 1 )); done
i=0; while [ $i -lt $empty ];  do bar+="β–‘"; i=$(( i + 1 )); done

# ── LINE 1: context bar | effort | branch ────────────────────────────────────
line1="${c}${bar}\033[0m ${pct}%"
line1+="  \033[35m⚑${effort}\033[0m"
[ -n "$branch" ] && line1+="  \033[2m🌿 ${branch}\033[0m"

# ── LINE 2: 5h limit | 7d limit with daily pace | model ─────────────────────
line2=""

# 5-hour session limit
if [ -n "$five_h" ]; then
    five_int=$(printf '%.0f' "$five_h")
    if   [ "$five_int" -ge 80 ]; then fc="\033[31m"
    elif [ "$five_int" -ge 50 ]; then fc="\033[33m"
    else fc="\033[32m"; fi
    line2+="${fc}5h: ${five_int}%\033[0m"
    if [ -n "$five_h_resets" ]; then
        reset_in=$(echo "$five_h_resets $now" | awk '{
            secs = $1 - $2
            if (secs < 0) secs = 0
            h = int(secs / 3600)
            m = int((secs % 3600) / 60)
            if (h > 0) printf "%dh%02dm", h, m
            else printf "%dm", m
        }')
        line2+=" \033[2m↺${reset_in}\033[0m"
    fi
fi

# 7-day limit vs relative budget (expected = days_elapsed/7 * 100%)
if [ -n "$seven_d" ]; then
    seven_int=$(printf '%.0f' "$seven_d")
    [ -n "$line2" ] && line2+="  "
    if [ -n "$seven_d_resets" ]; then
        delta_info=$(echo "$seven_d $seven_d_resets $now" | awk '{
            actual = $1
            days_left = ($2 - $3) / 86400
            if (days_left < 0) days_left = 0
            days_elapsed = 7 - days_left
            if (days_elapsed < 0) days_elapsed = 0
            expected = days_elapsed / 7 * 100
            delta = actual - expected
            if (delta > 5) color = "31"
            else if (delta > 0) color = "33"
            else color = "32"
            sign = (delta >= 0) ? "+" : ""
            printf "%s|%s%.1f%%", color, sign, delta
        }')
        delta_color="${delta_info%%|*}"
        delta_str="${delta_info##*|}"
        line2+="\033[${delta_color}m7d: ${seven_int}%\033[0m \033[2m(\033[0m\033[${delta_color}m${delta_str}\033[0m\033[2m vs budget)\033[0m"
    else
        line2+="7d: ${seven_int}%"
    fi
fi

# Lines changed
lines_add=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
lines_rm=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
if [ "$lines_add" -gt 0 ] || [ "$lines_rm" -gt 0 ]; then
    [ -n "$line2" ] && line2+="  "
    line2+="\033[32m+${lines_add}\033[0m/\033[31m-${lines_rm}\033[0m"
fi

# Model name (dimmed)
if [ -n "$model" ]; then
    [ -n "$line2" ] && line2+="  "
    line2+="\033[2m${model}\033[0m"
fi

# ── Output ────────────────────────────────────────────────────────────────────
printf '%b\n' "$line1"
[ -n "$line2" ] && printf '%b\n' "$line2"

You can find the full statusline documentation here or just run the /statusline command to generate one interactively.

The meta-lesson

This is the same pattern I keep coming back to. Don’t build tooling because a tutorial told you to. Build it because something in your workflow kept interrupting you and you got tired of it.

I didn’t set out to build a dashboard. I set out to stop typing /usage four times an hour. The statusline was the smallest possible fix for a real, repeated friction.

If you’re checking the same thing manually more than twice a session, you’re not being diligent. You’re being distracted.