Skip to content

Instantly share code, notes, and snippets.

@lucasmodrich
Last active May 30, 2026 04:23
Show Gist options
  • Select an option

  • Save lucasmodrich/dc35acae391da921974b35469d77ddaf to your computer and use it in GitHub Desktop.

Select an option

Save lucasmodrich/dc35acae391da921974b35469d77ddaf to your computer and use it in GitHub Desktop.
Claude Code custom two-line status line — context, plan usage, git, and session cost

Claude Code — Custom Status Line

A two-line status line for Claude Code that surfaces the information most useful during an active session: working directory, git state, model, context window usage, plan usage limits, and session cost.

📂 …/Dev/my-app | 🌿 main | ★ claude-sonnet-4-6
 Context [████░░░░░░] 42%  5h [███░░░░░░░] 35%  7d [██░░░░░░░░] 28%  💰 $0.87

What it shows

Line 1

  • Working directory — last two path components, $HOME collapsed to ~
  • Git branch and count of tracked-but-modified files (untracked files excluded)
  • Active model name

Line 2

  • Context window usage bar — green < 50%, yellow 50–70%, red ≥ 70%
  • 5-hour plan usage bar — green < 65%, yellow 65–85%, red ≥ 85%
  • 7-day plan usage bar — same thresholds; whichever of 5h/7d is higher has its label highlighted in amber as the binding constraint
  • Session cost in USD (hidden when zero)

Plan usage is fetched from the Claude API once per 60 seconds and cached at ~/.cache/claude_usage_cache.json. It requires an active Claude.ai OAuth session (~/.claude/.credentials.json). If the token is absent or the request fails, the 5h/7d bars are simply omitted.

Requirements

  • bash 4+
  • jq
  • curl
  • git (for branch display)

