Rate Limit and Session Info
auditedFree5h/7d rate-limit countdowns with directory, git branch, model, context %, effort/style, cost, and lines changed.
This statusline displays project information by reading session JSON from stdin and formatting it for display. It shows: directory name, git branch/status, AI model name, context window usage percentage, effort level, rate limit countdowns (5-hour and 7-day windows with time-until-reset), session cost in USD, and lines added/removed. It reads git metadata from the current workspace directory and user settings from ~/.claude/settings.json. It executes common read-only git commands (symbolic-ref, rev-parse, diff) to check repository state. The script uses 24-bit color ANSI codes to create a visual gradient (green to red) based on usage percentages. No network connections, no writes, no background processes.
Audits are advisory. This script runs on your machine — read it before installing.
The script — read it before you run it
#!/bin/bash
# statusline-command.sh — Claude Code status line
# Reads Claude Code session JSON from stdin and outputs a styled status line.
# Inspired by the Starship prompt: directory | git branch | model | context %
set -euo pipefail
input=$(cat)
# --- Directory (basename only) ---
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""')
if [ -n "$cwd" ]; then
if [ "$cwd" = "$HOME" ]; then
dir="~"
else
dir=$(basename "$cwd")
fi
else
dir=""
fi
# --- Git branch & status (skip optional lock to avoid stalls) ---
git_info=""
if [ -n "$cwd" ] && [ -d "$cwd/.git" ] || git -C "$cwd" rev-parse --git-dir >/dev/null 2>&1; then
branch=$(git -C "$cwd" symbolic-ref --short HEAD 2>/dev/null || git -C "$cwd" rev-parse --short HEAD 2>/dev/null || "")
if [ -n "$branch" ]; then
# Truncate long branch names (Symphony branches blow out the bar)
if [ "${#branch}" -gt 24 ]; then
branch="${branch:0:23}…"
fi
# Quick dirty check (no index lock needed)
if git -C "$cwd" diff --quiet 2>/dev/null && git -C "$cwd" diff --cached --quiet 2>/dev/null; then
git_info="$branch"
else
git_info="$branch [!]"
fi
fi
fi
# --- Model (strip trailing parenthetical, e.g. "Opus 4.7 (1M context)" → "Opus 4.7") ---
model=$(echo "$input" | jq -r '.model.display_name // ""' | sed -E 's/ *\([^)]*\)$//')
# --- 24-bit gradient helper (green → yellow → orange → red across 0..100) ---
# Emits an ANSI escape that wraps the given text. Input is clamped 0..100.
# Piecewise-linear in RGB across four waypoints for a smooth, finely-resolved gradient.
color_by_pct() {
local pct="$1"
local text="$2"
awk -v p="$pct" -v t="$text" 'BEGIN {
if (p < 0) p = 0
if (p > 100) p = 100
if (p <= 25) {
f = p / 25
r = 0 + f * (180 - 0)
g = 200 + f * (220 - 200)
b = 0
} else if (p <= 50) {
f = (p - 25) / 25
r = 180 + f * (255 - 180)
g = 220 + f * (200 - 220)
b = 0
} else if (p <= 75) {
f = (p - 50) / 25
r = 255
g = 200 + f * (130 - 200)
b = 0
} else {
f = (p - 75) / 25
r = 255
g = 130 + f * ( 30 - 130)
b = 0 + f * ( 30 - 0)
}
printf "\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, t
}'
}
color_by_remaining_pct() {
local pct="$1"
local text="$2"
local used_equivalent
used_equivalent=$(awk -v p="$pct" 'BEGIN {
if (p < 0) p = 0
if (p > 100) p = 100
printf "%.6f", 100 - p
}')
color_by_pct "$used_equivalent" "$text"
}
remaining_from_used_pct() {
local pct="$1"
awk -v p="$pct" 'BEGIN {
if (p < 0) p = 0
if (p > 100) p = 100
printf "%.6f", 100 - p
}'
}
# --- Context usage (24-bit gradient) ---
used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
if [ -n "$used_pct" ]; then
ctx_raw=$(printf "ctx:%.0f%%" "$used_pct")
ctx=$(color_by_pct "$used_pct" "$ctx_raw")
else
ctx=""
fi
# --- Effort level + output style (display names) ---
effort_info=""
effort=$(jq -r '.effortLevel // empty' "$HOME/.claude/settings.json" 2>/dev/null)
case "$effort" in
low) effort_display="1/5" ;;
medium) effort_display="2/5" ;;
high) effort_display="3/5" ;;
xhigh) effort_display="4/5" ;;
max) effort_display="5/5" ;;
"") effort_display="" ;;
null) effort_display="" ;;
*) effort_display="$effort" ;;
esac
style=$(echo "$input" | jq -r '.output_style.name // empty')
effort_parts=()
[ -n "$effort_display" ] && effort_parts+=("$effort_display")
[ -n "$style" ] && [ "$style" != "default" ] && [ "$style" != "null" ] && effort_parts+=("$style")
if [ "${#effort_parts[@]}" -gt 0 ]; then
IFS='/' effort_info="${effort_parts[*]}"
fi
# --- Rate limits (Claude.ai 5h and 7d windows; each label shows time-until-reset) ---
# Rate-limit percentages show amount remaining, so the color scale is inverted:
# high remaining is green; low remaining is red.
rate_info=""
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
five_resets_at=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
week_resets_at=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
if [ -n "$five_pct" ] || [ -n "$week_pct" ]; then
rate_parts=()
if [ -n "$week_pct" ]; then
# Time remaining until the 7-day window resets. Show days+hours, or just hours
# under a day; fall back to "7d" if no reset timestamp is available.
week_label="7d"
if [ -n "$week_resets_at" ]; then
now_epoch=$(date +%s)
week_diff_secs=$(( week_resets_at - now_epoch ))
if [ "$week_diff_secs" -gt 0 ]; then
week_days=$(( week_diff_secs / 86400 ))
week_hours=$(( (week_diff_secs % 86400) / 3600 ))
week_mins=$(( (week_diff_secs % 3600) / 60 ))
if [ "$week_days" -gt 0 ]; then
week_label="${week_days}d${week_hours}h"
elif [ "$week_hours" -gt 0 ]; then
week_label="${week_hours}h${week_mins}m"
else
week_label="${week_mins}m"
fi
else
week_label="0m"
fi
fi
week_remaining_pct=$(remaining_from_used_pct "$week_pct")
week_raw=$(printf "%s:%.0f%%" "$week_label" "$week_remaining_pct")
week_seg=$(color_by_remaining_pct "$week_remaining_pct" "$week_raw")
rate_parts+=("$week_seg")
fi
if [ -n "$five_pct" ]; then
# Time remaining until the 5h window resets replaces the static "5h" label.
five_label="5h"
if [ -n "$five_resets_at" ]; then
now_epoch=$(date +%s)
diff_secs=$(( five_resets_at - now_epoch ))
if [ "$diff_secs" -gt 0 ]; then
diff_mins=$(( diff_secs / 60 ))
if [ "$diff_mins" -ge 60 ]; then
hours=$(( diff_mins / 60 ))
mins=$(( diff_mins % 60 ))
five_label="${hours}h${mins}m"
else
five_label="${diff_mins}m"
fi
else
five_label="0m"
fi
fi
five_remaining_pct=$(remaining_from_used_pct "$five_pct")
five_pct_raw=$(printf "%s:%.0f%%" "$five_label" "$five_remaining_pct")
five_seg=$(color_by_remaining_pct "$five_remaining_pct" "$five_pct_raw")
rate_parts+=("$five_seg")
fi
IFS='/' rate_info="${rate_parts[*]}"
fi
# --- Session cost ---
cost_info=""
cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // empty')
if [ -n "$cost_usd" ]; then
# Only show if greater than 0
is_nonzero=$(echo "$cost_usd" | awk '{print ($1 > 0) ? "1" : "0"}')
if [ "$is_nonzero" = "1" ]; then
cost_info=$(printf '$%.2f' "$cost_usd")
fi
fi
# --- Session lines diff (+added/-removed) ---
lines_info=""
lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // empty')
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // empty')
if [ -n "$lines_added" ] || [ -n "$lines_removed" ]; then
added=${lines_added:-0}
removed=${lines_removed:-0}
if [ "$added" != "0" ] || [ "$removed" != "0" ]; then
lines_info="+${added}/-${removed}"
fi
fi
# --- Assemble ---
parts_out=()
[ -n "$dir" ] && parts_out+=("$dir")
[ -n "$rate_info" ] && parts_out+=("$rate_info")
[ -n "$ctx" ] && parts_out+=("$ctx")
[ -n "$model" ] && parts_out+=("$model")
[ -n "$effort_info" ] && parts_out+=("$effort_info")
[ -n "$cost_info" ] && parts_out+=("$cost_info")
[ -n "$lines_info" ] && parts_out+=("$lines_info")
[ -n "$git_info" ] && parts_out+=("$git_info")
joined=""
for part in "${parts_out[@]}"; do
if [ -z "$joined" ]; then
joined="$part"
else
joined="$joined · $part"
fi
done
printf "%s" "$joined"
exit 0