Nerd Font Duo
bash
Two Nerd Font lines: model, a 16-segment context bar, cost and quotas on top; directory, git counts, Python venv and vim mode below.
MIT licensed · original source
Preview
Clean repo
Opus 4.8 │ ▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱ 22% │ $0.41
app │ mainNew session
Opus 4.8 │ ▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 0% │ $0.00
app │ mainDirty branch
Sonnet 4.6 │ ▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱ 48% │ $0.41
app │ feat/auth ~1 ?1 │ INSERTNear-full
CTX 91% Opus 4.8 │ ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱ │ $4.12
app │ main │ VISUAL1M context
Fable 5 │ ▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱ 64% │ $0.41
app │ mainPost-compact
Haiku 4.5 │ ▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 0% │ $0.41
app │ main │ NORMALWorktree
Opus 4.8 │ ▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱ 37% │ $0.41
feature │ worktree-feature │ VISUAL LINENon-git
Opus 4.8 │ ▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱ 22% │ $0.41
scratchSource
#!/bin/bash
# Claude Code Status Line — Two-line layout with Nerd Font icons (MD range)
# Line 1: Model │ Context Bar (16 segs) │ Cost │ 5h usage │ 7d usage
# Line 2: Directory │ Git Branch & Status │ Venv │ Vim
input=$(< /dev/stdin)
now=$(date +%s)
# --- Extract all values in a single jq call ---
eval "$(jq -r '
@sh "model=\(.model.display_name // "?")",
@sh "cwd=\(.workspace.current_dir // ".")",
@sh "used_pct=\(.context_window.used_percentage // 0)",
@sh "cost=\(.cost.total_cost_usd // 0)",
@sh "vim_mode=\(.vim.mode // "")"
' <<< "$input")"
dir_name="${cwd##*/}"
# --- Theme detection ---
# Override with STATUSLINE_THEME=dark|light|auto (default: auto)
detect_theme() {
local theme="${STATUSLINE_THEME:-auto}"
if [ "$theme" != "auto" ]; then echo "$theme"; return; fi
# macOS system appearance
if defaults read -g AppleInterfaceStyle &>/dev/null; then
echo "dark"; return
fi
# COLORFGBG env var (e.g. "15;0" → bg=0 is dark)
if [ -n "$COLORFGBG" ]; then
local bg="${COLORFGBG##*;}"
if [ "$bg" -lt 8 ] 2>/dev/null; then
echo "dark"
else
echo "light"
fi
return
fi
echo "dark"
}
THEME=$(detect_theme)
# --- Colors ---
RST=$'\033[0m'
BOLD=$'\033[1m'
if [ "$THEME" = "light" ]; then
DIM=$'\033[90m' # bright black (gray) — visible on light bg
CYAN=$'\033[36m'
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
RED=$'\033[31m'
MAGENTA=$'\033[35m'
BLUE=$'\033[34m'
BG_RED=$'\033[41m'
WHITE_BOLD=$'\033[1;30m' # bold black — for alert badge text on light bg
else
DIM=$'\033[2m'
CYAN=$'\033[36m'
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
RED=$'\033[31m'
MAGENTA=$'\033[35m'
BLUE=$'\033[34m'
BG_RED=$'\033[41m'
WHITE_BOLD=$'\033[1;37m'
fi
# --- Nerd Font Icons (all MD range, U+F0000+) ---
ICON_MODEL="" # nf-md-robot
ICON_CTX="" # nf-md-memory
ICON_DIR="" # nf-md-folder_outline
ICON_GIT="" # nf-md-source_branch
ICON_COST="" # nf-md-cash
ICON_WARN="" # nf-fa-warning
ICON_VIM="" # nf-md-vim
ICON_VENV="" # nf-md-language_python
SEP="${DIM} │ ${RST}"
BAR_SEGMENTS=16
# --- Color helper for utilization percentage ---
# Thresholds: green < 50, yellow 50-79, red >= 80
pct_color() {
local pct=$1
if [ "$pct" -lt 50 ]; then echo "$GREEN"
elif [ "$pct" -lt 80 ]; then echo "$YELLOW"
else echo "$RED"
fi
}
# --- Context percentage ---
pct_int=${used_pct%.*}
pct_int=${pct_int:-0}
CTX_COLOR=$(pct_color "$pct_int")
# --- Progress bar ---
filled=$(( (pct_int * BAR_SEGMENTS + 50) / 100 ))
[ "$filled" -gt "$BAR_SEGMENTS" ] && filled=$BAR_SEGMENTS
empty=$((BAR_SEGMENTS - filled))
bar=""
for ((i = 0; i < filled; i++)); do bar="${bar}▰"; done
for ((i = 0; i < empty; i++)); do bar="${bar}▱"; done
# --- Cost ---
printf -v cost_str '$%.2f' "$cost"
# --- Git info (single git status --porcelain call) ---
git_info=""
if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then
branch=$(git -C "$cwd" branch --show-current 2>/dev/null)
[ -z "$branch" ] && branch="detached"
staged=0 unstaged=0 untracked=0
while IFS= read -r line; do
x=${line:0:1}
y=${line:1:1}
if [ "$x$y" = "??" ]; then
((untracked++))
else
[ "$x" != " " ] && [ "$x" != "?" ] && ((staged++))
[ "$y" != " " ] && ((unstaged++))
fi
done < <(git -C "$cwd" status --porcelain 2>/dev/null)
status=""
[ "$staged" -gt 0 ] && status="${status} ${GREEN}+${staged}${RST}"
[ "$unstaged" -gt 0 ] && status="${status} ${YELLOW}~${unstaged}${RST}"
[ "$untracked" -gt 0 ] && status="${status} ${DIM}?${untracked}${RST}"
git_info="${SEP}${MAGENTA}${ICON_GIT} ${branch}${RST}${status}"
fi
# --- Python venv detection ---
venv_str=""
venv_name="" py_ver=""
if [ -n "$VIRTUAL_ENV" ]; then
venv_name="${VIRTUAL_ENV##*/}"
else
for venv_dir in "$cwd/.venv" "$cwd/venv" "$cwd/.env"; do
if [ -f "${venv_dir}/bin/python" ]; then
venv_name="${venv_dir##*/}"
py_ver=$("${venv_dir}/bin/python" --version 2>/dev/null | awk '{print $2}' | cut -d. -f1-2)
break
fi
done
fi
if [ -n "$venv_name" ]; then
venv_str="${SEP}${YELLOW}${ICON_VENV} ${venv_name}${py_ver:+ ($py_ver)}${RST}"
fi
# --- Vim mode ---
vim_str=""
if [ -n "$vim_mode" ]; then
[[ "$vim_mode" == "NORMAL" ]] && vim_color=$BLUE || vim_color=$GREEN
vim_str="${SEP}${vim_color}${BOLD}${ICON_VIM} ${vim_mode}${RST}"
fi
# --- Anthropic OAuth usage API (cached, background refresh) ---
CACHE_DIR="$HOME/.claude/statusline-cache"
USAGE_CACHE="$CACHE_DIR/usage.dat"
LOCK_FILE="$CACHE_DIR/usage-update.lock"
CACHE_TTL=60
refresh_usage_cache() {
if ! mkdir "$LOCK_FILE" 2>/dev/null; then
lock_age=$(( now - $(stat -f%m "$LOCK_FILE" 2>/dev/null || echo 0) ))
[ "$lock_age" -gt 60 ] && rm -r "$LOCK_FILE" 2>/dev/null || return
mkdir "$LOCK_FILE" 2>/dev/null || return
fi
(
# Try macOS Keychain first, then fall back to credentials file
token=$(security find-generic-password -s "Claude Code-credentials" -a "$(whoami)" -w 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
[ -z "$token" ] && token=$(jq -r '.claudeAiOauth.accessToken // empty' "$HOME/.claude/.credentials.json" 2>/dev/null)
if [ -n "$token" ]; then
resp=$(curl -s --max-time 5 \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
https://api.anthropic.com/api/oauth/usage 2>/dev/null)
if echo "$resp" | jq -e '.five_hour' > /dev/null 2>&1; then
echo "$resp" | jq -r '
def to_epoch: split(".")[0] + "Z" | fromdateiso8601;
"\(.five_hour.utilization | floor) \(.five_hour.resets_at | to_epoch) \(.seven_day.utilization | floor) \(.seven_day.resets_at | to_epoch)"
' > "$USAGE_CACHE.tmp" 2>/dev/null && mv "$USAGE_CACHE.tmp" "$USAGE_CACHE"
fi
fi
rm -r "$LOCK_FILE" 2>/dev/null
) &
disown 2>/dev/null
}
format_countdown() {
local diff=$(( $1 - now ))
[ "$diff" -le 0 ] && echo "soon" && return
if [ "$diff" -ge 86400 ]; then
echo "$((diff / 86400))d$((diff % 86400 / 3600))h"
elif [ "$diff" -ge 3600 ]; then
echo "$((diff / 3600))h$((diff % 3600 / 60))m"
else
echo "$((diff / 60))m"
fi
}
usage_segment() {
local label=$1 pct=$2 reset_epoch=$3
local color reset_str
color=$(pct_color "$pct")
reset_str=$(format_countdown "$reset_epoch")
echo "${SEP}${DIM}${label}${RST} ${color}${pct}%${RST} ${DIM}↺ ${reset_str}${RST}"
}
if [ ! -f "$USAGE_CACHE" ]; then
[ -d "$CACHE_DIR" ] || mkdir -p "$CACHE_DIR" 2>/dev/null
refresh_usage_cache
else
cache_age=$(( now - $(stat -f%m "$USAGE_CACHE" 2>/dev/null || echo 0) ))
[ "$cache_age" -gt "$CACHE_TTL" ] && refresh_usage_cache
fi
usage_5h="" usage_7d=""
if [ -f "$USAGE_CACHE" ]; then
read -r pct5h epoch5h pct7d epoch7d < "$USAGE_CACHE" 2>/dev/null
if [ -n "$pct5h" ] && [ "$pct5h" != "-1" ]; then
usage_5h=$(usage_segment "5h" "$pct5h" "$epoch5h")
fi
if [ -n "$pct7d" ] && [ "$pct7d" != "-1" ]; then
usage_7d=$(usage_segment "7d" "$pct7d" "$epoch7d")
fi
fi
# --- Build output ---
line1_tail="${SEP}${DIM}${ICON_COST} ${cost_str}${RST}${usage_5h}${usage_7d}"
if [ "$pct_int" -ge 90 ]; then
line1="${BG_RED}${WHITE_BOLD} ${ICON_WARN} CTX ${pct_int}% ${RST} ${CYAN}${BOLD}${ICON_MODEL} ${model}${RST}${SEP}${CTX_COLOR}${bar}${RST}${line1_tail}"
else
line1="${CYAN}${BOLD}${ICON_MODEL} ${model}${RST}${SEP}${DIM}${ICON_CTX}${RST} ${CTX_COLOR}${bar} ${pct_int}%${RST}${line1_tail}"
fi
line2="${BLUE}${ICON_DIR} ${dir_name}${RST}${git_info}${venv_str}${vim_str}"
printf '%s\n%s' "$line1" "$line2"
What it shows
- Model display name with a robot icon
- Context window usage as a 16-segment progress bar plus a percentage
- A red warning badge with the context percentage when usage reaches 90% or more
- Session cost in US dollars, formatted to two decimal places
- 5-hour and 7-day usage percentages with a countdown to reset, when cached usage data from Anthropic's OAuth usage API is available
- Current directory name (last path segment only)
- Git branch name, or 'detached' when no branch is checked out
- Counts of staged, unstaged, and untracked files in the git repo
- Python virtual environment name, with the Python version when detected from a local .venv, venv, or .env folder
- Vim mode (e.g. NORMAL, INSERT) when Claude Code reports one
Requirements
- Bash
- jq for parsing the stdin JSON, credentials, and the usage API response
- git for branch and working-tree status
- curl for fetching usage data from api.anthropic.com
- A Nerd Font to render the Material Design icon glyphs
- macOS-flavored commands: stat -f%m for cache timestamps, plus defaults (theme detection) and security (Keychain token lookup)
- Network access to api.anthropic.com for the 5h/7d usage segments
- A Claude OAuth token, read from the macOS Keychain or from ~/.claude/.credentials.json
- awk and cut for extracting the Python version
Behavior notes
- When context usage reaches 90%, a red 'CTX 91%' badge replaces the memory icon and inline percentage, and the bar turns red
- Bar and percentage color follows thresholds: green below 50%, yellow from 50-79% (64% renders yellow), red at 80% or above
- When used_percentage is null (new session or just after /compact), the bar renders empty at 0%
- Git file counts only appear when the working tree is dirty; a clean repo shows just the branch name
- In a non-git directory the entire git segment disappears, leaving only the directory name on line two
- The vim segment appears only when a vim mode is reported; NORMAL renders blue, other modes render green
- The branch name comes from running git in the current directory, not from the input JSON
- The 5h/7d usage segments come from a cached API response rather than the rate_limits field on stdin, and did not appear in any rendered preview
- The venv segment did not appear in any preview; it shows only when a virtual environment is active or found in the directory
- Input fields like effort level, thinking, PR details, session name, agent name, and output style vary across scenarios but are not displayed
More status lines
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0hOpus 4.8 | main | ██░░░░░░░░ 22.0% | 44k / 200k | 02:07 26% | Wed 7%
Opus 4.8 │ main │ app │ ▓▓░░░░░░░░ 44k/200k │ $0.41
app ⏱ 10m ▲ +128 -34 ▓░░░░░░░ 22% $0.41 ◆ Opus
Opus 4.8 high ✦ │ ctx 22% │ app (main) │ PR #1287 ● │ $0.41 · 10m +128 -34
Session ● ● ● ● ● ● ● ● ○ ○ ○ 74% left Resets in 2h 7m
Weekly ● ● ● ● ● ● ● ● ● ● ○ 93% left Resets in 2d 1h
◐ Read:index.ts ✓ Read×1 ✓ Bash×1 ✓ Edit×1 ✓ Grep×1
◐ code-reviewer Review the change for regressions
◆ Opus 4.8 │ app/main
▰▰▰▰▱ 78% │ ↑44k ↓1.4k │ 5h: no token │ 7d: no token │ 10m12s
New to Claude Code status lines? Read the setup guide.