Created
January 29, 2026 17:50
-
-
Save xnohat/08149bdfd2144cc9c7acea585f654d77 to your computer and use it in GitHub Desktop.
claude code statusline with full info
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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