Skip to content

Instantly share code, notes, and snippets.

@CJHwong
Last active June 18, 2026 08:23
Show Gist options
  • Select an option

  • Save CJHwong/a89c883c8b8b7c28fad9d5cb7a62153c to your computer and use it in GitHub Desktop.

Select an option

Save CJHwong/a89c883c8b8b7c28fad9d5cb7a62153c to your computer and use it in GitHub Desktop.
Claude Code rich status line — rate limits, context, tokens, cost, uptime

Claude Code Status Line

A rich status line for Claude Code that shows everything you need at a glance.

Claude Sonnet 4 high │ 5h:6%→1h46m │ 7d:26%→03/24 │ ctx:3% ↑3.7k ↓8.4k │ $0.72 │ ⏱11m api:3m │ +156/-23 │ myproject:main

When extended thinking is off, the effort level is hidden:

Claude Sonnet 4 │ 5h:6%→1h46m │ 7d:26%→03/24 │ ctx:3% ↑3.7k ↓8.4k │ $0.72 │ ⏱11m api:3m │ +156/-23 │ myproject:main

What it shows (left to right)

Section Example Description
Model Claude Sonnet 4 Full model display name
Effort high Reasoning effort level — only shown when extended thinking is enabled (low/medium/high/xhigh/max)
5h rate limit 5h:6%→1h46m Usage % + countdown to reset
7d rate limit 7d:26%→03/24 Usage % + reset date
Context ctx:3% ↑3.7k ↓4.5k Context window usage, input/output tokens
Cost $0.72 Session API cost
Time ⏱11m api:3m Wall clock time + API wait time
Lines changed +156/-23 Lines added / removed this session
Project myproject:main Project name + git branch (or repo[wt:name] in a worktree)

Color coding

  • Green — under 50% (you're fine)
  • Yellow — 50-79% (heads up)
  • Red — 80%+ (watch out)
  • Bold redctx only, when exceeds_200k_tokens is true (you're past the native 200k window)

Session persistence

Cost, duration, token, line-change, and ctx counters are cached per session_id at ~/.claude/_statusline/<id>.json and max-merged with incoming values on every render. Resuming a session no longer resets the display to zero.

Rate limits are cached globally at ~/.claude/_statusline/_rate_limits.json (account-wide, not session-scoped). Incoming values always win; the cache is used as a fallback and is dropped once its resets_at passes. Cache files are never pruned — delete the directory any time to reset.

Install

curl -fsSL https://gist.githubusercontent.com/CJHwong/a89c883c8b8b7c28fad9d5cb7a62153c/raw/install.sh | bash

Manual install

  1. Save statusline-command.sh to ~/.claude/statusline-command.sh
  2. chmod +x ~/.claude/statusline-command.sh
  3. Add to ~/.claude/settings.json:
{
  "statusLine": {
    "type": "command",
    "command": "bash ~/.claude/statusline-command.sh"
  }
}
  1. Restart Claude Code

Requirements

  • jqbrew install jq (macOS) or apt install jq (Linux)
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_URL="https://gist.githubusercontent.com/CJHwong/a89c883c8b8b7c28fad9d5cb7a62153c/raw/statusline-command.sh"
DEST="$HOME/.claude/statusline-command.sh"
SETTINGS="$HOME/.claude/settings.json"
# Check dependencies
if ! command -v jq &>/dev/null; then
echo "Error: jq is required. Install with: brew install jq (macOS) or apt install jq (Linux)"
exit 1
fi
# Create ~/.claude if needed
mkdir -p "$HOME/.claude"
# Download statusline script
echo "Downloading statusline script..."
curl -fsSL "$SCRIPT_URL" -o "$DEST"
chmod +x "$DEST"
echo "Installed: $DEST"
# Configure settings.json
if [ -f "$SETTINGS" ]; then
if jq -e '.statusLine' "$SETTINGS" &>/dev/null; then
echo "statusLine already configured in $SETTINGS — skipping."
else
tmp=$(mktemp)
jq '. + {"statusLine": {"type": "command", "command": "bash '"$DEST"'"}}' "$SETTINGS" > "$tmp"
mv "$tmp" "$SETTINGS"
echo "Updated: $SETTINGS"
fi
else
cat > "$SETTINGS" <<EOF
{
"statusLine": {
"type": "command",
"command": "bash $DEST"
}
}
EOF
echo "Created: $SETTINGS"
fi
echo "Done! Restart Claude Code to see the status line."
#!/usr/bin/env bash
input=$(cat)
model=$(echo "$input" | jq -r '.model.display_name // "?"')
effort=$(echo "$input" | jq -r '.effort.level // empty')
thinking=$(echo "$input" | jq -r '.thinking.enabled // false')
project=$(basename "$(echo "$input" | jq -r '.workspace.project_dir // .cwd')")
branch=$(git --git-dir="$(echo "$input" | jq -r '.workspace.project_dir // .cwd')/.git" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null)
cache_dir="$HOME/.claude/_statusline"
mkdir -p "$cache_dir"
# Rate limits: account-wide, cached globally. Prefer incoming; fall back to
# cached only while its resets_at is still in the future.
rate_cache="$cache_dir/_rate_limits.json"
rate_current=$(echo "$input" | jq '{
h5: .rate_limits.five_hour.used_percentage,
h5_reset: .rate_limits.five_hour.resets_at,
d7: .rate_limits.seven_day.used_percentage,
d7_reset: .rate_limits.seven_day.resets_at
}')
now_epoch=$(date +%s)
if [ -f "$rate_cache" ]; then
rate_merged=$(jq -n --argjson a "$rate_current" --slurpfile b "$rate_cache" --argjson now "$now_epoch" '
($b[0]) as $c |
(if (($c.h5_reset // 0) > $now) then $c else $c + {h5: null, h5_reset: null} end) as $c |
(if (($c.d7_reset // 0) > $now) then $c else $c + {d7: null, d7_reset: null} end) as $c |
{
h5: ($a.h5 // $c.h5),
h5_reset: ($a.h5_reset // $c.h5_reset),
d7: ($a.d7 // $c.d7),
d7_reset: ($a.d7_reset // $c.d7_reset)
}')
else
rate_merged="$rate_current"
fi
printf '%s' "$rate_merged" > "$rate_cache.tmp" && mv "$rate_cache.tmp" "$rate_cache"
h5=$(echo "$rate_merged" | jq -r '.h5 // "–" | if type == "number" then round | tostring else . end')
d7=$(echo "$rate_merged" | jq -r '.d7 // "–" | if type == "number" then round | tostring else . end')
h5_reset=$(echo "$rate_merged" | jq -r '.h5_reset // empty')
d7_reset=$(echo "$rate_merged" | jq -r '.d7_reset // empty')
# Cumulative session fields (cost/duration/lines) are merged with a per-session
# cache via max so they survive a transient 0 at resume. Context-window state
# (ctx/in_tok/out_tok/exceeds_200k) is NOT cumulative — it drops after /compact —
# so those are taken from the live payload, never the stale cached peak.
session_id=$(echo "$input" | jq -r '.session_id // empty')
cache_file=""
current=$(echo "$input" | jq '{
cost: (.cost.total_cost_usd // 0),
dur: (.cost.total_duration_ms // 0),
api_dur: (.cost.total_api_duration_ms // 0),
lines_add: (.cost.total_lines_added // 0),
lines_del: (.cost.total_lines_removed // 0),
in_tok: (.context_window.total_input_tokens // 0),
out_tok: (.context_window.total_output_tokens // 0),
ctx: (.context_window.used_percentage // 0),
exceeds_200k: (.exceeds_200k_tokens // false)
}')
if [ -n "$session_id" ]; then
cache_file="$cache_dir/${session_id}.json"
fi
if [ -n "$cache_file" ] && [ -f "$cache_file" ]; then
merged=$(jq -n --argjson a "$current" --slurpfile b "$cache_file" '
($b[0]) as $c |
{
cost: ([$a.cost, $c.cost] | max),
dur: ([$a.dur, $c.dur] | max),
api_dur: ([$a.api_dur, $c.api_dur] | max),
lines_add: ([$a.lines_add, $c.lines_add] | max),
lines_del: ([$a.lines_del, $c.lines_del] | max),
in_tok: $a.in_tok,
out_tok: $a.out_tok,
ctx: $a.ctx,
exceeds_200k: $a.exceeds_200k
}')
else
merged="$current"
fi
if [ -n "$cache_file" ]; then
printf '%s' "$merged" > "$cache_file.tmp" && mv "$cache_file.tmp" "$cache_file"
fi
cost=$(echo "$merged" | jq -r '.cost | . * 100 | round | . / 100')
ctx=$(echo "$merged" | jq -r '.ctx | round')
exceeds_200k=$(echo "$merged" | jq -r '.exceeds_200k')
lines_add=$(echo "$merged" | jq -r '.lines_add')
lines_del=$(echo "$merged" | jq -r '.lines_del')
dur=$(echo "$merged" | jq -r '.dur')
api_dur=$(echo "$merged" | jq -r '.api_dur')
in_tok=$(echo "$merged" | jq -r '.in_tok')
out_tok=$(echo "$merged" | jq -r '.out_tok')
fmt_duration() {
local ms="$1" total_mins days hrs mins
total_mins=$(( ms / 60000 ))
days=$(( total_mins / 1440 ))
hrs=$(( (total_mins % 1440) / 60 ))
mins=$(( total_mins % 60 ))
if [ "$days" -gt 0 ]; then echo "${days}d${hrs}h${mins}m"
elif [ "$hrs" -gt 0 ]; then echo "${hrs}h${mins}m"
else echo "${mins}m"; fi
}
fmt_tokens() {
local val="$1"
if [ "$val" -ge 1000000 ]; then
printf "%.1fM" "$(echo "$val" | awk '{printf "%.1f", $1/1000000}')"
elif [ "$val" -ge 1000 ]; then
printf "%.1fk" "$(echo "$val" | awk '{printf "%.1f", $1/1000}')"
else
printf "%s" "$val"
fi
}
elapsed=$(fmt_duration "$dur")
api_elapsed=$(fmt_duration "$api_dur")
# Colors
G='\033[32m' Y='\033[33m' R='\033[31m' BR='\033[1;31m' # pct thresholds
X='\033[0m' # reset
CYAN='\033[36m' # model
MAG='\033[35m' # repo/branch
BLUE='\033[34m' # uptime
YEL='\033[33m' # cost
D='\033[90m' # labels/separators
fmt_reset_countdown() {
local epoch="$1"
if [ -z "$epoch" ]; then printf "${D}–${X}"; return; fi
local now diff h m
now=$(date +%s)
diff=$(( epoch - now ))
if [ "$diff" -le 0 ]; then printf "${G}now${X}"; return; fi
h=$(( diff / 3600 ))
m=$(( (diff % 3600) / 60 ))
if [ "$h" -gt 0 ]; then printf "${D}%dh%dm${X}" "$h" "$m"
else printf "${D}%dm${X}" "$m"; fi
}
fmt_reset_date() {
local epoch="$1"
if [ -z "$epoch" ]; then printf "${D}–${X}"; return; fi
if [ "$epoch" -le "$(date +%s)" ] 2>/dev/null; then printf "${G}now${X}"; return; fi
printf "${D}%s${X}" "$(date -r "$epoch" '+%a %H:%M')"
}
color_pct() {
local val="$1"
[ "$val" = "–" ] && printf "${D}–%%${X}" && return
if [ "$val" -ge 80 ]; then printf "${R}%s%%${X}" "$val"
elif [ "$val" -ge 50 ]; then printf "${Y}%s%%${X}" "$val"
else printf "${G}%s%%${X}" "$val"; fi
}
color_ctx() {
local val="$1" over="$2"
[ "$over" = "true" ] && printf "${BR}%s%%${X}" "$val" && return
color_pct "$val"
}
wt_name=$(echo "$input" | jq -r '.worktree.name // empty')
wt_branch=$(echo "$input" | jq -r '.worktree.branch // empty')
if [ -n "$wt_name" ]; then
# In worktree mode, resolve actual repo name from git's commondir
proj_dir=$(echo "$input" | jq -r '.workspace.project_dir // .cwd')
orig=$(basename "$(dirname "$(git -C "$proj_dir" rev-parse --git-common-dir 2>/dev/null)")")
repo="${orig:-$project}[wt:${wt_name}]"
else
repo="${project}"
[ -n "$branch" ] && repo="${project}:${branch}"
fi
model_display="${CYAN}${model}${X}"
if [ "$thinking" = "true" ] && [ -n "$effort" ]; then
model_display="${CYAN}${model}${X} ${Y}${effort}${X}"
fi
printf "%b ${D}│${X} 5h:%b${D}→${X}%b ${D}│${X} 7d:%b${D}→${X}%b ${D}│${X} ctx:%b ${D}↑${X}%s ${D}↓${X}%s ${D}│${X} ${YEL}\$%s${X} ${D}│${X} ${BLUE}up:%s api:%s${X} ${D}│${X} ${G}+%s${X}${D}/${X}${R}-%s${X} ${D}│${X} ${MAG}%s${X}" \
"$model_display" \
"$(color_pct "$h5")" "$(fmt_reset_countdown "$h5_reset")" \
"$(color_pct "$d7")" "$(fmt_reset_date "$d7_reset")" \
"$(color_ctx "$ctx" "$exceeds_200k")" "$(fmt_tokens "$in_tok")" "$(fmt_tokens "$out_tok")" \
"$cost" "$elapsed" "$api_elapsed" \
"$lines_add" "$lines_del" \
"$repo"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment