From 570d50bdb97918a0c6c560296d900367115c44dd Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Sat, 17 Jan 2026 10:58:02 +0900 Subject: modified .claude/settings.json, modified statuslines/statusline.sh --- default/.claude/statuslines/statusline.sh | 801 +++++++++++++++++++++--------- 1 file changed, 569 insertions(+), 232 deletions(-) (limited to 'default/.claude/statuslines/statusline.sh') diff --git a/default/.claude/statuslines/statusline.sh b/default/.claude/statuslines/statusline.sh index 54579fa..0d524d2 100755 --- a/default/.claude/statuslines/statusline.sh +++ b/default/.claude/statuslines/statusline.sh @@ -1,274 +1,611 @@ -#!/bin/bash +#!/usr/bin/env bash +set -euo pipefail 2>/dev/null || set -eu + +# ============================================================ +# STATUSLINE v2.0.0 - Claude Code Status Line +# ============================================================ + +readonly STATUSLINE_VERSION="2.0.0" + +# ============================================================ +# CONFIGURATION +# ============================================================ +readonly BAR_WIDTH=12 +readonly BAR_FILLED="โ–ˆ" +readonly BAR_EMPTY="โ–‘" + +# Colors (256-color palette) +readonly RED='\033[38;5;203m' +readonly GREEN='\033[38;5;150m' +readonly BLUE='\033[38;5;117m' +readonly MAGENTA='\033[38;5;147m' +readonly CYAN='\033[38;5;81m' +readonly ORANGE='\033[38;5;215m' +readonly YELLOW='\033[38;5;222m' +readonly GRAY='\033[38;5;245m' +readonly LIGHT_GRAY='\033[38;5;249m' +readonly NC='\033[0m' + +# Derived constants +readonly SEPARATOR="${GRAY}โ”‚${NC}" +readonly NULL_VALUE="null" + +# Icons +readonly MODEL_ICON="๐Ÿค–" +readonly CONTEXT_ICON="๐Ÿง " +readonly DIR_ICON="๐Ÿ“" +readonly GIT_ICON="๐ŸŒฟ" +readonly COST_ICON="๐Ÿ’ฐ" +readonly TOKEN_ICON="๐Ÿ“Š" +readonly TIME_ICON="โฑ๏ธ" +readonly VERSION_ICON="๐Ÿ“Ÿ" + +# Git state constants +readonly STATE_NOT_REPO="not_repo" +readonly STATE_CLEAN="clean" +readonly STATE_DIRTY="dirty" + +# Context usage messages (tiered by usage percentage) +readonly CONTEXT_MSG_VERY_LOW=( + "just getting started" + "barely touched it" + "fresh as a daisy" + "room for an elephant" + "zero stress mode" + "could do this all day" + "warming up the engines" + "practically empty" + "smooth sailing ahead" + "all systems nominal" +) + +readonly CONTEXT_MSG_LOW=( + "light snacking" + "taking it easy" + "smooth operator" + "just vibing" + "cruising altitude" + "nice and steady" + "zen mode activated" + "coasting along" + "comfortable cruise" + "looking good" +) + +readonly CONTEXT_MSG_MEDIUM=( + "halfway there" + "finding the groove" + "building momentum" + "picking up speed" + "getting interesting" + "entering the zone" + "getting warmer" + "balanced perfectly" + "sweet spot territory" + "gears are meshing" +) + +readonly CONTEXT_MSG_HIGH=( + "getting spicy" + "filling up fast" + "things heating up" + "turning up the heat" + "entering danger zone" + "feeling the pressure" + "approaching red zone" + "intensity rising" + "full throttle mode" + "hold on tight" +) + +readonly CONTEXT_MSG_CRITICAL=( + "living dangerously" + "pushing the limits" + "houston we have a problem" + "danger zone activated" + "running on fumes" + "this is fine ๐Ÿ”ฅ" + "critical mass approaching" + "maximum overdrive" + "context go brrrr" + "about to explode" +) + +# ============================================================ +# LOGGING +# ============================================================ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly LOG_FILE="${SCRIPT_DIR}/statusline.log" -STATUSLINE_VERSION="1.4.0" +log_debug() { + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] $*" >> "$LOG_FILE" 2>/dev/null || true +} -input=$(cat) +# ============================================================ +# UTILITY FUNCTIONS +# ============================================================ -# ---- check jq availability ---- -HAS_JQ=0 -if command -v jq >/dev/null 2>&1; then - HAS_JQ=1 -fi +# Check jq availability +check_jq() { + command -v jq >/dev/null 2>&1 +} -# 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" +# String utilities +get_dirname() { echo "${1##*/}"; } +sep() { echo -n " ${SEPARATOR} "; } + +# Format numbers with K/M suffixes +format_number() { + local num="${1:-0}" + [[ ! "$num" =~ ^[0-9]+$ ]] && { echo "$num"; return; } + + if [[ "$num" -lt 1000 ]]; then + echo "$num" + elif [[ "$num" -lt 1000000 ]]; then + local k=$((num / 1000)) + local remainder=$((num % 1000)) + if [[ "$k" -lt 10 ]]; then + local decimal=$((remainder / 100)) + echo "${k}.${decimal}K" + else + echo "${k}K" + fi else - echo "$input" - echo "[$TIMESTAMP] WARNING: jq not found, using bash fallback for JSON parsing" + local m=$((num / 1000000)) + local remainder=$((num % 1000000)) + if [[ "$m" -lt 10 ]]; then + local decimal=$((remainder / 100000)) + echo "${m}.${decimal}M" + else + echo "${m}M" + fi 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 +# Format duration (ms to human readable) +format_duration() { + local ms="${1:-0}" + [[ ! "$ms" =~ ^[0-9]+$ ]] && { echo ""; return; } + + local seconds=$((ms / 1000)) + local minutes=$((seconds / 60)) + local hours=$((minutes / 60)) + + if [[ "$hours" -gt 0 ]]; then + local remaining_mins=$((minutes % 60)) + echo "${hours}h${remaining_mins}m" + elif [[ "$minutes" -gt 0 ]]; then + local remaining_secs=$((seconds % 60)) + echo "${minutes}m${remaining_secs}s" + else + echo "${seconds}s" + fi +} + +# Format cost +format_cost() { + local cost="${1:-0}" + if [[ "$cost" =~ ^[0-9.]+$ ]]; then + printf "%.2f" "$cost" + else + echo "0.00" + fi +} + +# Get random context message based on usage percentage +get_context_message() { + local percent="${1:-0}" + local messages=() + + if [[ "$percent" -le 20 ]]; then + messages=("${CONTEXT_MSG_VERY_LOW[@]}") + elif [[ "$percent" -le 40 ]]; then + messages=("${CONTEXT_MSG_LOW[@]}") + elif [[ "$percent" -le 60 ]]; then + messages=("${CONTEXT_MSG_MEDIUM[@]}") + elif [[ "$percent" -le 80 ]]; then + messages=("${CONTEXT_MSG_HIGH[@]}") + else + messages=("${CONTEXT_MSG_CRITICAL[@]}") + fi + + local count=${#messages[@]} + local index=$((RANDOM % count)) + echo "${messages[$index]}" } -# ---- JSON extraction utilities ---- +# ============================================================ +# JSON PARSING +# ============================================================ + +parse_with_jq() { + local input="$1" + + echo "$input" | jq -r ' + .model.display_name // "Claude", + .workspace.current_dir // .cwd // "unknown", + (.context_window.context_window_size // 200000), + ( + (.context_window.current_usage.input_tokens // 0) + + (.context_window.current_usage.cache_creation_input_tokens // 0) + + (.context_window.current_usage.cache_read_input_tokens // 0) + ), + (.context_window.total_input_tokens // 0), + (.context_window.total_output_tokens // 0), + (.cost.total_cost_usd // 0), + (.cost.total_duration_ms // 0), + .version // "", + .session_id // "" + ' 2>/dev/null +} + +# Bash fallback for JSON parsing 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/') + + local value + value=$(echo "$json" | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:[[:space:]]*"\([^"]*\)".*/\1/') + + if [[ -z "$value" || "$value" == "null" ]]; then + value=$(echo "$json" | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*[0-9.]\+" | head -1 | sed 's/.*:[[:space:]]*\([0-9.]\+\).*/\1/') fi - if [ -n "$value" ] && [ "$value" != "null" ]; then + + 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="" +parse_without_jq() { + local input="$1" + + local model_name current_dir context_size current_usage + local total_input total_output cost_usd duration_ms version session_id + + model_name=$(extract_json_string "$input" "display_name" "Claude") + current_dir=$(extract_json_string "$input" "current_dir" "") + [[ -z "$current_dir" ]] && current_dir=$(extract_json_string "$input" "cwd" "unknown") + + context_size=$(extract_json_string "$input" "context_window_size" "200000") + current_usage=$(extract_json_string "$input" "input_tokens" "0") + total_input=$(extract_json_string "$input" "total_input_tokens" "0") + total_output=$(extract_json_string "$input" "total_output_tokens" "0") + cost_usd=$(extract_json_string "$input" "total_cost_usd" "0") + duration_ms=$(extract_json_string "$input" "total_duration_ms" "0") + version=$(extract_json_string "$input" "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 + printf '%s\n' "$model_name" "$current_dir" "$context_size" "$current_usage" \ + "$total_input" "$total_output" "$cost_usd" "$duration_ms" "$version" "$session_id" +} + +# ============================================================ +# GIT OPERATIONS (Optimized with porcelain v2) +# ============================================================ + +get_git_info() { + local current_dir="$1" + local git_opts=() + + [[ -n "$current_dir" && "$current_dir" != "$NULL_VALUE" && "$current_dir" != "unknown" ]] && git_opts=(-C "$current_dir") + + # Check if git repo + git "${git_opts[@]}" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { + echo "$STATE_NOT_REPO" + return 0 + } + + # Single git status call with all info (porcelain v2) + local status_output + status_output=$(git "${git_opts[@]}" status --porcelain=v2 --branch --untracked-files=all 2>/dev/null) || { + # Fallback for older git versions + local branch + branch=$(git "${git_opts[@]}" branch --show-current 2>/dev/null || git "${git_opts[@]}" rev-parse --short HEAD 2>/dev/null || echo "unknown") + echo "$STATE_CLEAN|$branch|0|0" + return 0 + } + + # Parse porcelain v2 output + local branch="" ahead="0" behind="0" + while IFS= read -r line; do + case "$line" in + "# branch.head "*) + branch="${line#\# branch.head }" + ;; + "# branch.ab "*) + local ab="${line#\# branch.ab }" + ahead="${ab%% *}" + ahead="${ahead#+}" + behind="${ab##* }" + behind="${behind#-}" + ;; + esac + done <<< "$status_output" + + branch="${branch:-(detached)}" + ahead="${ahead:-0}" + behind="${behind:-0}" + + # Count modified files (lines not starting with #) + local file_count=0 + while IFS= read -r line; do + [[ "$line" != \#* && -n "$line" ]] && ((file_count++)) + done <<< "$status_output" + + if [[ "$file_count" -eq 0 ]]; then + echo "$STATE_CLEAN|$branch|$ahead|$behind" + return 0 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}') + + # Get line changes + local added=0 removed=0 + local diff_output + diff_output=$(git "${git_opts[@]}" diff HEAD --numstat 2>/dev/null || true) + if [[ -n "$diff_output" ]]; then + while IFS=$'\t' read -r a r _; do + [[ "$a" =~ ^[0-9]+$ ]] && added=$((added + a)) + [[ "$r" =~ ^[0-9]+$ ]] && removed=$((removed + r)) + done <<< "$diff_output" 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) + echo "$STATE_DIRTY|$branch|$file_count|$added|$removed|$ahead|$behind" +} + +# ============================================================ +# COMPONENT BUILDERS +# ============================================================ + +build_model_component() { + local model_name="$1" + echo -n "${MODEL_ICON} ${CYAN}${model_name}${NC}" +} + +build_context_component() { + local context_size="$1" + local current_usage="$2" - if [ "$input_tokens" != "null" ] && [ "$output_tokens" != "null" ]; then - tot_tokens=$((input_tokens + output_tokens)) - [ "$tot_tokens" -eq 0 ] && tot_tokens="" + local context_percent=0 + if [[ "$current_usage" != "0" && "$context_size" -gt 0 ]] 2>/dev/null; then + context_percent=$((current_usage * 100 / context_size)) 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 ""}') + # Determine color based on usage (inverted - higher usage = more warning) + local bar_color + if [[ "$context_percent" -le 20 ]]; then + bar_color="$GREEN" + elif [[ "$context_percent" -le 40 ]]; then + bar_color="$CYAN" + elif [[ "$context_percent" -le 60 ]]; then + bar_color="$YELLOW" + elif [[ "$context_percent" -le 80 ]]; then + bar_color="$ORANGE" + else + bar_color="$RED" 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}') + # Build progress bar + local filled=$((context_percent * BAR_WIDTH / 100)) + local empty=$((BAR_WIDTH - filled)) + local bar="${bar_color}" + bar+=$(printf "%${filled}s" | tr ' ' "$BAR_FILLED") + bar+="${GRAY}" + bar+=$(printf "%${empty}s" | tr ' ' "$BAR_EMPTY") + bar+="${NC}" + + # Format numbers + local usage_fmt size_fmt + usage_fmt=$(format_number "$current_usage") + size_fmt=$(format_number "$context_size") + + # Get random message + local message + message=$(get_context_message "$context_percent") + + echo -n "${CONTEXT_ICON} ${GRAY}[${NC}${bar}${GRAY}]${NC} ${context_percent}% ${usage_fmt}/${size_fmt} ${GRAY}ยท ${message}${NC}" +} + +build_directory_component() { + local current_dir="$1" + + local dir_name + if [[ -n "$current_dir" && "$current_dir" != "$NULL_VALUE" && "$current_dir" != "unknown" ]]; then + # Replace home with ~ + current_dir="${current_dir/#$HOME/\~}" + dir_name=$(get_dirname "$current_dir") + else + dir_name=$(get_dirname "$PWD") 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/') + echo -n "${DIR_ICON} ${BLUE}${dir_name}${NC}" +} - if [ -n "$input_tokens" ] && [ -n "$output_tokens" ]; then - tot_tokens=$((input_tokens + output_tokens)) - [ "$tot_tokens" -eq 0 ] && tot_tokens="" +build_git_component() { + local git_data="$1" + + local state + IFS='|' read -r state _ <<< "$git_data" + + case "$state" in + "$STATE_NOT_REPO") + return 0 + ;; + "$STATE_CLEAN") + local branch ahead behind + IFS='|' read -r _ branch ahead behind <<< "$git_data" + echo -n "${GIT_ICON} ${MAGENTA}${branch}${NC}" + [[ "$ahead" -gt 0 ]] 2>/dev/null && echo -n " ${GREEN}โ†‘${ahead}${NC}" + [[ "$behind" -gt 0 ]] 2>/dev/null && echo -n " ${RED}โ†“${behind}${NC}" + ;; + "$STATE_DIRTY") + local branch files added removed ahead behind + IFS='|' read -r _ branch files added removed ahead behind <<< "$git_data" + echo -n "${GIT_ICON} ${MAGENTA}${branch}${NC}" + [[ "$ahead" -gt 0 ]] 2>/dev/null && echo -n " ${GREEN}โ†‘${ahead}${NC}" + [[ "$behind" -gt 0 ]] 2>/dev/null && echo -n " ${RED}โ†“${behind}${NC}" + echo -n " ${GRAY}ยท${NC} ${ORANGE}${files} files${NC}" + if [[ "$added" -gt 0 || "$removed" -gt 0 ]] 2>/dev/null; then + echo -n " ${GREEN}+${added}${NC}/${RED}-${removed}${NC}" + fi + ;; + esac +} + +build_cost_component() { + local cost_usd="$1" + local duration_ms="$2" + + [[ -z "$cost_usd" || "$cost_usd" == "0" || "$cost_usd" == "$NULL_VALUE" ]] && return 0 + + local cost_fmt + cost_fmt=$(format_cost "$cost_usd") + echo -n "${COST_ICON} ${YELLOW}\$${cost_fmt}${NC}" + + # Calculate burn rate ($/hour) + if [[ -n "$duration_ms" && "$duration_ms" -gt 0 ]] 2>/dev/null; then + local burn_rate + burn_rate=$(echo "$cost_usd $duration_ms" | awk '{printf "%.2f", $1 * 3600000 / $2}') + echo -n " ${GRAY}(\$${burn_rate}/h)${NC}" 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 ""}') +build_token_component() { + local total_input="$1" + local total_output="$2" + local duration_ms="$3" + + local total_tokens=$((total_input + total_output)) + [[ "$total_tokens" -eq 0 ]] && return 0 + + local tokens_fmt + tokens_fmt=$(format_number "$total_tokens") + echo -n "${TOKEN_ICON} ${LIGHT_GRAY}${tokens_fmt} tok${NC}" + + # Calculate TPM + if [[ -n "$duration_ms" && "$duration_ms" -gt 0 ]] 2>/dev/null; then + local tpm + tpm=$(echo "$total_tokens $duration_ms" | awk '{if ($2 > 0) printf "%.0f", $1 * 60000 / $2; else print "0"}') + echo -n " ${GRAY}(${tpm} tpm)${NC}" 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))" +} + +build_time_component() { + local duration_ms="$1" + + [[ -z "$duration_ms" || "$duration_ms" == "0" || "$duration_ms" == "$NULL_VALUE" ]] && return 0 + + local duration_fmt + duration_fmt=$(format_duration "$duration_ms") + [[ -n "$duration_fmt" ]] && echo -n "${TIME_ICON} ${LIGHT_GRAY}${duration_fmt}${NC}" +} + +build_version_component() { + local version="$1" + + [[ -z "$version" || "$version" == "$NULL_VALUE" ]] && return 0 + echo -n "${VERSION_ICON} ${GRAY}v${version}${NC}" +} + +# ============================================================ +# MAIN +# ============================================================ + +main() { + # Read input + local input + input=$(cat) || { + echo "Error: Failed to read stdin" >&2 + exit 1 + } + + log_debug "Status line triggered (v${STATUSLINE_VERSION})" + + # Parse JSON + local parsed + if check_jq; then + parsed=$(parse_with_jq "$input") + log_debug "Using jq for JSON parsing" else - line3="๐Ÿ’ฐ $(cost_color)\$$(printf '%.2f' "$cost_usd")$(rst)" + parsed=$(parse_without_jq "$input") + log_debug "Using bash fallback for JSON parsing" 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)" + + if [[ -z "$parsed" ]]; then + echo "Error: Failed to parse input" >&2 + exit 1 + fi + + # Extract fields + local model_name current_dir context_size current_usage + local total_input total_output cost_usd duration_ms version session_id + { + read -r model_name + read -r current_dir + read -r context_size + read -r current_usage + read -r total_input + read -r total_output + read -r cost_usd + read -r duration_ms + read -r version + read -r session_id + } <<< "$parsed" + + log_debug "Parsed: model=$model_name, dir=$current_dir, context=$current_usage/$context_size, cost=$cost_usd, duration=$duration_ms" + + # Get git info + local git_data + git_data=$(get_git_info "$current_dir") + + # Build components + local output="" + + # Line 1: Model | Directory | Git | Version + output+=$(build_model_component "$model_name") + output+=$(sep) + output+=$(build_directory_component "$current_dir") + + local git_component + git_component=$(build_git_component "$git_data") + [[ -n "$git_component" ]] && { output+=$(sep); output+="$git_component"; } + + local version_component + version_component=$(build_version_component "$version") + [[ -n "$version_component" ]] && { output+=$(sep); output+="$version_component"; } + + # Line 2: Context + output+=$'\n' + output+=$(build_context_component "$context_size" "$current_usage") + + # Line 3: Cost | Tokens | Time + local cost_component token_component time_component + cost_component=$(build_cost_component "$cost_usd" "$duration_ms") + token_component=$(build_token_component "$total_input" "$total_output" "$duration_ms") + time_component=$(build_time_component "$duration_ms") + + if [[ -n "$cost_component" || -n "$token_component" || -n "$time_component" ]]; then + output+=$'\n' + local first=1 + if [[ -n "$cost_component" ]]; then + output+="$cost_component" + first=0 fi - else - if [ -n "$line3" ]; then - line3="$line3 ๐Ÿ“Š $(usage_color)${tot_tokens} tok$(rst)" - else - line3="๐Ÿ“Š $(usage_color)${tot_tokens} tok$(rst)" + if [[ -n "$token_component" ]]; then + [[ "$first" -eq 0 ]] && output+=$(sep) + output+="$token_component" + first=0 + fi + if [[ -n "$time_component" ]]; then + [[ "$first" -eq 0 ]] && output+=$(sep) + output+="$time_component" 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' + + echo -e "$output" +} + +main "$@" -- cgit v1.2.3