>_ resources
Claude Code Statusline
A real-time analytics dashboard that lives in your Claude Code terminal. Tracks session cost, token burn rate, context window usage, cache efficiency, code changes, git status, and productivity — all in a single line.
225 lines of Bash · Zero dependencies beyond jq and bc
>_Installation
1. Prerequisites
The script requires jq (JSON parsing) and bc (math). Both are pre-installed on macOS. On Linux:
# Debian/Ubuntu
sudo apt install jq bc
# Arch
sudo pacman -S jq bc2. Download the script
Save the script to your Claude config directory:
curl -fsSL https://raba.pl/statusline-command.sh \
-o ~/.claude/statusline-command.sh3. Configure Claude Code
Add the statusline configuration to your ~/.claude/settings.json. Merge this into your existing settings:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline-command.sh"
}
}4. Restart Claude Code
Close and reopen Claude Code. The statusline will appear at the bottom of your terminal. That's it.
>_Sections Breakdown
The statusline is composed of segments separated by dim vertical bars. Each segment shows a different metric. Here is what each one means:
Directory
rabaplShows the name of your current working directory so you always know which project Claude is operating in.
Model
Opus 4.6Displays the active Claude model. Helps confirm whether you're running Opus, Sonnet, or Haiku.
Context Bar
████████████░░░░░░░░ 62%A 20-character visual progress bar showing context window usage. Colors shift from green (0-35%) through yellow (35-70%) to red (70-100%) as the window fills up.
Tokens
↑142.5K ↓ 18.3KTotal input (↑) and output (↓) tokens for the session. Values are formatted as K (thousands) or M (millions) for readability.
Session Cost
$2.85 $0.42/mCurrent session cost in USD (bold). When the session exceeds 60 seconds, a cost-per-minute rate is appended.
Daily Cost
today:$14.23Cumulative spending across all sessions for the current day. Costs are logged to ~/.claude/statusline-cost-log and deduplicated by session ID before summing.
Burn Rate & ETA
8420t/m ~23mToken burn rate (tokens per minute) and estimated time until the context window is exhausted at the current pace. Shows hours for long sessions.
Code Changes
+847/-129Lines of code added (green) and removed (red) during the session. A quick indicator of how much code is being generated.
Cache Hit Rate
73%Prompt cache hit rate — the percentage of input tokens served from cache vs. freshly processed. Higher values mean lower cost and faster responses.
Productivity
24810tok/$Output tokens per dollar spent. A measure of how efficiently the model converts your spend into generated output.
Rank
🏆 / 🥇 / 🥈 / 🥉Productivity tier badge. 🏆 = 50K+ tok/$, 🥇 = 20K+, 🥈 = 10K+, 🥉 = 5K+. Gamifies your cost efficiency.
Git Status
main S:2 U:1 A:0Current branch name with file counts — S (staged, green), U (unstaged, yellow), A (untracked/added, red). Branch name turns yellow when dirty. Git data is cached for 5 seconds.
Clock
16:42Current time of day. Simple but useful during long sessions to avoid losing track of time.
Time Remaining
~23mEstimated time remaining before the context window fills up, based on the current token burn rate.
>_How It Works
Data Flow
Claude Code pipes a JSON object to the statusline command via stdin on every render cycle. The JSON includes the active model, context window metrics, token counts, cost data, code change stats, session ID, and workspace info. The script parses this with jq, computes derived metrics with bc, and echoes a formatted string with ANSI color codes.
Performance
The script runs frequently, so performance matters. Git operations (branch, diff, ls-files) are cached for 5 seconds in /tmp/claude-statusline-git-cache. Daily cost totals are cached for 30 seconds. Token counts use K/M formatting to avoid long numbers.
Cost Logging
Every render appends a line to ~/.claude/statusline-cost-log in the format YYYY-MM-DD|session_id|cost_usd. When computing the daily total, the script deduplicates by session_id (keeping the latest cost per session) before summing. This gives you accurate cross-session daily spend tracking.
Color Gradient
The context window progress bar uses a 20-character block display. The first 7 blocks are green (safe), blocks 8-14 are yellow (caution), and blocks 15-20 are red (critical). Empty positions use a dim shade character.
>_Full Script
The complete statusline-command.sh — 225 lines of Bash. You can also download it directly.
#!/usr/bin/env bash
input=$(cat)
# --- Extract fields (correct paths from official docs) ---
model_name=$(echo "$input" | jq -r '.model.display_name // "?"')
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "~"')
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""')
used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
remaining_pct=$(echo "$input" | jq -r '.context_window.remaining_percentage // 100')
total_in=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0')
total_out=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0')
cur_in=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
cur_out=$(echo "$input" | jq -r '.context_window.current_usage.output_tokens // 0')
cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
context_size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
api_ms=$(echo "$input" | jq -r '.cost.total_api_duration_ms // 0')
lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
version=$(echo "$input" | jq -r '.version // ""')
session_id=$(echo "$input" | jq -r '.session_id // ""')
# --- Colors ---
red=$'\033[31m'; grn=$'\033[32m'; ylw=$'\033[33m'; blu=$'\033[34m'
mag=$'\033[35m'; cyn=$'\033[36m'; dim=$'\033[90m'; bld=$'\033[1m'; rst=$'\033[0m'; wht=$'\033[97m'
sep=" ${dim}${rst} "
# --- Format tokens ---
fmt() {
local n=$1
if [ "$n" -ge 1000000 ] 2>/dev/null; then echo "$(echo "scale=1; $n/1000000" | bc -l)M"
elif [ "$n" -ge 1000 ] 2>/dev/null; then echo "$(echo "scale=1; $n/1000" | bc -l)K"
else echo "$n"; fi
}
# --- Gradient progress bar (20 chars) ---
filled=$(printf "%.0f" "$(echo "$used_pct * 0.2" | bc -l 2>/dev/null || echo 0)")
bar=""
for ((i=0; i<filled && i<20; i++)); do
if [ $i -lt 7 ]; then bar+=$'\033[32m'"█"
elif [ $i -lt 14 ]; then bar+=$'\033[33m'"█"
else bar+=$'\033[31m'"█"; fi
done
for ((i=filled; i<20; i++)); do bar+="${dim}░"; done
bar+="${rst}"
# --- Cost ---
if [ "$(echo "$cost_usd < 0.01" | bc -l 2>/dev/null)" = "1" ]; then cost_str='<$0.01'
elif [ "$(echo "$cost_usd < 1" | bc -l 2>/dev/null)" = "1" ]; then cost_str=$(printf '$%.3f' "$cost_usd")
else cost_str=$(printf '$%.2f' "$cost_usd"); fi
# --- Duration ---
total_secs=$((duration_ms / 1000))
api_secs=$((api_ms / 1000))
h=$((total_secs / 3600)); m=$(((total_secs % 3600) / 60)); s=$((total_secs % 60))
if [ $h -gt 0 ]; then dur="${h}h${m}m"
elif [ $m -gt 0 ]; then dur="${m}m${s}s"
else dur="${s}s"; fi
# --- Burn rate & ETA ---
burn_str=""
if [ $total_secs -gt 0 ]; then
tpm=$(echo "scale=0; ($total_in + $total_out) * 60 / $total_secs" | bc -l 2>/dev/null || echo "0")
if [ "$tpm" -gt 0 ] 2>/dev/null; then
used_tokens=$((cur_in + cache_create + cache_read))
remaining=$((context_size - used_tokens))
if [ "$remaining" -gt 0 ] 2>/dev/null; then
ml=$(echo "scale=0; $remaining / $tpm" | bc -l 2>/dev/null || echo "?")
if [ "$ml" -lt 60 ] 2>/dev/null; then eta="~${ml}m"
else eta="~$((ml / 60))h"; fi
else
eta="full!"
fi
burn_str="${tpm}t/m ${eta}"
fi
fi
# --- Cost per minute ---
cpm_str=""
if [ $total_secs -gt 60 ]; then
cpm=$(echo "scale=3; $cost_usd * 60 / $total_secs" | bc -l 2>/dev/null || echo "0")
cpm_str="$(printf '$%.2f/m' "$cpm")"
fi
# --- Cache hit rate ---
cache_str=""
total_cache=$((cache_create + cache_read))
if [ "$total_cache" -gt 0 ] 2>/dev/null; then
hit_pct=$(echo "scale=0; $cache_read * 100 / ($cache_read + $cache_create + $cur_in)" | bc -l 2>/dev/null || echo "0")
cache_str="${hit_pct}%"
fi
# --- Lines changed ---
lines_str=""
if [ "$lines_added" -gt 0 ] 2>/dev/null || [ "$lines_removed" -gt 0 ] 2>/dev/null; then
lines_str="${grn}+${lines_added}${rst}/${red}-${lines_removed}${rst}"
fi
# --- Git (with caching for perf) ---
CACHE_FILE="/tmp/claude-statusline-git-cache"
CACHE_MAX_AGE=5
cd "$cwd" 2>/dev/null || cd ~
cache_stale() {
[ ! -f "$CACHE_FILE" ] || \
[ $(($(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0))) -gt $CACHE_MAX_AGE ]
}
if cache_stale; then
if git rev-parse --git-dir > /dev/null 2>&1; then
branch=$(git branch --show-current 2>/dev/null || echo "detached")
staged=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
unstaged=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
untracked=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ')
echo "${branch}|${staged}|${unstaged}|${untracked}" > "$CACHE_FILE"
else
echo "|||" > "$CACHE_FILE"
fi
fi
IFS='|' read -r branch staged unstaged untracked < "$CACHE_FILE"
git_str=""
if [ -n "$branch" ]; then
dirty=""
[ "$staged" -gt 0 ] 2>/dev/null && dirty+=" ${grn}S:${staged}${rst}"
[ "$unstaged" -gt 0 ] 2>/dev/null && dirty+=" ${ylw}U:${unstaged}${rst}"
[ "$untracked" -gt 0 ] 2>/dev/null && dirty+=" ${red}A:${untracked}${rst}"
if [ -n "$dirty" ]; then
git_str="${ylw}${branch}${rst}${dirty}"
else
git_str="${grn}${branch}${rst}"
fi
fi
# --- Daily cumulative cost (cached, 30s refresh) ---
COST_LOG="$HOME/.claude/statusline-cost-log"
COST_CACHE="/tmp/claude-statusline-cost-totals"
COST_CACHE_AGE=30
today=$(date +%Y-%m-%d)
echo "${today}|${session_id}|${cost_usd}" >> "$COST_LOG" 2>/dev/null
cost_totals_stale() {
[ ! -f "$COST_CACHE" ] || \
[ $(($(date +%s) - $(stat -f %m "$COST_CACHE" 2>/dev/null || stat -c %Y "$COST_CACHE" 2>/dev/null || echo 0))) -gt $COST_CACHE_AGE ]
}
daily_cost=""
if cost_totals_stale && [ -f "$COST_LOG" ]; then
d_cost=$(awk -F'|' -v d="$today" '$1==d {a[$2]=$3} END {s=0; for(k in a) s+=a[k]; printf "%.2f",s}' "$COST_LOG" 2>/dev/null || echo "0")
echo "${d_cost}" > "$COST_CACHE"
fi
if [ -f "$COST_CACHE" ]; then
read -r daily_cost < "$COST_CACHE"
fi
cost_history_str=""
if [ -n "$daily_cost" ] && [ "$daily_cost" != "0.00" ] 2>/dev/null; then
cost_history_str="today:\$${daily_cost}"
fi
# --- MCP server count ---
mcp_str=""
mcp_count=0
if [ -f "$HOME/.claude/settings.json" ]; then
plugin_count=$(jq '[.enabledPlugins // {} | to_entries[] | select(.value == true)] | length' "$HOME/.claude/settings.json" 2>/dev/null || echo "0")
server_count=$(jq '.mcpServers // {} | keys | length' "$HOME/.claude/settings.json" 2>/dev/null || echo "0")
mcp_count=$((plugin_count + server_count))
fi
[ "$mcp_count" -gt 0 ] 2>/dev/null && mcp_str="${mcp_count}"
# --- Productivity score & rank ---
prod_str=""
rank_str=""
if [ "$(echo "$cost_usd > 0.01" | bc -l 2>/dev/null)" = "1" ]; then
prod=$(echo "scale=0; $total_out / $cost_usd" | bc -l 2>/dev/null || echo "0")
prod_str="${prod}tok/\$"
if [ "$prod" -ge 50000 ] 2>/dev/null; then rank_str="🏆"
elif [ "$prod" -ge 20000 ] 2>/dev/null; then rank_str="🥇"
elif [ "$prod" -ge 10000 ] 2>/dev/null; then rank_str="🥈"
elif [ "$prod" -ge 5000 ] 2>/dev/null; then rank_str="🥉"
else rank_str=""; fi
fi
# --- Build output ---
dir_name="${cwd##*/}"
parts=""
parts+="📁 ${wht}${dir_name}${rst}"
parts+="${sep}${cyn}🤖 ${model_name}${rst}"
parts+="${sep}${bar} $(printf '%.0f' "$used_pct")%"
parts+="${sep}↑$(fmt $total_in) ↓$(fmt $total_out)"
parts+="${sep}💰 ${bld}${cost_str}${rst}"
[ -n "$cpm_str" ] && parts+=" ${dim}${cpm_str}${rst}"
[ -n "$cost_history_str" ] && parts+="${sep}📊 ${dim}${cost_history_str}${rst}"
[ -n "$burn_str" ] && parts+="${sep}🔥 ${dim}${burn_str}${rst}"
[ -n "$lines_str" ] && parts+="${sep}✏️ ${lines_str}"
[ -n "$cache_str" ] && parts+="${sep}💾 ${dim}${cache_str}${rst}"
[ -n "$prod_str" ] && parts+="${sep}⚡ ${dim}${prod_str}${rst}"
[ -n "$rank_str" ] && parts+=" ${rank_str}"
[ -n "$git_str" ] && parts+="${sep}🌿 ${git_str}"
parts+="${sep}🕐 ${dim}$(date '+%H:%M')${rst}"
if [ -n "$eta" ]; then
parts+="${sep}⏳ ${dim}${eta}${rst}"
fi
echo "$parts"