Files
repo_Claude/.claude/statusline.sh
T
egutierrez bf8651020e feat(statusline): rate-limit burndown colors + reset times
- Show 5h reset as HH:MM and 7d reset as "Day HH:MM" from rate_limits.*.resets_at
- Color 5h/7d pill+arrow by burndown vs expected rate (5h: 20%/h, 7d: 14%/day)
- Green if available% >= expected, yellow if >= expected/2, red otherwise
- Add separator between 5h and 7d blocks
2026-05-08 20:42:48 +02:00

257 lines
8.8 KiB
Bash
Executable File

#!/bin/bash
# Status Line para Claude Code - Optimizado para Vibecoding
# Muestra: modelo, contexto, git, costos, rate limits y más
# Leer JSON desde stdin
INPUT=$(cat)
# Colores ANSI
RESET='\033[0m'
BOLD='\033[1m'
DIM='\033[2m'
CYAN='\033[36m'
GREEN='\033[32m'
YELLOW='\033[33m'
RED='\033[31m'
BLUE='\033[34m'
MAGENTA='\033[35m'
WHITE='\033[37m'
GRAY='\033[90m'
# Extraer datos del JSON
MODEL=$(echo "$INPUT" | jq -r '.model.display_name // "Unknown"')
CONTEXT_PCT=$(echo "$INPUT" | jq -r '.context_window.used_percentage // 0' | xargs printf "%.0f")
CONTEXT_TOTAL=$(echo "$INPUT" | jq -r '.context_window.context_window_size // 200000')
CURRENT_DIR=$(echo "$INPUT" | jq -r '.workspace.current_dir // "~"' | sed "s|$HOME|~|")
# Tokens de entrada y salida (current_usage puede ser null antes del primer API call)
INPUT_TOKENS=$(echo "$INPUT" | jq -r '.context_window.current_usage.input_tokens // 0')
OUTPUT_TOKENS=$(echo "$INPUT" | jq -r '.context_window.current_usage.output_tokens // 0')
CACHE_READ=$(echo "$INPUT" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
CACHE_CREATION=$(echo "$INPUT" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
TOTAL_INPUT=$(echo "$INPUT" | jq -r '.context_window.total_input_tokens // 0')
TOTAL_OUTPUT=$(echo "$INPUT" | jq -r '.context_window.total_output_tokens // 0')
# Calcular contexto usado (suma de todos los tokens de entrada)
CONTEXT_USED=$((INPUT_TOKENS + CACHE_READ + CACHE_CREATION))
# Calcular porcentaje manualmente si el del JSON es 0
if [ "$CONTEXT_PCT" -eq 0 ] && [ "$CONTEXT_USED" -gt 0 ]; then
CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
fi
# Costos
TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f")
SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
LINES_ADDED=$(echo "$INPUT" | jq -r '.cost.total_lines_added // 0')
LINES_REMOVED=$(echo "$INPUT" | jq -r '.cost.total_lines_removed // 0')
# Rate Limits
RATE_5H=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.used_percentage // 0' | xargs printf "%.0f")
RATE_7D=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.used_percentage // 0' | xargs printf "%.0f")
RESET_5H_EPOCH=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.resets_at // 0')
RESET_7D_EPOCH=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.resets_at // 0')
# Formatear resets (vacio si epoch=0)
RESET_5H=""
RESET_7D=""
[ "$RESET_5H_EPOCH" -gt 0 ] && RESET_5H=$(date -d "@$RESET_5H_EPOCH" +"%H:%M" 2>/dev/null)
[ "$RESET_7D_EPOCH" -gt 0 ] && RESET_7D=$(date -d "@$RESET_7D_EPOCH" +"%a %H:%M" 2>/dev/null)
# Git info (si estamos en un repo)
GIT_BRANCH=""
GIT_STATUS=""
if git rev-parse --git-dir > /dev/null 2>&1; then
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "detached")
# Obtener archivos staged, modified, untracked
STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l)
MODIFIED=$(git diff --numstat 2>/dev/null | wc -l)
UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l)
# Commits ahead/behind
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
if [ -n "$UPSTREAM" ]; then
AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "0")
BEHIND=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "0")
else
AHEAD="0"
BEHIND="0"
fi
# Construir status compacto
GIT_STATUS=""
[ "$STAGED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}+${STAGED} "
[ "$MODIFIED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}~${MODIFIED} "
[ "$UNTRACKED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}?${UNTRACKED} "
[ "$AHEAD" -gt 0 ] && GIT_STATUS="${GIT_STATUS}${AHEAD} "
[ "$BEHIND" -gt 0 ] && GIT_STATUS="${GIT_STATUS}${BEHIND} "
# Trim trailing space
GIT_STATUS=$(echo "$GIT_STATUS" | sed 's/ $//')
fi
# Función para crear barra de progreso
progress_bar() {
local pct=$1
local width=10
local filled=$(( pct * width / 100 ))
local empty=$(( width - filled ))
# Color según porcentaje
local color=$GREEN
if [ "$pct" -ge 80 ]; then
color=$RED
elif [ "$pct" -ge 60 ]; then
color=$YELLOW
fi
printf "${color}"
for ((i=0; i<filled; i++)); do printf "█"; done
printf "${GRAY}"
for ((i=0; i<empty; i++)); do printf "░"; done
printf "${RESET}"
}
# Función para formatear duración
format_duration() {
local ms=$1
local seconds=$((ms / 1000))
local minutes=$((seconds / 60))
local hours=$((minutes / 60))
if [ "$hours" -gt 0 ]; then
printf "%dh%dm" $hours $((minutes % 60))
elif [ "$minutes" -gt 0 ]; then
printf "%dm%ds" $minutes $((seconds % 60))
else
printf "%ds" $seconds
fi
}
# Función para formatear tokens (k para miles, M para millones)
format_tokens() {
local tokens=$1
if [ "$tokens" -ge 1000000 ]; then
printf "%.1fM" $(echo "scale=1; $tokens / 1000000" | bc)
elif [ "$tokens" -ge 1000 ]; then
printf "%.0fk" $(echo "scale=0; $tokens / 1000" | bc)
else
printf "%d" $tokens
fi
}
# Hora actual
CURRENT_TIME=$(date +"%H:%M")
# ===== CONSTRUIR STATUS LINE (2 LÍNEAS) =====
# ===== LÍNEA 1: Información Principal =====
LINE1=""
# 1. Modelo
LINE1="${LINE1}${BOLD}${CYAN}${MODEL}${RESET}"
# 2. Contexto con barra y usado/total
CONTEXT_USED_FMT=$(format_tokens $CONTEXT_USED)
CONTEXT_TOTAL_FMT=$(format_tokens $CONTEXT_TOTAL)
LINE1="${LINE1} ${GRAY}${RESET} ${BOLD}CTX:${RESET} $(progress_bar $CONTEXT_PCT) ${YELLOW}${CONTEXT_PCT}%${RESET} ${DIM}(${CONTEXT_USED_FMT}/${CONTEXT_TOTAL_FMT})${RESET}"
# 3. Tokens entrada/salida (formateados)
INPUT_FMT=$(format_tokens $INPUT_TOKENS)
OUTPUT_FMT=$(format_tokens $OUTPUT_TOKENS)
LINE1="${LINE1} ${GRAY}${RESET} ${CYAN}IN:${RESET}${INPUT_FMT} ${MAGENTA}OUT:${RESET}${OUTPUT_FMT}"
if [ "$CACHE_READ" -gt 0 ]; then
CACHE_FMT=$(format_tokens $CACHE_READ)
LINE1="${LINE1} ${DIM}(cache:${CACHE_FMT})${RESET}"
fi
# 4. Git (si existe)
if [ -n "$GIT_BRANCH" ]; then
LINE1="${LINE1} ${GRAY}${RESET} ${BOLD}${MAGENTA}${GIT_BRANCH}${RESET}"
if [ -n "$GIT_STATUS" ]; then
LINE1="${LINE1} ${DIM}[${GIT_STATUS}]${RESET}"
fi
fi
# 5. Hora actual
LINE1="${LINE1} ${GRAY}${RESET} ${WHITE}${CURRENT_TIME}${RESET}"
# ===== LÍNEA 2: Detalles =====
LINE2=""
# 1. Costos
if (( $(echo "$TOTAL_COST > 0" | bc -l) )); then
LINE2="${LINE2}${BOLD}${GREEN}\$${TOTAL_COST}${RESET}"
else
LINE2="${LINE2}${DIM}\$0.000${RESET}"
fi
# 2. Ediciones
LINE2="${LINE2} ${GRAY}${RESET} ${GREEN}+${LINES_ADDED}${RESET}${GRAY}/${RED}-${LINES_REMOVED}${RESET}"
# 3. Tokens totales acumulados (formateados)
TOTAL_IN_FMT=$(format_tokens $TOTAL_INPUT)
TOTAL_OUT_FMT=$(format_tokens $TOTAL_OUTPUT)
LINE2="${LINE2} ${GRAY}${RESET} ${DIM}Total:${RESET} ${CYAN}${TOTAL_IN_FMT}${RESET}${GRAY}/${MAGENTA}${TOTAL_OUT_FMT}${RESET}"
# 4. Rate Limits (siempre mostrar)
LINE2="${LINE2} ${GRAY}${RESET} ${BOLD}Limits:${RESET}"
# Color por burndown vs tasa esperada
# Tasa: % consumible permitido por unidad de tiempo (5h: 20%/h, 7d: 14%/dia)
# expected = tasa * unidades_restantes_hasta_reset
# available = 100 - used%
# verde: available >= expected (consumo bajo control)
# amarillo: available >= expected/2 (consumo agresivo)
# rojo: available < expected/2 (riesgo de agotar antes del reset)
NOW_EPOCH=$(date +%s)
burndown_color() {
local used_pct=$1
local secs_left=$2
local rate=$3
local secs_per_unit=$4
local available=$((100 - used_pct))
if [ "$secs_left" -le 0 ]; then
printf "%s" "$GREEN"; return
fi
local expected
expected=$(echo "scale=2; $rate * $secs_left / $secs_per_unit" | bc)
if (( $(echo "$available >= $expected" | bc -l) )); then
printf "%s" "$GREEN"
elif (( $(echo "$available >= $expected / 2" | bc -l) )); then
printf "%s" "$YELLOW"
else
printf "%s" "$RED"
fi
}
# 5h limit (tasa 20%/h)
SECS_5H=0
[ "$RESET_5H_EPOCH" -gt "$NOW_EPOCH" ] && SECS_5H=$((RESET_5H_EPOCH - NOW_EPOCH))
C5=$(burndown_color $RATE_5H $SECS_5H 20 3600)
RESET_5H_STR=""
[ -n "$RESET_5H" ] && RESET_5H_STR=" ${C5}${RESET_5H}${RESET}"
LINE2="${LINE2} ${C5}5h:${RATE_5H}%${RESET}${RESET_5H_STR} ${GRAY}${RESET}"
# 7d limit (tasa 14%/dia)
SECS_7D=0
[ "$RESET_7D_EPOCH" -gt "$NOW_EPOCH" ] && SECS_7D=$((RESET_7D_EPOCH - NOW_EPOCH))
C7=$(burndown_color $RATE_7D $SECS_7D 14 86400)
RESET_7D_STR=""
[ -n "$RESET_7D" ] && RESET_7D_STR=" ${C7}${RESET_7D}${RESET}"
LINE2="${LINE2} ${C7}7d:${RATE_7D}%${RESET}${RESET_7D_STR}"
# 5. Duración sesión
if [ "$SESSION_DURATION" -gt 0 ]; then
DURATION_STR=$(format_duration $SESSION_DURATION)
LINE2="${LINE2} ${GRAY}${RESET} ${DIM}${DURATION_STR}${RESET}"
fi
# 6. Directorio actual
LINE2="${LINE2} ${GRAY}${RESET} ${BLUE}${CURRENT_DIR}${RESET}"
# Imprimir resultado (2 líneas)
echo -e "$LINE1"
echo -e "$LINE2"