Installation

  1. Save claude_statusline.sh somewhere on your system and make it executable:

    mkdir -p ~/.claude
    cp claude_statusline.sh ~/.claude/statusline.sh
    chmod +x ~/.claude/statusline.sh
  2. Add the statusCommand key to ~/.claude/settings.json:

    {
      "statusLine": {
         "type": "command",
         "command": "bash ~/.claude/statusline.sh"
    }

    Use an absolute path. Claude Code pipes a JSON object to the script on stdin at the start of each turn and displays whatever the script writes to stdout.

  3. Start a new Claude Code session — the status line appears immediately below the input prompt.

# MIT License
#
# Copyright (c) 2026 Lucas Modrich
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#!/usr/bin/env bash
# Claude Code status line
input=$(cat)
# Extract all fields from stdin JSON — single jq call, one value per line via mapfile
# (avoids the @tsv tab-collapsing bug when fields are empty)
mapfile -t _f < <(jq -r '
.workspace.current_dir // .cwd,
(.model.display_name // ""),
(.context_window.used_percentage // ""),
(.cost.total_cost_usd // "")
' <<< "$input")
cwd="${_f[0]}" model="${_f[1]}" used_pct="${_f[2]}" cost_usd="${_f[3]}"
# Plan usage — cached at ~/.cache/claude_usage_cache.json for 60s
# Token is only read from disk on a cache miss
five_h_pct="" seven_d_pct=""
usage_cache="$HOME/.cache/claude_usage_cache.json"
if [ -f "$usage_cache" ] && [ $(( $(date +%s) - $(stat -c %Y "$usage_cache") )) -lt 60 ]; then
usage_json=$(cat "$usage_cache")
else
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$HOME/.claude/.credentials.json" 2>/dev/null)
if [ -n "$token" ]; then
usage_json=$(curl -sf --max-time 3 \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "Content-Type: application/json" \
'https://api.anthropic.com/api/oauth/usage' 2>/dev/null)
[ -n "$usage_json" ] && { mkdir -p "$HOME/.cache"; echo "$usage_json" > "$usage_cache"; }
fi
fi
if [ -n "$usage_json" ]; then
mapfile -t _u < <(jq -r '
(.five_hour.utilization // ""),
(.seven_day.utilization // "")
' <<< "$usage_json")
five_h_pct="${_u[0]}" seven_d_pct="${_u[1]}"
fi
# Directory display: collapse $HOME to ~ then show last 2 path components
cwd_display="${cwd/#$HOME/\~}"
dir_display=$(awk -F'/' '{
n = NF
if (n <= 2) print $0
else print "…/" $(n-1) "/" $n
}' <<< "$cwd_display")
# Git branch and uncommitted count — single git call, single awk pass on in-memory output
git_branch="" git_uncommitted=0
if git_out=$(git -C "$cwd" -c gc.auto=0 status --branch --porcelain=v2 2>/dev/null); then
read -r git_branch git_uncommitted < <(awk '
/^# branch\.head / { branch = $3 }
/^[^#?!]/ { count++ }
END { print (branch ? branch : ""), count+0 }
' <<< "$git_out")
fi
# Usage bar renderer — binding=1 renders the label in amber (higher utilization wins)
render_usage_bar() {
local pct=$1 label=$2 binding=${3:-0} lo=${4:-65} hi=${5:-85}
local filled empty bar="" color label_fmt i
pct=$(printf '%.0f' "$pct")
filled=$(( pct * 10 / 100 )); empty=$(( 10 - filled ))
for ((i=0; i<filled; i++)); do bar="${bar}█"; done
for ((i=0; i<empty; i++)); do bar="${bar}░"; done
if [ "$pct" -lt "$lo" ]; then color="\033[32m"
elif [ "$pct" -lt "$hi" ]; then color="\033[33m"
else color="\033[31m"
fi
if [ "$binding" -eq 1 ]; then
label_fmt="\033[33m%s\033[0m"
else
label_fmt="\033[2;37m%s\033[0m"
fi
printf " ${label_fmt} ${color}[%s] %d%%\033[0m" "$label" "$bar" "$pct"
}
# ── Line 1 ──────────────────────────────────────────────────────────────────
printf "\033[1;36m📂 %s\033[0m" "$dir_display"
printf " \033[2;37m|\033[0m"
if [ -n "$git_branch" ]; then
if [ "$git_uncommitted" -gt 0 ]; then
printf " \033[1;36m🌿 %s (%d)\033[0m" "$git_branch" "$git_uncommitted"
else
printf " \033[1;36m🌿 %s\033[0m" "$git_branch"
fi
printf " \033[2;37m|\033[0m"
fi
[ -n "$model" ] && printf " \033[2;37m★ %s\033[0m" "$model"
# ── Line 2 ──────────────────────────────────────────────────────────────────
printf "\n"
render_usage_bar "${used_pct:-0}" "Context" 0 50 70
if [ -n "$five_h_pct" ] && [ -n "$seven_d_pct" ]; then
if awk -v a="$five_h_pct" -v b="$seven_d_pct" 'BEGIN { exit (a >= b) ? 0 : 1 }'; then
render_usage_bar "$five_h_pct" "5h" 1
render_usage_bar "$seven_d_pct" "7d" 0
else
render_usage_bar "$five_h_pct" "5h" 0
render_usage_bar "$seven_d_pct" "7d" 1
fi
elif [ -n "$five_h_pct" ]; then render_usage_bar "$five_h_pct" "5h" 0
elif [ -n "$seven_d_pct" ]; then render_usage_bar "$seven_d_pct" "7d" 0
fi
# Cost: single awk call handles both the >0 guard and formatting
if [ -n "$cost_usd" ]; then
cost_fmt=$(awk -v v="$cost_usd" 'BEGIN { if (v+0 > 0) { printf "%.2f", v+0; exit 0 } exit 1 }')
[ -n "$cost_fmt" ] && printf " \033[2;37m💰 \033[0m\033[37m\$%s\033[0m" "$cost_fmt"
fi
printf "\n"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment