summaryrefslogtreecommitdiff
path: root/.claude/statuslines
diff options
context:
space:
mode:
Diffstat (limited to '.claude/statuslines')
-rwxr-xr-x.claude/statuslines/statusline.sh274
1 files changed, 274 insertions, 0 deletions
diff --git a/.claude/statuslines/statusline.sh b/.claude/statuslines/statusline.sh
new file mode 100755
index 0000000..54579fa
--- /dev/null
+++ b/.claude/statuslines/statusline.sh
@@ -0,0 +1,274 @@
+#!/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'