Skip to content

Instantly share code, notes, and snippets.

@dui
Created April 11, 2026 00:07
Show Gist options
  • Select an option

  • Save dui/6f387f68e5f4592d5191006189c2e7d4 to your computer and use it in GitHub Desktop.

Select an option

Save dui/6f387f68e5f4592d5191006189c2e7d4 to your computer and use it in GitHub Desktop.
Claude Code statusline — 2-line context bar with token buckets, 5h/7d rate limits, $ cost breakdown. Pairs with cc_cost.py.
#!/bin/bash
input=$(cat)
# One-shot debug dump: write the latest received JSON so we can verify what
# Claude Code actually sends. Safe to leave on; it's a single file overwrite.
echo "$input" > /tmp/claude-statusline-last.json
# ── Colors ────────────────────────────────────────────────────────
RESET="\033[0m"
GREEN="\033[38;2;74;222;128m"
YELLOW="\033[33m"
ORANGE="\033[38;5;208m"
RED="\033[31m"
# Context bar icons
C_SYS="\033[38;2;136;136;136m" # Gray — system prompt/tools
C_MSG="\033[38;2;147;51;234m" # Purple — messages
C_FREE="\033[38;2;80;80;80m" # Dim — free space
C_BUF="\033[38;2;100;100;100m" # Dim — autocompact buffer
# Token buckets (per-call)
C_NEW="$GREEN" # Green — uncached input
C_CW="$YELLOW" # Yellow — cache write
C_CR="\033[36m" # Cyan — cache read
C_OUT="\033[34m" # Blue — output
# Chrome
C_DIR="\033[38;2;255;255;255m" # White — directory
C_WT="\033[38;2;255;193;7m" # Gold — worktree tag
C_BR="\033[38;2;78;205;196m" # Teal — git branch
C_SUM="\033[38;2;180;130;255m" # Light purple — session totals
C_ADD="$GREEN" # Green — lines added
C_DEL="$RED" # Red — lines removed
C_DIM="\033[38;2;100;100;100m" # Dim — separators
SEP="${C_DIM}|${RESET}"
# ── Parse JSON (single jq call) ──────────────────────────────────
eval "$(echo "$input" | jq -r '
@sh "CWD=\(.workspace.current_dir // "?")",
@sh "TRANSCRIPT_PATH=\(.transcript_path // "")",
@sh "CONTEXT_PCT=\(.context_window.used_percentage // 0 | floor)",
@sh "CONTEXT_SIZE=\(.context_window.context_window_size // 200000)",
@sh "DURATION_MS=\(.cost.total_duration_ms // 0)",
@sh "LINES_ADDED=\(.cost.total_lines_added // 0)",
@sh "LINES_REMOVED=\(.cost.total_lines_removed // 0)",
@sh "NEW_TOKENS=\(.context_window.current_usage.input_tokens // 0)",
@sh "CACHE_WRITE=\(.context_window.current_usage.cache_creation_input_tokens // 0)",
@sh "CACHE_READ=\(.context_window.current_usage.cache_read_input_tokens // 0)",
@sh "OUT_TOKENS=\(.context_window.current_usage.output_tokens // 0)",
@sh "TOTAL_IN=\(.context_window.total_input_tokens // 0)",
@sh "TOTAL_OUT=\(.context_window.total_output_tokens // 0)",
@sh "SESSION_COST_USD=\(.cost.total_cost_usd // 0)",
@sh "FIVE_H_PCT=\(.rate_limits.five_hour.used_percentage // empty)",
@sh "FIVE_H_RESET=\(.rate_limits.five_hour.resets_at // empty)",
@sh "SEVEN_D_PCT=\(.rate_limits.seven_day.used_percentage // empty)",
@sh "SEVEN_D_RESET=\(.rate_limits.seven_day.resets_at // empty)"
')"
# ── Helpers ───────────────────────────────────────────────────────
fmt() {
local t=$1
if [ "$t" -ge 1000000 ]; then
awk "BEGIN {printf \"%.1fM\", $t/1000000}"
elif [ "$t" -ge 1000 ]; then
echo "$((t / 1000))k"
else
echo "$t"
fi
}
# 4-tier color ramp: green → yellow → orange → red
ramp() {
local val=$1 t1=$2 t2=$3 t3=$4
if [ "$val" -lt "$t1" ]; then echo "$GREEN"
elif [ "$val" -lt "$t2" ]; then echo "$YELLOW"
elif [ "$val" -lt "$t3" ]; then echo "$ORANGE"
else echo "$RED"
fi
}
# ── Derived values ────────────────────────────────────────────────
# Token formatting
NEW_TOK=$(fmt "$NEW_TOKENS")
CW_TOK=$(fmt "$CACHE_WRITE")
CR_TOK=$(fmt "$CACHE_READ")
OUT_TOK=$(fmt "$OUT_TOKENS")
TOT_IN=$(fmt "$TOTAL_IN")
TOT_OUT=$(fmt "$TOTAL_OUT")
# Context window label (200k or 1M)
if [ "$CONTEXT_SIZE" -ge 1000000 ]; then CTX_SIZE="1M"
else CTX_SIZE="$((CONTEXT_SIZE / 1000))k"; fi
# Context breakdown (approximate: sys ~24k fixed, buffer ~16.5% of window)
SYS_OVERHEAD=24000
COMPACT_BUFFER=$((CONTEXT_SIZE * 165 / 1000))
TOTAL_INPUT_NOW=$((NEW_TOKENS + CACHE_WRITE + CACHE_READ))
MSG_TOKENS=$((TOTAL_INPUT_NOW - SYS_OVERHEAD)); [ "$MSG_TOKENS" -lt 0 ] && MSG_TOKENS=0
FREE_TOKENS=$((CONTEXT_SIZE - TOTAL_INPUT_NOW - COMPACT_BUFFER)); [ "$FREE_TOKENS" -lt 0 ] && FREE_TOKENS=0
SYS_TOK=$(fmt "$SYS_OVERHEAD")
MSG_TOK=$(fmt "$MSG_TOKENS")
FREE_TOK=$(fmt "$FREE_TOKENS")
BUF_TOK=$(fmt "$COMPACT_BUFFER")
# Context bar (20 cells, each = 5% of window)
BAR_WIDTH=20
SYS_CELLS=$(( (SYS_OVERHEAD * BAR_WIDTH + CONTEXT_SIZE / 2) / CONTEXT_SIZE ))
BUF_CELLS=$(( (COMPACT_BUFFER * BAR_WIDTH + CONTEXT_SIZE / 2) / CONTEXT_SIZE ))
MSG_CELLS=$(( (MSG_TOKENS * BAR_WIDTH + CONTEXT_SIZE / 2) / CONTEXT_SIZE ))
FREE_CELLS=$(( BAR_WIDTH - SYS_CELLS - MSG_CELLS - BUF_CELLS ))
[ "$FREE_CELLS" -lt 0 ] && FREE_CELLS=0
CTX_BAR=""
for ((i=0; i<SYS_CELLS; i++)); do CTX_BAR+="${C_SYS}⛁"; done
for ((i=0; i<MSG_CELLS; i++)); do CTX_BAR+="${C_MSG}⛁"; done
for ((i=0; i<FREE_CELLS; i++)); do CTX_BAR+="${C_FREE}⛶"; done
for ((i=0; i<BUF_CELLS; i++)); do CTX_BAR+="${C_BUF}⛝"; done
CTX_BAR+="${RESET}"
# Dynamic colors (context %: 50/70/80, time: 60/120/180 mins)
CTX_COLOR=$(ramp "$CONTEXT_PCT" 50 70 80)
SECS=$((DURATION_MS / 1000))
MINS=$((SECS / 60))
TIME="${MINS}m$(printf '%02d' $((SECS % 60)))s"
C_TIME=$(ramp "$MINS" 60 120 180)
# Directory + git
DIR_NAME="${CWD##*/}"
BRANCH=""
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
if [ -f "$CWD/.git" ] || [[ "$GIT_DIR" == *"/worktrees/"* ]]; then
DIR_NAME+="${C_WT}[wt]${RESET}"
fi
fi
# ── Daily Target (from cached stats) ──────────────────────────────
STATS_CACHE="$HOME/.cache/pulse-stats/statusbar.json"
STATS_STALE_S=300 # 5 minutes
DAILY_PROGRESS=""
if [ -f "$STATS_CACHE" ]; then
# Check staleness — refresh if >5min old or wrong date
CACHE_MTIME=$(stat -f %m "$STATS_CACHE" 2>/dev/null || echo 0)
NOW_EPOCH=$(date +%s)
CACHE_AGE=$(( NOW_EPOCH - CACHE_MTIME ))
CACHE_DATE=$(jq -r '.today.date // ""' "$STATS_CACHE" 2>/dev/null)
TODAY_DATE=$(date +%Y-%m-%d)
if [ "$CACHE_DATE" != "$TODAY_DATE" ] || [ "$CACHE_AGE" -gt "$STATS_STALE_S" ]; then
python3 "$HOME/.claude/skills/pulse/stats.py" --statusbar &>/dev/null &
fi
# Read cache values
eval "$(jq -r '
@sh "DT_LINES=\(.today.lines_added // 0)",
@sh "DT_TARGET=\(.target.daily_lines // 5000)",
@sh "DT_PCT=\(.target.progress_pct // 0)"
' "$STATS_CACHE" 2>/dev/null)"
# Format lines added (e.g., 2.1k)
if [ "${DT_LINES:-0}" -ge 1000 ]; then
DT_FMT=$(awk "BEGIN {printf \"%.1fk\", ${DT_LINES}/1000}")
else
DT_FMT="${DT_LINES:-0}"
fi
# Format target (e.g., 5k)
if [ "${DT_TARGET:-0}" -ge 1000 ]; then
DT_TGT_FMT=$(awk "BEGIN {printf \"%.0fk\", ${DT_TARGET}/1000}")
else
DT_TGT_FMT="${DT_TARGET:-0}"
fi
# Color based on progress (inverted: higher = better)
DT_PCT_VAL="${DT_PCT:-0}"
if [ "$DT_PCT_VAL" -ge 100 ]; then
C_PROG="$GREEN"
elif [ "$DT_PCT_VAL" -ge 50 ]; then
C_PROG="$YELLOW"
elif [ "$DT_PCT_VAL" -ge 25 ]; then
C_PROG="$ORANGE"
else
C_PROG="$RED"
fi
DAILY_PROGRESS="${C_PROG}${DT_FMT}/${DT_TGT_FMT}${RESET}"
fi
# ── cc_cost cache: window + weekly $ totals ──────────────────────
# Refreshed in background by `cc_cost.py --cache-statusbar` (~5s).
# Read instantly from JSON; trigger refresh if stale or missing.
CC_COST_CACHE="$HOME/.cache/claude-stats/cc_cost.json"
CC_COST_STALE_S=300 # 5 minutes
WINDOW_COST_USD=""
WINDOW_CW_USD=""; WINDOW_CR_USD=""; WINDOW_OUT_USD=""
WEEKLY_COST_USD=""
WEEKLY_CW_USD=""; WEEKLY_CR_USD=""; WEEKLY_OUT_USD=""
if [ -f "$CC_COST_CACHE" ]; then
CC_COST_MTIME=$(stat -f %m "$CC_COST_CACHE" 2>/dev/null || echo 0)
NOW_EPOCH=$(date +%s)
CC_COST_AGE=$(( NOW_EPOCH - CC_COST_MTIME ))
if [ "$CC_COST_AGE" -gt "$CC_COST_STALE_S" ]; then
python3 "$HOME/Notes/scripts/cc_cost.py" --cache-statusbar &>/dev/null &
fi
eval "$(jq -r '
@sh "WINDOW_COST_USD=\(.window_cost_usd // empty)",
@sh "WINDOW_CW_USD=\(.window_cw_usd // empty)",
@sh "WINDOW_CR_USD=\(.window_cr_usd // empty)",
@sh "WINDOW_OUT_USD=\(.window_out_usd // empty)",
@sh "WEEKLY_COST_USD=\(.weekly_cost_usd // empty)",
@sh "WEEKLY_CW_USD=\(.weekly_cw_usd // empty)",
@sh "WEEKLY_CR_USD=\(.weekly_cr_usd // empty)",
@sh "WEEKLY_OUT_USD=\(.weekly_out_usd // empty)"
' "$CC_COST_CACHE" 2>/dev/null)"
else
# First run: kick off the build so it's available next render.
python3 "$HOME/Notes/scripts/cc_cost.py" --cache-statusbar &>/dev/null &
fi
# Build a [cw cr out] bracket from three USD values. Empty if any missing.
build_breakdown() {
local cw="$1" cr="$2" out="$3"
[ -z "$cw" ] || [ -z "$cr" ] || [ -z "$out" ] && return
local cw_fmt cr_fmt out_fmt
cw_fmt=$(fmt_usd "$cw")
cr_fmt=$(fmt_usd "$cr")
out_fmt=$(fmt_usd "$out")
printf " %b[%b%b%s%b %b%s%b %b%s%b%b]%b" \
"$C_DIM" "$RESET" "$C_CW" "$cw_fmt" "$RESET" \
"$C_CR" "$cr_fmt" "$RESET" \
"$C_OUT" "$out_fmt" "$RESET" "$C_DIM" "$RESET"
}
# Compact $ formatter: $X.XX (<10), $X (10-999), $X.Xk (>=1000)
fmt_usd() {
awk "BEGIN {
v = $1
if (v >= 1000) printf \"\$%.1fk\", v/1000
else if (v >= 10) printf \"\$%d\", v
else printf \"\$%.2f\", v
}"
}
# ── Cost & rate limits (from authoritative Claude Code JSON) ─────
# These come straight from Claude Code (no JSONL scanning needed):
# - cost.total_cost_usd : actual session cost
# - rate_limits.five_hour.* : official 5h cap % + reset epoch
# - rate_limits.seven_day.* : official weekly cap % + reset epoch
COST_BLOCK=""
SESSION_COST_FMT=$(awk "BEGIN {printf \"%.2f\", ${SESSION_COST_USD:-0}}")
# 5-hour rate limit block (only present for Pro/Max after first API response)
FIVE_H_BLOCK=""
if [ -n "$FIVE_H_PCT" ]; then
FIVE_H_INT=$(awk "BEGIN {printf \"%d\", ${FIVE_H_PCT:-0}}")
C_5H=$(ramp "$FIVE_H_INT" 25 50 75)
if [ -n "$FIVE_H_RESET" ]; then
NOW_EPOCH=$(date +%s)
REM_5H_S=$(( FIVE_H_RESET - NOW_EPOCH ))
[ "$REM_5H_S" -lt 0 ] && REM_5H_S=0
REM_5H_H=$(( REM_5H_S / 3600 ))
REM_5H_M=$(( (REM_5H_S % 3600) / 60 ))
if [ "$REM_5H_H" -gt 0 ]; then
REM_5H_FMT="${REM_5H_H}h${REM_5H_M}m"
else
REM_5H_FMT="${REM_5H_M}m"
fi
FIVE_H_BLOCK=" ${C_5H}${FIVE_H_INT}%${RESET}${C_DIM}/5h·${REM_5H_FMT}${RESET}"
else
FIVE_H_BLOCK=" ${C_5H}${FIVE_H_INT}%${RESET}${C_DIM}/5h${RESET}"
fi
if [ -n "$WINDOW_COST_USD" ]; then
WINDOW_FMT=$(fmt_usd "$WINDOW_COST_USD")
WINDOW_BREAKDOWN=$(build_breakdown "$WINDOW_CW_USD" "$WINDOW_CR_USD" "$WINDOW_OUT_USD")
FIVE_H_BLOCK="${FIVE_H_BLOCK}${C_DIM}·${RESET}${C_5H}${WINDOW_FMT}${RESET}${WINDOW_BREAKDOWN}"
fi
fi
# 7-day rate limit block
SEVEN_D_BLOCK=""
if [ -n "$SEVEN_D_PCT" ]; then
SEVEN_D_INT=$(awk "BEGIN {printf \"%d\", ${SEVEN_D_PCT:-0}}")
C_7D=$(ramp "$SEVEN_D_INT" 25 50 75)
if [ -n "$SEVEN_D_RESET" ]; then
NOW_EPOCH=$(date +%s)
REM_7D_S=$(( SEVEN_D_RESET - NOW_EPOCH ))
[ "$REM_7D_S" -lt 0 ] && REM_7D_S=0
REM_7D_D=$(( REM_7D_S / 86400 ))
REM_7D_H=$(( (REM_7D_S % 86400) / 3600 ))
if [ "$REM_7D_D" -gt 0 ]; then
REM_7D_FMT="${REM_7D_D}d${REM_7D_H}h"
else
REM_7D_FMT="${REM_7D_H}h"
fi
SEVEN_D_BLOCK=" ${C_7D}${SEVEN_D_INT}%${RESET}${C_DIM}/7d·${REM_7D_FMT}${RESET}"
else
SEVEN_D_BLOCK=" ${C_7D}${SEVEN_D_INT}%${RESET}${C_DIM}/7d${RESET}"
fi
if [ -n "$WEEKLY_COST_USD" ]; then
WEEKLY_FMT=$(fmt_usd "$WEEKLY_COST_USD")
WEEKLY_BREAKDOWN=$(build_breakdown "$WEEKLY_CW_USD" "$WEEKLY_CR_USD" "$WEEKLY_OUT_USD")
SEVEN_D_BLOCK="${SEVEN_D_BLOCK}${C_DIM}·${RESET}${C_7D}${WEEKLY_FMT}${RESET}${WEEKLY_BREAKDOWN}"
fi
fi
# Color session cost by absolute amount (rough thresholds)
C_SESS_COST="$GREEN"
SESS_INT=$(awk "BEGIN {printf \"%d\", ${SESSION_COST_USD:-0}}")
if [ "$SESS_INT" -ge 50 ]; then C_SESS_COST="$RED"
elif [ "$SESS_INT" -ge 20 ]; then C_SESS_COST="$ORANGE"
elif [ "$SESS_INT" -ge 10 ]; then C_SESS_COST="$YELLOW"
fi
# Per-bucket session breakdown [cw, cr, out] from cc_cost.py
# Scoped to the current Claude Code process invocation: filter by process
# start (now − DURATION_MS) so the bracket aligns with cost.total_cost_usd,
# which also resets on /compact and on session resume. ~50ms wall.
BREAKDOWN_BLOCK=""
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && awk "BEGIN {exit !(${SESSION_COST_USD:-0} >= 0.10)}"; then
NOW_EPOCH=$(date +%s)
PROC_START_EPOCH=$(( NOW_EPOCH - DURATION_MS / 1000 - 30 )) # 30s safety buffer
BREAK_OUT=$(python3 "$HOME/Notes/scripts/cc_cost.py" --session-breakdown "$TRANSCRIPT_PATH" "$PROC_START_EPOCH" 2>/dev/null)
if [ -n "$BREAK_OUT" ]; then
eval "$BREAK_OUT"
if awk "BEGIN {exit !(${TOTAL_USD:-0} > 0)}"; then
CW_FMT=$(awk "BEGIN {printf \"%.2f\", ${CW_USD}}")
CR_FMT=$(awk "BEGIN {printf \"%.2f\", ${CR_USD}}")
OUT_FMT=$(awk "BEGIN {printf \"%.2f\", ${OUT_USD}}")
BREAKDOWN_BLOCK=" ${C_DIM}[${RESET}${C_CW}\$${CW_FMT}${RESET} ${C_CR}\$${CR_FMT}${RESET} ${C_OUT}\$${OUT_FMT}${RESET}${C_DIM}]${RESET}"
fi
fi
fi
COST_BLOCK="${C_SESS_COST}\$${SESSION_COST_FMT}${RESET}${BREAKDOWN_BLOCK}${FIVE_H_BLOCK}${SEVEN_D_BLOCK}"
# ── Output (two lines) ────────────────────────────────────────────
# Line 1: identity + context + per-call tokens + session totals + lines
# Line 2: [daily progress] + cost + time
echo -ne "\
${C_DIR}${DIR_NAME}${RESET}$([ -n "$BRANCH" ] && echo " ${SEP} ${C_BR}${BRANCH}${RESET}") ${SEP} \
${CTX_BAR} ${CTX_COLOR}${CONTEXT_PCT}%${RESET} $(fmt "$TOTAL_INPUT_NOW")/${CTX_SIZE} \
${C_SYS}⛁${RESET}${SYS_TOK} \
${C_MSG}⛁${RESET}${MSG_TOK} \
${C_FREE}⛶${RESET}${FREE_TOK} \
${C_BUF}⛝${RESET}${BUF_TOK} ${SEP} \
${C_NEW}•${RESET}${NEW_TOK} \
${C_CW}✎${RESET}${CW_TOK} \
${C_CR}↺${RESET}${CR_TOK} \
${C_OUT}▶${RESET}${OUT_TOK} ${SEP} \
${C_SUM}Σ↓${RESET}${TOT_IN} ${C_SUM}↑${RESET}${TOT_OUT} ${SEP} \
${C_ADD}+${LINES_ADDED}${RESET}${C_DEL}/-${LINES_REMOVED}${RESET}\n\
${DAILY_PROGRESS:+${DAILY_PROGRESS} ${SEP} }${COST_BLOCK} ${SEP} ${C_TIME}${TIME}${RESET}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment