Claude Code 네이티브 statusLine 방식의 4줄 상태바 스크립트와 어느 머신에서든 한 줄로 설치·업데이트할 수 있는 스크립트 추가. 크로스플랫폼(Linux/macOS) stat 호환 처리 포함. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
209 lines
6.6 KiB
Bash
Executable File
209 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Claude Code 4-line statusline
|
|
# Reads JSON from stdin, outputs ANSI-colored 4-line status bar
|
|
|
|
# --- Color definitions (bright variants for dark backgrounds) ---
|
|
RED=$'\033[91m'
|
|
GREEN=$'\033[92m'
|
|
YELLOW=$'\033[93m'
|
|
BLUE=$'\033[94m'
|
|
MAGENTA=$'\033[95m'
|
|
CYAN=$'\033[96m'
|
|
GRAY=$'\033[37m'
|
|
WHITE=$'\033[97m'
|
|
BOLD=$'\033[1m'
|
|
DIM=$'\033[2m'
|
|
RESET=$'\033[0m'
|
|
SEP="${GRAY} │ ${RESET}"
|
|
|
|
# --- Read all JSON input ---
|
|
input=$(cat)
|
|
|
|
# --- Single jq call: extract all 16 fields newline-separated ---
|
|
# Using newlines instead of tabs to avoid bash IFS merging consecutive delimiters
|
|
mapfile -t _fields < <(echo "$input" | jq -r '
|
|
(.model.display_name // .model.id // "?"),
|
|
(.model.id // "?"),
|
|
(.context_window.used_percentage // 0 | floor | tostring),
|
|
(.context_window.context_window_size // 0 | tostring),
|
|
(.context_window.total_input_tokens // 0 | tostring),
|
|
(.context_window.total_output_tokens // 0 | tostring),
|
|
(.cost.total_cost_usd // 0 | . * 100 | floor | . / 100 | tostring),
|
|
(.cost.total_duration_ms // 0 | tostring),
|
|
(.cost.total_lines_added // 0 | tostring),
|
|
(.cost.total_lines_removed // 0 | tostring),
|
|
(if .session_id == null or .session_id == "" then "--------" else .session_id end),
|
|
(.version // "?.?.?"),
|
|
(if .output_style.name == null or .output_style.name == "" then "default" else .output_style.name end),
|
|
(.workspace.current_dir // "~"),
|
|
(.vim.mode // ""),
|
|
(.agent.name // "")
|
|
')
|
|
model_name="${_fields[0]}"
|
|
model_id="${_fields[1]}"
|
|
ctx_pct="${_fields[2]}"
|
|
ctx_size="${_fields[3]}"
|
|
total_in_tok="${_fields[4]}"
|
|
total_out_tok="${_fields[5]}"
|
|
cost="${_fields[6]}"
|
|
dur_ms="${_fields[7]}"
|
|
lines_add="${_fields[8]}"
|
|
lines_rm="${_fields[9]}"
|
|
session_id="${_fields[10]}"
|
|
version="${_fields[11]}"
|
|
style="${_fields[12]}"
|
|
cwd="${_fields[13]}"
|
|
vim_mode="${_fields[14]}"
|
|
agent_name="${_fields[15]}"
|
|
|
|
# --- Hostname (cached in variable, never changes) ---
|
|
HOSTNAME_SHORT=$(hostname -s 2>/dev/null || echo "unknown")
|
|
|
|
# --- Context progress bar (12 chars wide) ---
|
|
pct=${ctx_pct:-0}
|
|
filled=$(( pct * 12 / 100 ))
|
|
empty=$(( 12 - filled ))
|
|
|
|
if [ "$pct" -ge 70 ]; then
|
|
bar_color="$RED"
|
|
elif [ "$pct" -ge 50 ]; then
|
|
bar_color="$YELLOW"
|
|
else
|
|
bar_color="$GREEN"
|
|
fi
|
|
|
|
bar_filled=""
|
|
bar_empty=""
|
|
for (( i=0; i<filled; i++ )); do bar_filled+="█"; done
|
|
for (( i=0; i<empty; i++ )); do bar_empty+="░"; done
|
|
progress_bar="${bar_color}${bar_filled}${GRAY}${bar_empty}${RESET}"
|
|
pct_display="${bar_color}${pct}%${RESET}"
|
|
|
|
# --- Cost color ---
|
|
cost_val=${cost:-0}
|
|
# Compare using awk for floating-point
|
|
cost_display=$(awk -v c="$cost_val" '
|
|
BEGIN {
|
|
if (c+0 >= 5) col="\033[91m";
|
|
else if (c+0 >= 2) col="\033[93m";
|
|
else col="\033[92m";
|
|
printf "%s$%.2f\033[0m", col, c+0
|
|
}')
|
|
|
|
# --- Token formatting ---
|
|
format_tokens() {
|
|
local tok=$1
|
|
if [ "$tok" -ge 1000000 ] 2>/dev/null; then
|
|
echo "$(( tok / 1000000 ))M"
|
|
elif [ "$tok" -ge 1000 ] 2>/dev/null; then
|
|
echo "$(( tok / 1000 ))k"
|
|
else
|
|
echo "${tok}"
|
|
fi
|
|
}
|
|
|
|
in_tok_display=$(format_tokens "${total_in_tok:-0}")
|
|
|
|
# --- Model tier symbol ---
|
|
case "$model_id" in
|
|
*opus*) tier_sym="${MAGENTA}${BOLD}◆${RESET}"; model_color="${MAGENTA}${BOLD}" ;;
|
|
*sonnet*) tier_sym="${BLUE}${BOLD}◇${RESET}"; model_color="${BLUE}${BOLD}" ;;
|
|
*haiku*) tier_sym="${GREEN}${BOLD}○${RESET}"; model_color="${GREEN}${BOLD}" ;;
|
|
*) tier_sym="${GRAY}●${RESET}"; model_color="${GRAY}" ;;
|
|
esac
|
|
|
|
# --- Session ID (first 8 chars) ---
|
|
session_short="${session_id:0:8}"
|
|
|
|
# --- Duration formatting ---
|
|
dur_total_sec=$(( ${dur_ms:-0} / 1000 ))
|
|
if [ "$dur_total_sec" -ge 3600 ]; then
|
|
dur_h=$(( dur_total_sec / 3600 ))
|
|
dur_m=$(( (dur_total_sec % 3600) / 60 ))
|
|
dur_display="${dur_h}h ${dur_m}m"
|
|
elif [ "$dur_total_sec" -ge 60 ]; then
|
|
dur_m=$(( dur_total_sec / 60 ))
|
|
dur_s=$(( dur_total_sec % 60 ))
|
|
dur_display="${dur_m}m ${dur_s}s"
|
|
else
|
|
dur_display="${dur_total_sec}s"
|
|
fi
|
|
|
|
# --- Git branch (cached with 5s TTL) ---
|
|
CACHE_FILE="/tmp/claude-statusline-git-cache"
|
|
NOW=$(date +%s)
|
|
GIT_BRANCH=""
|
|
if [ -f "$CACHE_FILE" ]; then
|
|
CACHE_AGE=$(( NOW - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0) ))
|
|
if [ "$CACHE_AGE" -lt 5 ]; then
|
|
GIT_BRANCH=$(cat "$CACHE_FILE")
|
|
fi
|
|
fi
|
|
if [ -z "$GIT_BRANCH" ]; then
|
|
GIT_BRANCH=$(cd "${cwd:-$HOME}" 2>/dev/null && git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
echo "$GIT_BRANCH" > "$CACHE_FILE"
|
|
fi
|
|
|
|
# ============================================================
|
|
# LINE 1: Identity & Resources
|
|
# ============================================================
|
|
line1="\xf0\x9f\x8e\xa8 ${WHITE}${style}${RESET}"
|
|
line1+="${SEP}"
|
|
line1+="\xf0\x9f\x96\xa5\xef\xb8\x8f ${CYAN}${BOLD}${HOSTNAME_SHORT}${RESET}"
|
|
line1+="${SEP}"
|
|
line1+="\xf0\x9f\xa7\xa0 [${progress_bar}] ${pct_display}"
|
|
line1+="${SEP}"
|
|
line1+="\xf0\x9f\x92\xb0 ${cost_display}"
|
|
line1+=" ${GRAY}${in_tok_display} tokens${RESET}"
|
|
|
|
# ============================================================
|
|
# LINE 2: Model & Session
|
|
# ============================================================
|
|
line2="\xf0\x9f\xa4\x96 ${tier_sym} ${model_color}${model_name}${RESET}"
|
|
line2+="${SEP}"
|
|
line2+="\xf0\x9f\x94\x91 ${GRAY}${session_short}${RESET}"
|
|
line2+="${SEP}"
|
|
line2+="\xf0\x9f\x93\x9f ${CYAN}v${version}${RESET}"
|
|
|
|
# ============================================================
|
|
# LINE 3: Workspace & Tools
|
|
# ============================================================
|
|
line3="\xf0\x9f\x93\x81 ${CYAN}${cwd}${RESET}"
|
|
if [ -n "$GIT_BRANCH" ]; then
|
|
line3+="${SEP}"
|
|
line3+="\xf0\x9f\x8c\xbf ${GREEN}${BOLD}${GIT_BRANCH}${RESET}"
|
|
fi
|
|
line3+="${SEP}"
|
|
line3+="\xf0\x9f\x93\x8a ${GREEN}▲+${lines_add}${RESET} ${RED}▼-${lines_rm}${RESET}"
|
|
line3+="${SEP}"
|
|
line3+="\xe2\x8f\xb1\xef\xb8\x8f ${YELLOW}${dur_display}${RESET}"
|
|
|
|
# ============================================================
|
|
# LINE 4: Mode & Status (conditional)
|
|
# ============================================================
|
|
line4="\xe2\x8f\xb5\xe2\x8f\xb5 ${BOLD}${style}${RESET}"
|
|
|
|
if [ -n "$vim_mode" ]; then
|
|
vim_upper=$(echo "$vim_mode" | tr '[:lower:]' '[:upper:]')
|
|
case "$vim_upper" in
|
|
NORMAL) vim_color="$BLUE" ;;
|
|
INSERT) vim_color="$GREEN" ;;
|
|
*) vim_color="$WHITE" ;;
|
|
esac
|
|
line4+="${SEP}"
|
|
line4+="\xf0\x9f\x94\xb2 ${vim_color}${vim_upper}${RESET}"
|
|
fi
|
|
|
|
if [ -n "$agent_name" ]; then
|
|
line4+="${SEP}"
|
|
line4+="\xf0\x9f\x8f\xb7\xef\xb8\x8f ${MAGENTA}${agent_name}${RESET}"
|
|
fi
|
|
|
|
# ============================================================
|
|
# Output all 4 lines
|
|
# ============================================================
|
|
printf '%b\n' "$line1"
|
|
printf '%b\n' "$line2"
|
|
printf '%b\n' "$line3"
|
|
printf '%b\n' "$line4"
|