|
#!/bin/bash |
|
set -euo pipefail |
|
|
|
# ============================================================ |
|
# CONFIGURATION |
|
# ============================================================ |
|
readonly BAR_WIDTH=15 |
|
readonly BAR_FILLED="█" |
|
readonly BAR_EMPTY="░" |
|
|
|
readonly RED='\033[0;31m' |
|
readonly GREEN='\033[0;32m' |
|
readonly BLUE='\033[0;34m' |
|
readonly MAGENTA='\033[0;35m' |
|
readonly CYAN='\033[0;36m' |
|
readonly ORANGE='\033[0;33m' |
|
readonly GRAY='\033[0;90m' |
|
readonly NC='\033[0m' |
|
|
|
# ============================================================ |
|
# FUNCTIONS |
|
# ============================================================ |
|
|
|
parse_claude_input() { |
|
local input="$1" |
|
|
|
local parsed |
|
parsed=$(echo "$input" | jq -r ' |
|
.model.display_name, |
|
.workspace.current_dir, |
|
(.context_window.context_window_size // 200000), |
|
(.context_window.total_input_tokens + .context_window.total_output_tokens) |
|
' 2>/dev/null) || { |
|
echo "Error: Failed to parse JSON input" >&2 |
|
return 1 |
|
} |
|
|
|
echo "$parsed" |
|
} |
|
|
|
build_progress_bar() { |
|
local percent="$1" |
|
local width="${2:-$BAR_WIDTH}" |
|
|
|
local filled=$((percent * width / 100)) |
|
local empty=$((width - filled)) |
|
|
|
local bar empty_bar |
|
printf -v bar '%*s' "$filled" '' |
|
bar=${bar// /$BAR_FILLED} |
|
printf -v empty_bar '%*s' "$empty" '' |
|
echo "${bar}${empty_bar// /$BAR_EMPTY}" |
|
} |
|
|
|
get_ahead_behind() { |
|
local git_opts=("$@") |
|
|
|
local upstream counts |
|
upstream=$(git "${git_opts[@]}" rev-parse --abbrev-ref '@{upstream}' 2>/dev/null) && \ |
|
counts=$(git "${git_opts[@]}" rev-list --left-right --count HEAD..."$upstream" 2>/dev/null) && \ |
|
echo "$counts" | awk '{print $1 "|" $2}' || echo "0|0" |
|
} |
|
|
|
append_ahead_behind() { |
|
local ahead="$1" |
|
local behind="$2" |
|
local output="" |
|
|
|
if [ "$ahead" -gt 0 ] || [ "$behind" -gt 0 ]; then |
|
output="${GRAY}|${NC}" |
|
[ "$ahead" -gt 0 ] && output+=" ${GREEN}↑${ahead}${NC}" |
|
[ "$behind" -gt 0 ] && output+=" ${RED}↓${behind}${NC}" |
|
fi |
|
|
|
echo "$output" |
|
} |
|
|
|
get_git_stats() { |
|
local current_dir="$1" |
|
local git_opts=() |
|
|
|
# Build git -C option if directory specified |
|
if [ -n "$current_dir" ] && [ "$current_dir" != "null" ]; then |
|
git_opts=(-C "$current_dir") |
|
fi |
|
|
|
git "${git_opts[@]}" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { |
|
echo "not_repo" |
|
return 0 |
|
} |
|
|
|
local branch |
|
branch=$(git "${git_opts[@]}" branch --show-current 2>/dev/null || echo "detached") |
|
|
|
local status_output |
|
status_output=$(git "${git_opts[@]}" status --porcelain 2>/dev/null) || { |
|
echo "not_repo" |
|
return 0 |
|
} |
|
|
|
local ahead_behind |
|
ahead_behind=$(get_ahead_behind "${git_opts[@]}") |
|
|
|
# Clean state |
|
if [ -z "$status_output" ]; then |
|
echo "clean|$branch|$ahead_behind" |
|
return 0 |
|
fi |
|
|
|
# Dirty state: count files and lines |
|
local total_files |
|
total_files=$(echo "$status_output" | wc -l | tr -d ' ') |
|
|
|
# Combine staged and unstaged diffs in single awk pass |
|
local added removed |
|
read -r added removed < <( |
|
{ |
|
git "${git_opts[@]}" diff --numstat --cached 2>/dev/null |
|
git "${git_opts[@]}" diff --numstat 2>/dev/null |
|
} | awk '{added+=$1; removed+=$2} END {print added+0, removed+0}' |
|
) |
|
|
|
echo "dirty|$branch|$total_files|$added|$removed|$ahead_behind" |
|
} |
|
|
|
format_git_info() { |
|
local git_data="$1" |
|
|
|
local state branch rest |
|
IFS='|' read -r state branch rest <<< "$git_data" |
|
|
|
case "$state" in |
|
not_repo) |
|
echo " ${ORANGE}(not a git repository)${NC}" |
|
;; |
|
clean) |
|
local ahead_behind="$rest" |
|
local ahead behind |
|
IFS='|' read -r ahead behind <<< "$ahead_behind" |
|
|
|
local output="${GRAY}(${NC}${MAGENTA}$branch${NC}" |
|
local ahead_behind_str |
|
ahead_behind_str=$(append_ahead_behind "$ahead" "$behind") |
|
[ -n "$ahead_behind_str" ] && output+=" $ahead_behind_str" |
|
output+="${GRAY})${NC}" |
|
|
|
echo " $output" |
|
;; |
|
dirty) |
|
local total_files added removed ahead_behind |
|
IFS='|' read -r total_files added removed ahead_behind <<< "$rest" |
|
local ahead behind |
|
IFS='|' read -r ahead behind <<< "$ahead_behind" |
|
|
|
local output="${GRAY}(${NC}${MAGENTA}$branch${NC} ${GRAY}|${NC} ${GRAY}${total_files} files${NC}" |
|
[ "$added" -gt 0 ] && output+=" ${GREEN}+${added}${NC}" |
|
[ "$removed" -gt 0 ] && output+=" ${RED}-${removed}${NC}" |
|
|
|
local ahead_behind_str |
|
ahead_behind_str=$(append_ahead_behind "$ahead" "$behind") |
|
[ -n "$ahead_behind_str" ] && output+=" $ahead_behind_str" |
|
output+="${GRAY})${NC}" |
|
|
|
echo " $output" |
|
;; |
|
esac |
|
} |
|
|
|
# ============================================================ |
|
# MAIN |
|
# ============================================================ |
|
|
|
main() { |
|
command -v jq >/dev/null 2>&1 || { |
|
echo "Error: jq required" >&2 |
|
exit 1 |
|
} |
|
|
|
local input |
|
input=$(< /dev/stdin) |
|
|
|
local parsed |
|
parsed=$(parse_claude_input "$input") || exit 1 |
|
|
|
local model_name current_dir context_size current_usage |
|
{ |
|
read -r model_name |
|
read -r current_dir |
|
read -r context_size |
|
read -r current_usage |
|
} <<< "$parsed" |
|
|
|
# Calculate context percentage |
|
local context_percent=0 |
|
if [[ "$current_usage" != "0" && "$context_size" -gt 0 ]]; then |
|
context_percent=$((current_usage * 100 / context_size)) |
|
fi |
|
|
|
# Build progress bar |
|
local bar |
|
bar=$(build_progress_bar "$context_percent" "$BAR_WIDTH") |
|
|
|
# Extract directory name |
|
local dir_name |
|
if [ -n "$current_dir" ] && [ "$current_dir" != "null" ]; then |
|
dir_name=$(basename "$current_dir") |
|
else |
|
dir_name=$(basename "$PWD") |
|
fi |
|
|
|
# Get git information |
|
local git_data git_info |
|
git_data=$(get_git_stats "$current_dir") |
|
git_info=$(format_git_info "$git_data") |
|
|
|
# Build final output |
|
local context_info="${GRAY}${bar}${NC} ${context_percent}%" |
|
echo -e "${BLUE}${dir_name}${NC} ${GRAY}|${NC} ${CYAN}${model_name}${NC} ${GRAY}|${NC} ${context_info}${git_info:+ ${GRAY}|${NC}}${git_info}" |
|
} |
|
|
|
main "$@" |