#!/usr/bin/env bash # Claude Code 4-line statusline # Reads JSON from stdin, outputs ANSI-colored 4-line status bar # --- Color definitions (bright variants for dark backgrounds) --- RED=$'\033[91m' GREEN=$'\033[92m' YELLOW=$'\033[93m' BLUE=$'\033[94m' MAGENTA=$'\033[95m' CYAN=$'\033[96m' GRAY=$'\033[37m' WHITE=$'\033[97m' BOLD=$'\033[1m' DIM=$'\033[2m' RESET=$'\033[0m' SEP="${GRAY} │ ${RESET}" # --- Read all JSON input --- input=$(cat) # --- Single jq call: extract all 16 fields newline-separated --- # Using newlines instead of tabs to avoid bash IFS merging consecutive delimiters mapfile -t _fields < <(echo "$input" | jq -r ' (.model.display_name // .model.id // "?"), (.model.id // "?"), (.context_window.used_percentage // 0 | floor | tostring), (.context_window.context_window_size // 0 | tostring), (.context_window.total_input_tokens // 0 | tostring), (.context_window.total_output_tokens // 0 | tostring), (.cost.total_cost_usd // 0 | . * 100 | floor | . / 100 | tostring), (.cost.total_duration_ms // 0 | tostring), (.cost.total_lines_added // 0 | tostring), (.cost.total_lines_removed // 0 | tostring), (if .session_id == null or .session_id == "" then "--------" else .session_id end), (.version // "?.?.?"), (if .output_style.name == null or .output_style.name == "" then "default" else .output_style.name end), (.workspace.current_dir // "~"), (.vim.mode // ""), (.agent.name // "") ') model_name="${_fields[0]}" model_id="${_fields[1]}" ctx_pct="${_fields[2]}" ctx_size="${_fields[3]}" total_in_tok="${_fields[4]}" total_out_tok="${_fields[5]}" cost="${_fields[6]}" dur_ms="${_fields[7]}" lines_add="${_fields[8]}" lines_rm="${_fields[9]}" session_id="${_fields[10]}" version="${_fields[11]}" style="${_fields[12]}" cwd="${_fields[13]}" vim_mode="${_fields[14]}" agent_name="${_fields[15]}" # --- Hostname (cached in variable, never changes) --- HOSTNAME_SHORT=$(hostname -s 2>/dev/null || echo "unknown") # --- Context progress bar (12 chars wide) --- pct=${ctx_pct:-0} filled=$(( pct * 12 / 100 )) empty=$(( 12 - filled )) if [ "$pct" -ge 70 ]; then bar_color="$RED" elif [ "$pct" -ge 50 ]; then bar_color="$YELLOW" else bar_color="$GREEN" fi bar_filled="" bar_empty="" for (( i=0; i= 5) col="\033[91m"; else if (c+0 >= 2) col="\033[93m"; else col="\033[92m"; printf "%s$%.2f\033[0m", col, c+0 }') # --- Token formatting --- format_tokens() { local tok=$1 if [ "$tok" -ge 1000000 ] 2>/dev/null; then echo "$(( tok / 1000000 ))M" elif [ "$tok" -ge 1000 ] 2>/dev/null; then echo "$(( tok / 1000 ))k" else echo "${tok}" fi } in_tok_display=$(format_tokens "${total_in_tok:-0}") # --- Model tier symbol --- case "$model_id" in *opus*) tier_sym="${MAGENTA}${BOLD}◆${RESET}"; model_color="${MAGENTA}${BOLD}" ;; *sonnet*) tier_sym="${BLUE}${BOLD}◇${RESET}"; model_color="${BLUE}${BOLD}" ;; *haiku*) tier_sym="${GREEN}${BOLD}○${RESET}"; model_color="${GREEN}${BOLD}" ;; *) tier_sym="${GRAY}●${RESET}"; model_color="${GRAY}" ;; esac # --- Session ID (first 8 chars) --- session_short="${session_id:0:8}" # --- Duration formatting --- dur_total_sec=$(( ${dur_ms:-0} / 1000 )) if [ "$dur_total_sec" -ge 3600 ]; then dur_h=$(( dur_total_sec / 3600 )) dur_m=$(( (dur_total_sec % 3600) / 60 )) dur_display="${dur_h}h ${dur_m}m" elif [ "$dur_total_sec" -ge 60 ]; then dur_m=$(( dur_total_sec / 60 )) dur_s=$(( dur_total_sec % 60 )) dur_display="${dur_m}m ${dur_s}s" else dur_display="${dur_total_sec}s" fi # --- Git branch (cached with 5s TTL) --- CACHE_FILE="/tmp/claude-statusline-git-cache" NOW=$(date +%s) GIT_BRANCH="" if [ -f "$CACHE_FILE" ]; then CACHE_AGE=$(( NOW - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0) )) if [ "$CACHE_AGE" -lt 5 ]; then GIT_BRANCH=$(cat "$CACHE_FILE") fi fi if [ -z "$GIT_BRANCH" ]; then GIT_BRANCH=$(cd "${cwd:-$HOME}" 2>/dev/null && git symbolic-ref --short HEAD 2>/dev/null || echo "") echo "$GIT_BRANCH" > "$CACHE_FILE" fi # ============================================================ # LINE 1: Identity & Resources # ============================================================ line1="\xf0\x9f\x8e\xa8 ${WHITE}${style}${RESET}" line1+="${SEP}" line1+="\xf0\x9f\x96\xa5\xef\xb8\x8f ${CYAN}${BOLD}${HOSTNAME_SHORT}${RESET}" line1+="${SEP}" line1+="\xf0\x9f\xa7\xa0 [${progress_bar}] ${pct_display}" line1+="${SEP}" line1+="\xf0\x9f\x92\xb0 ${cost_display}" line1+=" ${GRAY}${in_tok_display} tokens${RESET}" # ============================================================ # LINE 2: Model & Session # ============================================================ line2="\xf0\x9f\xa4\x96 ${tier_sym} ${model_color}${model_name}${RESET}" line2+="${SEP}" line2+="\xf0\x9f\x94\x91 ${GRAY}${session_short}${RESET}" line2+="${SEP}" line2+="\xf0\x9f\x93\x9f ${CYAN}v${version}${RESET}" # ============================================================ # LINE 3: Workspace & Tools # ============================================================ line3="\xf0\x9f\x93\x81 ${CYAN}${cwd}${RESET}" if [ -n "$GIT_BRANCH" ]; then line3+="${SEP}" line3+="\xf0\x9f\x8c\xbf ${GREEN}${BOLD}${GIT_BRANCH}${RESET}" fi line3+="${SEP}" line3+="\xf0\x9f\x93\x8a ${GREEN}▲+${lines_add}${RESET} ${RED}▼-${lines_rm}${RESET}" line3+="${SEP}" line3+="\xe2\x8f\xb1\xef\xb8\x8f ${YELLOW}${dur_display}${RESET}" # ============================================================ # LINE 4: Mode & Status (conditional) # ============================================================ line4="\xe2\x8f\xb5\xe2\x8f\xb5 ${BOLD}${style}${RESET}" if [ -n "$vim_mode" ]; then vim_upper=$(echo "$vim_mode" | tr '[:lower:]' '[:upper:]') case "$vim_upper" in NORMAL) vim_color="$BLUE" ;; INSERT) vim_color="$GREEN" ;; *) vim_color="$WHITE" ;; esac line4+="${SEP}" line4+="\xf0\x9f\x94\xb2 ${vim_color}${vim_upper}${RESET}" fi if [ -n "$agent_name" ]; then line4+="${SEP}" line4+="\xf0\x9f\x8f\xb7\xef\xb8\x8f ${MAGENTA}${agent_name}${RESET}" fi # ============================================================ # Output all 4 lines # ============================================================ printf '%b\n' "$line1" printf '%b\n' "$line2" printf '%b\n' "$line3" printf '%b\n' "$line4"