Rate Limit and Session Info

auditedFree

5h/7d rate-limit countdowns with directory, git branch, model, context %, effort/style, cost, and lines changed.

by 0x_Osprey4 installsrate-limitcontextcostgit
Claude Code
>
my-project · 3d12h:38%/2h30m:65% · ctx:42% · Fable 5 · 3/5 · $0.12 · +23/-5
? for shortcuts⏵⏵ accept edits on
Security auditby claude-sonnet-4-5

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.

reads-homesubprocessesgit-metadata

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

Reviews

no reviews yet