#!/bin/bash STATUSLINE_VERSION="1.4.0" input=$(cat) # ---- check jq availability ---- HAS_JQ=0 if command -v jq >/dev/null 2>&1; then HAS_JQ=1 fi # Get the directory where this statusline script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="${SCRIPT_DIR}/statusline.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') # ---- logging ---- { echo "[$TIMESTAMP] Status line triggered (cc-statusline v${STATUSLINE_VERSION})" echo "[$TIMESTAMP] Input:" if [ "$HAS_JQ" -eq 1 ]; then echo "$input" | jq . 2>/dev/null || echo "$input" echo "[$TIMESTAMP] Using jq for JSON parsing" else echo "$input" echo "[$TIMESTAMP] WARNING: jq not found, using bash fallback for JSON parsing" fi echo "---" } >>"$LOG_FILE" 2>/dev/null # ---- color helpers (force colors for Claude Code) ---- use_color=1 [ -n "$NO_COLOR" ] && use_color=0 C() { if [ "$use_color" -eq 1 ]; then printf '\033[%sm' "$1"; fi; } RST() { if [ "$use_color" -eq 1 ]; then printf '\033[0m'; fi; } # ---- modern sleek colors ---- dir_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;117m'; fi; } # sky blue model_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;147m'; fi; } # light purple version_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;180m'; fi; } # soft yellow cc_version_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;249m'; fi; } # light gray style_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;245m'; fi; } # gray rst() { if [ "$use_color" -eq 1 ]; then printf '\033[0m'; fi; } # ---- time helpers ---- progress_bar() { pct="${1:-0}" width="${2:-10}" [[ "$pct" =~ ^[0-9]+$ ]] || pct=0 ((pct < 0)) && pct=0 ((pct > 100)) && pct=100 filled=$((pct * width / 100)) empty=$((width - filled)) printf '%*s' "$filled" '' | tr ' ' '=' printf '%*s' "$empty" '' | tr ' ' '-' } # git utilities num_or_zero() { v="$1" [[ "$v" =~ ^[0-9]+$ ]] && echo "$v" || echo 0 } # ---- JSON extraction utilities ---- extract_json_string() { local json="$1" local key="$2" local default="${3:-}" local field="${key##*.}" field="${field%% *}" local value=$(echo "$json" | grep -o "\"${field}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:[[:space:]]*"\([^"]*\)".*/\1/') if [ -n "$value" ]; then value=$(echo "$value" | sed 's/\\\\/\//g') fi if [ -z "$value" ] || [ "$value" = "null" ]; then value=$(echo "$json" | grep -o "\"${field}\"[[:space:]]*:[[:space:]]*[0-9.]\+" | head -1 | sed 's/.*:[[:space:]]*\([0-9.]\+\).*/\1/') fi if [ -n "$value" ] && [ "$value" != "null" ]; then echo "$value" else echo "$default" fi } # ---- basics ---- if [ "$HAS_JQ" -eq 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) session_id=$(echo "$input" | jq -r '.session_id // ""' 2>/dev/null) cc_version=$(echo "$input" | jq -r '.version // ""' 2>/dev/null) output_style=$(echo "$input" | jq -r '.output_style.name // ""' 2>/dev/null) else current_dir=$(echo "$input" | grep -o '"workspace"[[:space:]]*:[[:space:]]*{[^}]*"current_dir"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"current_dir"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | sed 's/\\\\/\//g') if [ -z "$current_dir" ] || [ "$current_dir" = "null" ]; then current_dir=$(echo "$input" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | sed 's/\\\\/\//g') fi [ -z "$current_dir" ] && current_dir="unknown" current_dir=$(echo "$current_dir" | sed "s|^$HOME|~|g") model_name=$(echo "$input" | grep -o '"model"[[:space:]]*:[[:space:]]*{[^}]*"display_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"display_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') [ -z "$model_name" ] && model_name="Claude" model_version="" session_id=$(extract_json_string "$input" "session_id" "") cc_version=$(echo "$input" | grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') output_style=$(echo "$input" | grep -o '"output_style"[[:space:]]*:[[:space:]]*{[^}]*"name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') fi # ---- git colors ---- git_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;150m'; fi; } # soft green # ---- git ---- 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 # ---- context window calculation (native) ---- context_pct="" context_remaining_pct="" context_color() { if [ "$use_color" -eq 1 ]; then printf '\033[1;37m'; fi; } # default white if [ "$HAS_JQ" -eq 1 ]; then CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // 200000' 2>/dev/null) USAGE=$(echo "$input" | jq '.context_window.current_usage' 2>/dev/null) if [ "$USAGE" != "null" ] && [ -n "$USAGE" ]; then CURRENT_TOKENS=$(echo "$USAGE" | jq '(.input_tokens // 0) + (.cache_creation_input_tokens // 0) + (.cache_read_input_tokens // 0)' 2>/dev/null) if [ -n "$CURRENT_TOKENS" ] && [ "$CURRENT_TOKENS" -gt 0 ] 2>/dev/null; then context_used_pct=$((CURRENT_TOKENS * 100 / CONTEXT_SIZE)) context_remaining_pct=$((100 - context_used_pct)) ((context_remaining_pct < 0)) && context_remaining_pct=0 ((context_remaining_pct > 100)) && context_remaining_pct=100 if [ "$context_remaining_pct" -le 20 ]; then context_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;203m'; fi; } # coral red elif [ "$context_remaining_pct" -le 40 ]; then context_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;215m'; fi; } # peach else context_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;158m'; fi; } # mint green fi context_pct="${context_remaining_pct}%" fi fi fi # ---- usage colors ---- usage_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;189m'; fi; } # lavender cost_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;222m'; fi; } # light gold burn_color() { if [ "$use_color" -eq 1 ]; then printf '\033[38;5;220m'; fi; } # bright gold # ---- cost and usage extraction ---- cost_usd="" cost_per_hour="" tpm="" tot_tokens="" # Extract cost and token data from Claude Code's native input if [ "$HAS_JQ" -eq 1 ]; then # Cost data cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // empty' 2>/dev/null) total_duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // empty' 2>/dev/null) # Calculate burn rate ($/hour) from cost and duration if [ -n "$cost_usd" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then cost_per_hour=$(echo "$cost_usd $total_duration_ms" | awk '{printf "%.2f", $1 * 3600000 / $2}') fi # Token data from native context_window (no ccusage needed) input_tokens=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0' 2>/dev/null) output_tokens=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0' 2>/dev/null) if [ "$input_tokens" != "null" ] && [ "$output_tokens" != "null" ]; then tot_tokens=$((input_tokens + output_tokens)) [ "$tot_tokens" -eq 0 ] && tot_tokens="" fi # Calculate tokens per minute from native data if [ -n "$tot_tokens" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then tpm=$(echo "$tot_tokens $total_duration_ms" | awk '{if ($2 > 0) printf "%.0f", $1 * 60000 / $2; else print ""}') fi else # Bash fallback for cost extraction cost_usd=$(echo "$input" | grep -o '"total_cost_usd"[[:space:]]*:[[:space:]]*[0-9.]*' | sed 's/.*:[[:space:]]*\([0-9.]*\).*/\1/') total_duration_ms=$(echo "$input" | grep -o '"total_duration_ms"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\([0-9]*\).*/\1/') if [ -n "$cost_usd" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then cost_per_hour=$(echo "$cost_usd $total_duration_ms" | awk '{printf "%.2f", $1 * 3600000 / $2}') fi # Token data from native context_window (bash fallback) input_tokens=$(echo "$input" | grep -o '"total_input_tokens"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\([0-9]*\).*/\1/') output_tokens=$(echo "$input" | grep -o '"total_output_tokens"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\([0-9]*\).*/\1/') if [ -n "$input_tokens" ] && [ -n "$output_tokens" ]; then tot_tokens=$((input_tokens + output_tokens)) [ "$tot_tokens" -eq 0 ] && tot_tokens="" fi if [ -n "$tot_tokens" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then tpm=$(echo "$tot_tokens $total_duration_ms" | awk '{if ($2 > 0) printf "%.0f", $1 * 60000 / $2; else print ""}') fi fi # ---- log extracted data ---- { echo "[$TIMESTAMP] Extracted: dir=${current_dir:-}, model=${model_name:-}, version=${model_version:-}, git=${git_branch:-}, context=${context_pct:-}, cost=${cost_usd:-}, cost_ph=${cost_per_hour:-}, tokens=${tot_tokens:-}, tpm=${tpm:-}" } >>"$LOG_FILE" 2>/dev/null # ---- render statusline ---- # Line 1: Core info (directory, git, model, claude code version, output style) printf '📁 %s%s%s' "$(dir_color)" "$current_dir" "$(rst)" if [ -n "$git_branch" ]; then printf ' 🌿 %s%s%s' "$(git_color)" "$git_branch" "$(rst)" fi printf ' 🤖 %s%s%s' "$(model_color)" "$model_name" "$(rst)" if [ -n "$model_version" ] && [ "$model_version" != "null" ]; then printf ' 🏷️ %s%s%s' "$(version_color)" "$model_version" "$(rst)" fi if [ -n "$cc_version" ] && [ "$cc_version" != "null" ]; then printf ' 📟 %sv%s%s' "$(cc_version_color)" "$cc_version" "$(rst)" fi if [ -n "$output_style" ] && [ "$output_style" != "null" ]; then printf ' 🎨 %s%s%s' "$(style_color)" "$output_style" "$(rst)" fi # Line 2: Context and session time line2="" if [ -n "$context_pct" ]; then context_bar=$(progress_bar "$context_remaining_pct" 10) line2="🧠 $(context_color)Context Remaining: ${context_pct} [${context_bar}]$(rst)" fi if [ -z "$line2" ] && [ -z "$context_pct" ]; then line2="🧠 $(context_color)Context Remaining: TBD$(rst)" fi # Line 3: Cost and usage analytics line3="" if [ -n "$cost_usd" ] && [[ "$cost_usd" =~ ^[0-9.]+$ ]]; then if [ -n "$cost_per_hour" ] && [[ "$cost_per_hour" =~ ^[0-9.]+$ ]]; then cost_per_hour_formatted=$(printf '%.2f' "$cost_per_hour") line3="💰 $(cost_color)\$$(printf '%.2f' "$cost_usd")$(rst) ($(burn_color)\$${cost_per_hour_formatted}/h$(rst))" else line3="💰 $(cost_color)\$$(printf '%.2f' "$cost_usd")$(rst)" fi fi if [ -n "$tot_tokens" ] && [[ "$tot_tokens" =~ ^[0-9]+$ ]]; then if [ -n "$tpm" ] && [[ "$tpm" =~ ^[0-9.]+$ ]]; then tpm_formatted=$(printf '%.0f' "$tpm") if [ -n "$line3" ]; then line3="$line3 📊 $(usage_color)${tot_tokens} tok (${tpm_formatted} tpm)$(rst)" else line3="📊 $(usage_color)${tot_tokens} tok (${tpm_formatted} tpm)$(rst)" fi else if [ -n "$line3" ]; then line3="$line3 📊 $(usage_color)${tot_tokens} tok$(rst)" else line3="📊 $(usage_color)${tot_tokens} tok$(rst)" fi fi fi # Print lines if [ -n "$line2" ]; then printf '\n%s' "$line2" fi if [ -n "$line3" ]; then printf '\n%s' "$line3" fi printf '\n'