Created
November 18, 2025 14:15
-
-
Save srigi/fc4cd8b7776c6cd7051afdc599a790c7 to your computer and use it in GitHub Desktop.
Claude Code status-line script
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
| Screenshot: https://i.ibb.co/NdNZhY3X/Screenshot-2025-11-18-at-15-04-18.png |
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 | |
| # Claude Code status-line - shows context usage with dynamic progress bar | |
| # dependencies: jq | |
| set -euo pipefail | |
| # ============================================================================ | |
| # CONSTANTS | |
| # ============================================================================ | |
| readonly CONTEXT_LIMIT=200000 # 200k tokens for basic subscription | |
| # Color codes | |
| readonly COLOR_RESET="\033[0m" | |
| readonly COLOR_TEXT="\033[38;5;250m" # Light gray for text | |
| # Model colors | |
| readonly COLOR_SONNET="\033[38;5;114m" # Green | |
| readonly COLOR_OPUS="\033[38;5;215m" # Orange | |
| readonly COLOR_OTHER="\033[38;5;75m" # Blue | |
| # Progress bar colors | |
| readonly COLOR_BAR_0_20="\033[38;5;255m" # White | |
| readonly COLOR_BAR_20_40="\033[38;5;153m" # Light blue/cyan | |
| readonly COLOR_BAR_40_60="\033[38;5;117m" # Sky blue | |
| readonly COLOR_BAR_60_80="\033[38;5;214m" # Orange | |
| readonly COLOR_BAR_80_100="\033[38;5;196m" # Red | |
| # Empty block colors (by usage percentage) | |
| readonly COLOR_EMPTY_0_20="\033[38;5;242m" | |
| readonly COLOR_EMPTY_20_40="\033[38;5;244m" | |
| readonly COLOR_EMPTY_40_60="\033[38;5;246m" | |
| readonly COLOR_EMPTY_60_80="\033[38;5;250m" | |
| readonly COLOR_EMPTY_80_100="\033[38;5;254m" | |
| # Progress bar sizing | |
| readonly BAR_MIN_LENGTH=40 | |
| readonly BAR_MAX_LENGTH=120 | |
| # ============================================================================ | |
| # HELPER FUNCTIONS | |
| # ============================================================================ | |
| parse_json_input() { | |
| local input="$1" | |
| # extract all needed fields | |
| echo "$input" | jq -r ' | |
| (.model.display_name // "Claude"), | |
| (.model.id // ""), | |
| (.transcript_path // ""), | |
| (.session_id // ""), | |
| (if .exceeds_200k_tokens == null then "unknown" else (.exceeds_200k_tokens | tostring) end), | |
| (.cost.total_cost_usd // 0 | tostring) | |
| ' 2>/dev/null || { | |
| # default outputs on error | |
| echo "Claude" | |
| echo "" | |
| echo "" | |
| echo "" | |
| echo "unknown" | |
| echo "0" | |
| } | |
| } | |
| get_model_color() { | |
| local model_name="$1" | |
| if [[ "$model_name" == *"Sonnet"* ]]; then | |
| echo -n "$COLOR_SONNET" | |
| elif [[ "$model_name" == *"Opus"* ]]; then | |
| echo -n "$COLOR_OPUS" | |
| else | |
| echo -n "$COLOR_OTHER" | |
| fi | |
| } | |
| get_terminal_width() { | |
| if [[ -n "${COLUMNS:-}" ]]; then | |
| echo "$COLUMNS" | |
| else | |
| tput cols 2>/dev/null || echo 160 # fallback | |
| fi | |
| } | |
| get_display_length() { | |
| local str="$1" | |
| local len | |
| # strip ANSI codes | |
| len=$(echo -e "$str" | sed 's/\x1b\[[0-9;]*m//g' | wc -c | tr -d ' ') | |
| echo $((len - 1)) # Account for newline | |
| } | |
| # Get progress bar colors based on percentage | |
| get_bar_colors() { | |
| local pct="$1" | |
| if [[ $pct -lt 20 ]]; then | |
| echo "$COLOR_BAR_0_20 $COLOR_EMPTY_0_20" | |
| elif [[ $pct -lt 40 ]]; then | |
| echo "$COLOR_BAR_20_40 $COLOR_EMPTY_20_40" | |
| elif [[ $pct -lt 60 ]]; then | |
| echo "$COLOR_BAR_40_60 $COLOR_EMPTY_40_60" | |
| elif [[ $pct -lt 80 ]]; then | |
| echo "$COLOR_BAR_60_80 $COLOR_EMPTY_60_80" | |
| else | |
| echo "$COLOR_BAR_80_100 $COLOR_EMPTY_80_100" | |
| fi | |
| } | |
| build_statusline() { | |
| local left="$1" | |
| local right="$2" | |
| local term_width="$3" | |
| local left_len right_len padding pad_string | |
| left_len=$(get_display_length "$left") | |
| right_len=$(get_display_length "$right") | |
| padding=$((term_width - left_len - right_len)) | |
| if [[ $padding -lt 1 ]]; then padding=1; fi | |
| pad_string=$(printf "%*s" $padding "") | |
| echo -e "${left}${pad_string}${right}" | |
| } | |
| # ============================================================================ | |
| # MAIN CONTEXT CALCULATION | |
| # ============================================================================ | |
| calculate_context() { | |
| # Read JSON input from stdin | |
| local input | |
| input=$(cat) | |
| # Parse JSON once to extract all fields | |
| local json_output model_name model_id transcript_path session_id exceeds_200k total_cost | |
| json_output=$(parse_json_input "$input") | |
| model_name=$(echo "$json_output" | sed -n '1p') | |
| model_id=$(echo "$json_output" | sed -n '2p') | |
| transcript_path=$(echo "$json_output" | sed -n '3p') | |
| session_id=$(echo "$json_output" | sed -n '4p') | |
| exceeds_200k=$(echo "$json_output" | sed -n '5p') | |
| total_cost=$(echo "$json_output" | sed -n '6p') | |
| local term_width | |
| term_width=$(get_terminal_width) | |
| local model_color | |
| model_color=$(get_model_color "$model_name") | |
| if [[ ! -f "$transcript_path" ]]; then | |
| # No transcript - show N/A | |
| local left_side="${model_color}${model_name}${COLOR_RESET} " | |
| local right_side="${COLOR_TEXT}context size N/A${COLOR_RESET}" | |
| build_statusline "$left_side" "$right_side" "$term_width" | |
| return | |
| fi | |
| # Parse transcript to get token usage | |
| local total_tokens | |
| # Use bash loop + jq to parse JSONL and find most recent usage | |
| local last_record="" | |
| while IFS= read -r line; do | |
| # Check if line has usage data and is not sidechain | |
| if echo "$line" | jq -e '.isSidechain != true and .message.usage' >/dev/null 2>&1; then | |
| last_record="$line" | |
| fi | |
| done < "$transcript_path" | |
| if [[ -n "$last_record" ]]; then | |
| # Extract usage data and calculate context length | |
| total_tokens=$(echo "$last_record" | jq ' | |
| .message.usage | | |
| (.input_tokens // 0) + | |
| (.cache_read_input_tokens // 0) + | |
| (.cache_creation_input_tokens // 0) | |
| ') | |
| else | |
| total_tokens=0 | |
| fi | |
| # Check if no usage data available | |
| if [[ $total_tokens -eq 0 ]]; then | |
| local left_side="${model_color}${model_name}${COLOR_RESET} " | |
| local right_side="${COLOR_TEXT}context size N/A${COLOR_RESET}" | |
| build_statusline "$left_side" "$right_side" "$term_width" | |
| return | |
| fi | |
| # Calculate usage percentage | |
| local progress_pct_int | |
| progress_pct_int=$(( total_tokens * 100 / CONTEXT_LIMIT )) | |
| if [[ $progress_pct_int -gt 100 ]]; then | |
| progress_pct_int=100 | |
| fi | |
| # Format token counts | |
| local formatted_tokens formatted_limit | |
| formatted_tokens="$(( total_tokens / 1000 ))k" | |
| formatted_limit="$(( CONTEXT_LIMIT / 1000 ))k" | |
| # Calculate dynamic progress bar length | |
| local bar_length | |
| bar_length=$(( term_width / 2 )) | |
| if [[ $bar_length -lt $BAR_MIN_LENGTH ]]; then bar_length=$BAR_MIN_LENGTH; fi | |
| if [[ $bar_length -gt $BAR_MAX_LENGTH ]]; then bar_length=$BAR_MAX_LENGTH; fi | |
| # Calculate filled blocks | |
| local filled_blocks empty_blocks | |
| filled_blocks=$(( (progress_pct_int * bar_length) / 100 )) | |
| if [[ $filled_blocks -gt $bar_length ]]; then filled_blocks=$bar_length; fi | |
| empty_blocks=$((bar_length - filled_blocks)) | |
| # Get colors based on percentage | |
| local colors bar_color empty_color | |
| colors=$(get_bar_colors "$progress_pct_int") | |
| bar_color=$(echo "$colors" | cut -d' ' -f1) | |
| empty_color=$(echo "$colors" | cut -d' ' -f2) | |
| # Build progress bar with centered percentage | |
| local pct_text="${progress_pct_int}%" | |
| local pct_len=${#pct_text} | |
| local center_pos=$(( (bar_length - pct_len) / 2 )) | |
| local progress_bar="" | |
| for ((i=0; i<bar_length; i++)); do | |
| if [[ $i -eq $center_pos ]]; then | |
| progress_bar+="${COLOR_RESET}${COLOR_TEXT}${pct_text}${COLOR_RESET}" | |
| i=$((i + pct_len - 1)) | |
| elif [[ $i -lt $filled_blocks ]]; then | |
| progress_bar+="${bar_color}█" | |
| else | |
| progress_bar+="${empty_color}░" | |
| fi | |
| done | |
| # Build left and right sides | |
| local left_side="${model_color}${model_name}${COLOR_RESET} " | |
| local right_side="${progress_bar}${COLOR_RESET} ${COLOR_TEXT}(${formatted_tokens}/${formatted_limit})${COLOR_RESET}" | |
| # Output aligned statusline | |
| build_statusline "$left_side" "$right_side" "$term_width" | |
| } | |
| # ============================================================================ | |
| # MAIN ENTRY POINT | |
| # ============================================================================ | |
| calculate_context |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment