Skip to content

Instantly share code, notes, and snippets.

@xnohat
Created January 29, 2026 17:50
Show Gist options
  • Select an option

  • Save xnohat/08149bdfd2144cc9c7acea585f654d77 to your computer and use it in GitHub Desktop.

Select an option

Save xnohat/08149bdfd2144cc9c7acea585f654d77 to your computer and use it in GitHub Desktop.
claude code statusline with full info
#!/bin/bash
# Custom Claude Code statusline for Bash
# Cross-platform support: macOS, Linux
# Theme: detailed | Features: directory, git, model, usage, session, tokens
#
# Context Window Calculation:
# - 100% = compaction threshold (not model limit)
# - Self-calibrates via PreCompact hook
# - Falls back to smart defaults based on window size
input=$(cat)
# Calibration file path (now in /tmp/ck/ namespace - fixes #178)
CALIBRATION_PATH="${TMPDIR:-/tmp}/ck/calibration.json"
# ---- time helpers ----
to_epoch() {
ts="$1"
if command -v gdate >/dev/null 2>&1; then gdate -d "$ts" +%s 2>/dev/null && return; fi
date -u -j -f "%Y-%m-%dT%H:%M:%S%z" "${ts/Z/+0000}" +%s 2>/dev/null && return
python3 - "$ts" <<'PY' 2>/dev/null
import sys, datetime
s=sys.argv[1].replace('Z','+00:00')
print(int(datetime.datetime.fromisoformat(s).timestamp()))
PY
}
fmt_time_hm() {
epoch="$1"
if date -r 0 +%s >/dev/null 2>&1; then date -r "$epoch" +"%H:%M"; else date -d "@$epoch" +"%H:%M"; fi
}
# ---- compact threshold calculation ----
# Get smart default compact threshold based on context window size
# Research-based defaults:
# - 200k window: ~80% (160k) - confirmed from GitHub issues
# - 500k window: ~60% (300k) - estimated
# - 1M window: ~33% (330k) - derived from user observations
get_default_compact_threshold() {
local context_size="$1"
# Known thresholds (autocompact buffer = 22.5% for 200k)
case "$context_size" in
200000) echo 155000; return ;; # 77.5% - confirmed via /context
1000000) echo 330000; return ;; # 33% - 1M beta window
esac
# Tiered defaults based on window size
if [ "$context_size" -ge 1000000 ] 2>/dev/null; then
echo $((context_size * 33 / 100))
else
# Default: ~77.5% for standard windows (200k confirmed)
echo $((context_size * 775 / 1000))
fi
}
# Read calibrated threshold from file if available
get_compact_threshold() {
local context_size="$1"
# Try to read calibration file
if [ -f "$CALIBRATION_PATH" ] && command -v jq >/dev/null 2>&1; then
local calibrated
calibrated=$(jq -r --arg key "$context_size" '.[$key].threshold // empty' "$CALIBRATION_PATH" 2>/dev/null)
if [ -n "$calibrated" ] && [ "$calibrated" -gt 0 ] 2>/dev/null; then
echo "$calibrated"
return
fi
fi
# Fall back to smart defaults
get_default_compact_threshold "$context_size"
}
# ---- progress bar ----
progress_bar() {
pct="${1:-0}"; width="${2:-12}"
[[ "$pct" =~ ^[0-9]+$ ]] || pct=0; ((pct<0))&&pct=0; ((pct>100))&&pct=100
filled=$(( pct * width / 100 )); empty=$(( width - filled ))
# β–° (U+25B0) filled, β–± (U+25B1) empty - smooth horizontal rectangles
for ((i=0; i<filled; i++)); do printf 'β–°'; done
for ((i=0; i<empty; i++)); do printf 'β–±'; done
}
# ---- severity emoji (no colors) ----
get_severity_emoji() {
local pct="$1"
if [ "$pct" -ge 90 ] 2>/dev/null; then
echo "πŸ”΄" # Critical
elif [ "$pct" -ge 70 ] 2>/dev/null; then
echo "🟑" # Warning
else
echo "🟒" # Healthy
fi
}
# git utilities
num_or_zero() { v="$1"; [[ "$v" =~ ^[0-9]+$ ]] && echo "$v" || echo 0; }
# ---- git (detect early for fallback mode) ----
git_branch=""
if git rev-parse --git-dir >/dev/null 2>&1; then
git_branch=$(git branch --show-current 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)
fi
# ---- basics ----
if command -v jq >/dev/null 2>&1; then
current_dir=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "unknown"' 2>/dev/null | sed "s|^$HOME|~|g")
model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"' 2>/dev/null)
model_version=$(echo "$input" | jq -r '.model.version // ""' 2>/dev/null)
else
# Fallback: Extract basic info without jq using grep/sed
current_dir=$(echo "$input" | grep -o '"current_dir"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:.*"\([^"]*\)".*/\1/' | sed "s|^$HOME|~|g")
[ -z "$current_dir" ] && current_dir=$(pwd | sed "s|^$HOME|~|g")
model_name=$(echo "$input" | grep -o '"display_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:.*"\([^"]*\)".*/\1/')
[ -z "$model_name" ] && model_name="Claude"
model_version=""
# Render minimal statusline without jq and exit
printf 'πŸ“ %s' "$current_dir"
if [ -n "$git_branch" ]; then
printf ' 🌿 %s' "$git_branch"
fi
printf ' πŸ€– %s' "$model_name"
printf '\n'
exit 0
fi
# ---- Native Claude Code data integration ----
session_txt=""
cost_usd=""; lines_added=0; lines_removed=0
context_pct=0; context_txt=""
BILLING_MODE="${CLAUDE_BILLING_MODE:-api}"
# Extract native cost data from Claude Code
cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // empty' 2>/dev/null)
lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // 0' 2>/dev/null)
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0' 2>/dev/null)
# Extract context window usage (Claude Code v2.0.65+)
# Calculate percentage against COMPACT THRESHOLD, not model limit
# 100% = compaction imminent
context_input=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0' 2>/dev/null)
context_output=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0' 2>/dev/null)
context_size=$(echo "$input" | jq -r '.context_window.context_window_size // 0' 2>/dev/null)
if [ -n "$context_size" ] && [ "$context_size" -gt 0 ] 2>/dev/null; then
context_total=$((context_input + context_output))
compact_threshold=$(get_compact_threshold "$context_size")
# Calculate percentage against compact threshold
context_pct=$((context_total * 100 / compact_threshold))
# Clamp to 100% max to handle edge cases
((context_pct > 100)) && context_pct=100
# Get severity emoji and progress bar
severity_emoji=$(get_severity_emoji "$context_pct")
bar=$(progress_bar "$context_pct" 12)
context_txt="${severity_emoji} ${bar} ${context_pct}%"
fi
# Session timer - parse local transcript JSONL (zero external dependencies)
transcript_path=$(echo "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
# Get first API call timestamp from session JSONL
first_api_call=$(grep -m1 '"usage"' "$transcript_path" 2>/dev/null | jq -r '.timestamp // empty' 2>/dev/null)
if [ -n "$first_api_call" ]; then
# Calculate 5-hour billing block (Anthropic windows)
# Blocks: 00:00-05:00, 05:00-10:00, 10:00-15:00, 15:00-20:00, 20:00-01:00 UTC
current_utc_hour=$(date -u +%H)
block_start=$((10#$current_utc_hour / 5 * 5))
block_end=$((block_start + 5))
# Handle day wraparound (e.g., 20:00 UTC block ends at 01:00 UTC next day)
if [ $block_end -ge 24 ]; then
block_end=$((block_end - 24))
block_end_date="tomorrow"
else
block_end_date="today"
fi
# Calculate remaining time until block reset
now_sec=$(date +%s)
block_end_sec=$(date -u -d "${block_end_date} ${block_end}:00 UTC" +%s 2>/dev/null)
if [ -n "$block_end_sec" ] && [ "$block_end_sec" -gt 0 ]; then
remaining=$((block_end_sec - now_sec))
if [ $remaining -gt 0 ] && [ $remaining -lt 18000 ]; then
rh=$((remaining / 3600))
rm=$(((remaining % 3600) / 60))
block_end_local=$(date -d "@${block_end_sec}" +"%H:%M" 2>/dev/null)
session_txt="${rh}h ${rm}m until reset at ${block_end_local}"
fi
fi
fi
fi
# ---- render statusline (no ANSI colors - emoji only) ----
printf 'πŸ“ %s' "$current_dir"
# git display
if [ -n "$git_branch" ]; then
printf ' 🌿 %s' "$git_branch"
fi
printf ' πŸ€– %s' "$model_name"
if [ -n "$model_version" ] && [ "$model_version" != "null" ]; then
printf ' %s' "$model_version"
fi
# session time
if [ -n "$session_txt" ]; then
printf ' βŒ› %s' "$session_txt"
fi
# cost (only show for API billing mode)
if [ "$BILLING_MODE" = "api" ] && [ -n "$cost_usd" ] && [[ "$cost_usd" =~ ^[0-9.]+$ ]]; then
printf ' πŸ’΅ $%.4f' "$cost_usd"
fi
# lines changed
if [ -n "$lines_added" ] && [ -n "$lines_removed" ] && [[ "$lines_added" =~ ^[0-9]+$ ]] && [[ "$lines_removed" =~ ^[0-9]+$ ]]; then
if [ "$lines_added" -gt 0 ] || [ "$lines_removed" -gt 0 ]; then
printf ' πŸ“ +%d -%d' "$lines_added" "$lines_removed"
fi
fi
# context window usage (Claude Code v2.0.65+)
if [ -n "$context_txt" ]; then
printf ' %s' "$context_txt"
fi
# trailing newline (POSIX compliance)
printf '\n'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment