diff options
Diffstat (limited to 'ar/.config/claude/statuslines/statusline.sh')
| -rwxr-xr-x | ar/.config/claude/statuslines/statusline.sh | 236 |
1 files changed, 228 insertions, 8 deletions
diff --git a/ar/.config/claude/statuslines/statusline.sh b/ar/.config/claude/statuslines/statusline.sh index 047590a..e000b2b 100755 --- a/ar/.config/claude/statuslines/statusline.sh +++ b/ar/.config/claude/statuslines/statusline.sh @@ -2,10 +2,10 @@ set -euo pipefail 2>/dev/null || set -eu # ============================================================ -# STATUSLINE v2.0.0 - Claude Code Status Line +# STATUSLINE v2.1.0 - Claude Code Status Line # ============================================================ -readonly STATUSLINE_VERSION="2.0.0" +readonly STATUSLINE_VERSION="2.1.0" # ============================================================ # CONFIGURATION @@ -36,9 +36,16 @@ readonly CONTEXT_ICON="đ§ " readonly DIR_ICON="đ" readonly GIT_ICON="đŋ" readonly COST_ICON="đ°" +readonly DAILY_COST_ICON="đ
" readonly TOKEN_ICON="đ" +readonly CACHE_ICON="đž" readonly TIME_ICON="đ" +readonly REMAINING_ICON="âŗ" readonly VERSION_ICON="đ" +readonly PYTHON_ICON="đ" +readonly GO_ICON="đĻĢ" +readonly NODE_ICON="đĻ" +readonly RUST_ICON="đĻ" # Git state constants readonly STATE_NOT_REPO="not_repo" @@ -223,6 +230,86 @@ get_context_message() { } # ============================================================ +# LANGUAGE/RUNTIME DETECTION +# ============================================================ + +detect_language_info() { + local current_dir="$1" + local lang_info="" + + # Use current_dir or fallback to PWD + local check_dir="${current_dir:-$PWD}" + [[ "$check_dir" == "$NULL_VALUE" || "$check_dir" == "unknown" ]] && check_dir="$PWD" + + # Check for Python project + if [[ -n "${VIRTUAL_ENV:-}" ]]; then + local venv_name="${VIRTUAL_ENV##*/}" + local folder_name="${check_dir##*/}" + # Show venv name unless it's generic + if [[ "$venv_name" == ".venv" || "$venv_name" == "venv" ]]; then + venv_name="$folder_name" + fi + local pyver + pyver=$(python3 --version 2>/dev/null | cut -d' ' -f2 || echo '') + [[ -n "$pyver" ]] && lang_info="${PYTHON_ICON} ${GREEN}${pyver}${NC} ${GRAY}(${venv_name})${NC}" + elif [[ -f "$check_dir/requirements.txt" || -f "$check_dir/setup.py" || -f "$check_dir/pyproject.toml" || -f "$check_dir/Pipfile" ]]; then + local pyver + pyver=$(python3 --version 2>/dev/null | cut -d' ' -f2 || echo '') + [[ -n "$pyver" ]] && lang_info="${PYTHON_ICON} ${GREEN}${pyver}${NC}" + # Check for Go project + elif [[ -f "$check_dir/go.mod" || -f "$check_dir/go.sum" ]]; then + local gover + gover=$(go version 2>/dev/null | grep -oE 'go[0-9]+\.[0-9]+(\.[0-9]+)?' | sed 's/go//' || echo '') + [[ -n "$gover" ]] && lang_info="${GO_ICON} ${CYAN}${gover}${NC}" + # Check for Node.js project + elif [[ -f "$check_dir/package.json" ]]; then + local nodever + nodever=$(node --version 2>/dev/null | sed 's/v//' || echo '') + [[ -n "$nodever" ]] && lang_info="${NODE_ICON} ${GREEN}${nodever}${NC}" + # Check for Rust project + elif [[ -f "$check_dir/Cargo.toml" ]]; then + local rustver + rustver=$(rustc --version 2>/dev/null | cut -d' ' -f2 || echo '') + [[ -n "$rustver" ]] && lang_info="${RUST_ICON} ${ORANGE}${rustver}${NC}" + fi + + echo "$lang_info" +} + +# ============================================================ +# CCUSAGE DATA FETCHING +# ============================================================ + +get_ccusage_data() { + local session_id="$1" + + # Check if npx is available + command -v npx >/dev/null 2>&1 || return 1 + + # Get daily cost + local daily_cost="" + local daily_json + daily_json=$(npx ccusage daily --json 2>/dev/null) + if [[ -n "$daily_json" ]]; then + daily_cost=$(echo "$daily_json" | jq -r '.totals.totalCost // 0' 2>/dev/null) + fi + + # Get active block data (projections, remaining time) + local remaining_minutes="" projected_cost="" block_cost="" burn_rate="" + local blocks_json + blocks_json=$(npx ccusage blocks --active --json 2>/dev/null) + if [[ -n "$blocks_json" ]]; then + remaining_minutes=$(echo "$blocks_json" | jq -r '.blocks[0].projection.remainingMinutes // empty' 2>/dev/null) + projected_cost=$(echo "$blocks_json" | jq -r '.blocks[0].projection.totalCost // empty' 2>/dev/null) + block_cost=$(echo "$blocks_json" | jq -r '.blocks[0].costUSD // empty' 2>/dev/null) + burn_rate=$(echo "$blocks_json" | jq -r '.blocks[0].burnRate.costPerHour // empty' 2>/dev/null) + fi + + # Output: daily_cost|remaining_minutes|projected_cost|block_cost|burn_rate + echo "${daily_cost:-0}|${remaining_minutes:-0}|${projected_cost:-0}|${block_cost:-0}|${burn_rate:-0}" +} + +# ============================================================ # JSON PARSING # ============================================================ @@ -243,7 +330,9 @@ parse_with_jq() { (.cost.total_cost_usd // 0), (.cost.total_duration_ms // 0), .version // "", - .session_id // "" + .session_id // "", + (.context_window.current_usage.cache_creation_input_tokens // 0), + (.context_window.current_usage.cache_read_input_tokens // 0) ' 2>/dev/null } @@ -272,6 +361,7 @@ parse_without_jq() { local model_name current_dir context_size current_usage local total_input total_output cost_usd duration_ms version session_id + local cache_creation cache_read model_name=$(extract_json_string "$input" "display_name" "Claude") current_dir=$(extract_json_string "$input" "current_dir" "") @@ -285,9 +375,12 @@ parse_without_jq() { duration_ms=$(extract_json_string "$input" "total_duration_ms" "0") version=$(extract_json_string "$input" "version" "") session_id=$(extract_json_string "$input" "session_id" "") + cache_creation=$(extract_json_string "$input" "cache_creation_input_tokens" "0") + cache_read=$(extract_json_string "$input" "cache_read_input_tokens" "0") printf '%s\n' "$model_name" "$current_dir" "$context_size" "$current_usage" \ - "$total_input" "$total_output" "$cost_usd" "$duration_ms" "$version" "$session_id" + "$total_input" "$total_output" "$cost_usd" "$duration_ms" "$version" "$session_id" \ + "$cache_creation" "$cache_read" } # ============================================================ @@ -517,6 +610,91 @@ build_version_component() { echo -n "${VERSION_ICON} ${GRAY}v${version}${NC}" } +build_language_component() { + local lang_info="$1" + [[ -z "$lang_info" ]] && return 0 + echo -n "$lang_info" +} + +build_daily_cost_component() { + local daily_cost="$1" + + [[ -z "$daily_cost" || "$daily_cost" == "0" || "$daily_cost" == "$NULL_VALUE" ]] && return 0 + + local cost_fmt + cost_fmt=$(format_cost "$daily_cost") + echo -n "${DAILY_COST_ICON} ${ORANGE}\$${cost_fmt}/day${NC}" +} + +build_remaining_time_component() { + local remaining_minutes="$1" + local projected_cost="$2" + + [[ -z "$remaining_minutes" || "$remaining_minutes" == "0" || "$remaining_minutes" == "$NULL_VALUE" ]] && return 0 + + local hours=$((remaining_minutes / 60)) + local mins=$((remaining_minutes % 60)) + + local time_str + if [[ "$hours" -gt 0 ]]; then + time_str="${hours}h ${mins}m" + else + time_str="${mins}m" + fi + + # Color based on remaining time + local time_color + if [[ "$remaining_minutes" -gt 120 ]]; then + time_color="$GREEN" + elif [[ "$remaining_minutes" -gt 60 ]]; then + time_color="$YELLOW" + elif [[ "$remaining_minutes" -gt 30 ]]; then + time_color="$ORANGE" + else + time_color="$RED" + fi + + echo -n "${REMAINING_ICON} ${time_color}${time_str} left${NC}" + + # Show projected cost if available + if [[ -n "$projected_cost" && "$projected_cost" != "0" && "$projected_cost" != "$NULL_VALUE" ]]; then + local proj_fmt + proj_fmt=$(format_cost "$projected_cost") + echo -n " ${GRAY}(đĩ\$${proj_fmt})${NC}" + fi +} + +build_cache_component() { + local cache_creation="$1" + local cache_read="$2" + + # Skip if no significant cache usage + local total_cache=$((cache_creation + cache_read)) + [[ "$total_cache" -lt 1000 ]] && return 0 + + local creation_fmt read_fmt + creation_fmt=$(format_number "$cache_creation") + read_fmt=$(format_number "$cache_read") + + # Calculate cache hit ratio + local hit_ratio=0 + if [[ "$total_cache" -gt 0 ]]; then + hit_ratio=$((cache_read * 100 / total_cache)) + fi + + # Color based on hit ratio (higher is better for cost) + local ratio_color + if [[ "$hit_ratio" -ge 80 ]]; then + ratio_color="$GREEN" + elif [[ "$hit_ratio" -ge 50 ]]; then + ratio_color="$YELLOW" + else + ratio_color="$ORANGE" + fi + + echo -n "${CACHE_ICON} ${GRAY}+${creation_fmt}${NC}/${ratio_color}âē${read_fmt}${NC} ${GRAY}(${hit_ratio}% hit)${NC}" +} + # ============================================================ # MAIN # ============================================================ @@ -549,6 +727,7 @@ main() { # Extract fields local model_name current_dir context_size current_usage local total_input total_output cost_usd duration_ms version session_id + local cache_creation cache_read { read -r model_name read -r current_dir @@ -560,6 +739,8 @@ main() { read -r duration_ms read -r version read -r session_id + read -r cache_creation + read -r cache_read } <<<"$parsed" log_debug "Parsed: model=$model_name, dir=$current_dir, context=$current_usage/$context_size, cost=$cost_usd, duration=$duration_ms" @@ -568,14 +749,34 @@ main() { local git_data git_data=$(get_git_info "$current_dir") + # Get language/runtime info + local lang_info + lang_info=$(detect_language_info "$current_dir") + + # Get ccusage data (daily cost, remaining time, etc.) + local ccusage_data="" daily_cost="" remaining_minutes="" projected_cost="" block_cost="" ccusage_burn_rate="" + if check_jq; then + ccusage_data=$(get_ccusage_data "$session_id" 2>/dev/null || echo "") + if [[ -n "$ccusage_data" ]]; then + IFS='|' read -r daily_cost remaining_minutes projected_cost block_cost ccusage_burn_rate <<<"$ccusage_data" + fi + fi + # Build components local output="" - # Line 1: Model | Directory | Git | Version + # Line 1: Model | Directory | Language | Git | Version output+=$(build_model_component "$model_name") output+=$(sep) output+=$(build_directory_component "$current_dir") + local lang_component + lang_component=$(build_language_component "$lang_info") + [[ -n "$lang_component" ]] && { + output+=$(sep) + output+="$lang_component" + } + local git_component git_component=$(build_git_component "$git_data") [[ -n "$git_component" ]] && { @@ -594,19 +795,32 @@ main() { output+=$'\n' output+=$(build_context_component "$context_size" "$current_usage") - # Line 3: Cost | Tokens | Time - local cost_component token_component time_component + # Line 3: Cost | Daily Cost | Remaining Time | Tokens | Time + local cost_component daily_cost_component remaining_component token_component time_component cache_component cost_component=$(build_cost_component "$cost_usd" "$duration_ms") + daily_cost_component=$(build_daily_cost_component "$daily_cost") + remaining_component=$(build_remaining_time_component "$remaining_minutes" "$projected_cost") token_component=$(build_token_component "$total_input" "$total_output" "$duration_ms") time_component=$(build_time_component "$duration_ms") + cache_component=$(build_cache_component "${cache_creation:-0}" "${cache_read:-0}") - if [[ -n "$cost_component" || -n "$token_component" || -n "$time_component" ]]; then + if [[ -n "$cost_component" || -n "$daily_cost_component" || -n "$remaining_component" || -n "$token_component" || -n "$time_component" ]]; then output+=$'\n' local first=1 if [[ -n "$cost_component" ]]; then output+="$cost_component" first=0 fi + if [[ -n "$daily_cost_component" ]]; then + [[ "$first" -eq 0 ]] && output+=$(sep) + output+="$daily_cost_component" + first=0 + fi + if [[ -n "$remaining_component" ]]; then + [[ "$first" -eq 0 ]] && output+=$(sep) + output+="$remaining_component" + first=0 + fi if [[ -n "$token_component" ]]; then [[ "$first" -eq 0 ]] && output+=$(sep) output+="$token_component" @@ -618,6 +832,12 @@ main() { fi fi + # Line 4: Cache info (if significant) + if [[ -n "$cache_component" ]]; then + output+=$'\n' + output+="$cache_component" + fi + echo -e "$output" } |
