Created
June 14, 2026 03:43
-
-
Save bearlike/d72d4a67556e7823d6aa8d6402c556e0 to your computer and use it in GitHub Desktop.
Claude Code status line script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Claude Code status line script (Nerd Font enhanced - nf-md icons) | |
| # Personal edition | |
| input=$(cat) | |
| cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""') | |
| host_part="$(whoami)@$(hostname -s)" | |
| model_name=$(echo "$input" | jq -r '.model.display_name // ""') | |
| used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty') | |
| ctx_total=$(echo "$input" | jq -r '.context_window.context_window_size // 0') | |
| ctx_used=$(echo "$input" | jq -r '.context_window.tokens_used // 0') | |
| five_hour_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty') | |
| five_hour_resets=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty') | |
| seven_day_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty') | |
| seven_day_resets=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty') | |
| # Try JSON input first, fall back to settings.json | |
| effort=$(echo "$input" | jq -r '.effortLevel // empty') | |
| [ -z "$effort" ] && effort=$(jq -r '.effortLevel // empty' ~/.claude/settings.json 2>/dev/null) | |
| # Shorten cwd: replace $HOME with ~ | |
| home_dir="$HOME" | |
| short_dir="${cwd/#$home_dir/\~}" | |
| # ANSI colors | |
| green=$'\033[32m' | |
| red=$'\033[31m' | |
| yellow=$'\033[33m' | |
| cyan=$'\033[36m' | |
| blue=$'\033[34m' | |
| magenta=$'\033[35m' | |
| bold=$'\033[1m' | |
| reset=$'\033[0m' | |
| # Nerd Font icons with colors baked in | |
| icon_stash="${yellow}${reset}" | |
| icon_gauge="${cyan}${reset}" | |
| icon_folder="${blue}${reset}" | |
| icon_branch="${magenta}${reset}" | |
| icon_clock="${yellow}${reset}" | |
| icon_load="${green}${reset}" | |
| icon_user="${cyan}${reset}" | |
| icon_pr="${blue}${reset}" | |
| icon_team="${magenta}${reset}" | |
| icon_limit="${red}${reset}" | |
| # Git info | |
| git_part="" | |
| branch="" | |
| pr_part="" | |
| if git -C "$cwd" rev-parse --git-dir >/dev/null 2>&1; then | |
| # Use repo toplevel so counts cover the entire repo, not just $cwd subdir | |
| repo_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) | |
| branch=$(git -C "$repo_root" -c core.hooksPath=/dev/null symbolic-ref --short HEAD 2>/dev/null || git -C "$repo_root" rev-parse --short HEAD 2>/dev/null) | |
| stash_count=$(git -C "$repo_root" -c core.hooksPath=/dev/null stash list 2>/dev/null | wc -l | tr -d ' ') | |
| staged=$(git -C "$repo_root" -c core.hooksPath=/dev/null diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ') | |
| unstaged=$(git -C "$repo_root" -c core.hooksPath=/dev/null diff --numstat 2>/dev/null | wc -l | tr -d ' ') | |
| untracked=$(git -C "$repo_root" -c core.hooksPath=/dev/null ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ') | |
| stash_part="" | |
| [ "$stash_count" -gt 0 ] && stash_part=" ${icon_stash} stash:${stash_count}" | |
| untracked_part="" | |
| [ "$untracked" -gt 0 ] && untracked_part=" ${yellow}?${untracked}${reset}" | |
| git_part=" | ${icon_branch} ${branch}${stash_part} ${green}+${staged}${reset} ${red}-${unstaged}${reset}${untracked_part}" | |
| # PR detection: use a per-branch cache file (TTL 120s) to avoid gh slowness | |
| if [ -n "$branch" ] && command -v gh >/dev/null 2>&1; then | |
| cache_dir="${TMPDIR:-/tmp}/claude-statusline-pr" | |
| mkdir -p "$cache_dir" | |
| # Sanitise branch name for use as a filename | |
| cache_key=$(printf '%s' "$branch" | tr '/' '_' | tr -dc '[:alnum:]_.-') | |
| cache_file="$cache_dir/${cache_key}.cache" | |
| cache_ttl=120 # seconds | |
| pr_cached="" | |
| if [ -f "$cache_file" ]; then | |
| file_age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) | |
| [ "$file_age" -lt "$cache_ttl" ] && pr_cached=$(cat "$cache_file" 2>/dev/null) | |
| fi | |
| if [ -z "$pr_cached" ]; then | |
| # Run gh with a short timeout; write result to cache even if empty | |
| pr_json=$(cd "$repo_root" && timeout 3 gh pr view --json number,title,state 2>/dev/null) | |
| pr_number=$(echo "$pr_json" | jq -r '.number // empty' 2>/dev/null) | |
| pr_state=$(echo "$pr_json" | jq -r '.state // empty' 2>/dev/null) | |
| if [ -n "$pr_number" ]; then | |
| pr_cached="${pr_number}:${pr_state}" | |
| else | |
| pr_cached="none" | |
| fi | |
| printf '%s' "$pr_cached" > "$cache_file" | |
| fi | |
| if [ "$pr_cached" != "none" ] && [ -n "$pr_cached" ]; then | |
| pr_number="${pr_cached%%:*}" | |
| pr_state="${pr_cached##*:}" | |
| case "$pr_state" in | |
| OPEN) pr_color="$green" ;; | |
| MERGED) pr_color="$magenta" ;; | |
| CLOSED) pr_color="$red" ;; | |
| *) pr_color="$cyan" ;; | |
| esac | |
| pr_part=" | ${icon_pr} ${pr_color}PR #${pr_number}${reset}" | |
| fi | |
| fi | |
| fi | |
| # Format seconds-until-reset as "Xh Ym" or "Ym" | |
| fmt_reset_in() { | |
| local epoch=$1 | |
| [ -z "$epoch" ] && return | |
| local now secs_left mins hours | |
| now=$(date +%s) | |
| secs_left=$(( epoch - now )) | |
| [ "$secs_left" -le 0 ] && return | |
| mins=$(( secs_left / 60 )) | |
| hours=$(( mins / 60 )) | |
| mins=$(( mins % 60 )) | |
| if [ "$hours" -gt 0 ]; then | |
| printf '%dh %dm' "$hours" "$mins" | |
| else | |
| printf '%dm' "$mins" | |
| fi | |
| } | |
| # Rate limit parts | |
| rate_limit_part="" | |
| if [ -n "$five_hour_pct" ] || [ -n "$seven_day_pct" ]; then | |
| rl_segments="" | |
| if [ -n "$five_hour_pct" ]; then | |
| fh_int=$(printf "%.0f" "$five_hour_pct" 2>/dev/null || echo "$five_hour_pct") | |
| if [ "$fh_int" -ge 80 ] 2>/dev/null; then | |
| fh_color="$red" | |
| elif [ "$fh_int" -ge 50 ] 2>/dev/null; then | |
| fh_color="$yellow" | |
| else | |
| fh_color="$green" | |
| fi | |
| fh_reset=$(fmt_reset_in "$five_hour_resets") | |
| fh_label="${fh_color}5h:${fh_int}%${reset}" | |
| [ -n "$fh_reset" ] && fh_label="${fh_label} ${dim}(resets in ${fh_reset})${reset}" | |
| rl_segments="$fh_label" | |
| fi | |
| if [ -n "$seven_day_pct" ]; then | |
| sd_int=$(printf "%.0f" "$seven_day_pct" 2>/dev/null || echo "$seven_day_pct") | |
| if [ "$sd_int" -ge 80 ] 2>/dev/null; then | |
| sd_color="$red" | |
| elif [ "$sd_int" -ge 50 ] 2>/dev/null; then | |
| sd_color="$yellow" | |
| else | |
| sd_color="$green" | |
| fi | |
| sd_reset=$(fmt_reset_in "$seven_day_resets") | |
| sd_label="${sd_color}7d:${sd_int}%${reset}" | |
| [ -n "$sd_reset" ] && sd_label="${sd_label} ${dim}(resets in ${sd_reset})${reset}" | |
| if [ -n "$rl_segments" ]; then | |
| rl_segments="${rl_segments} ${sd_label}" | |
| else | |
| rl_segments="$sd_label" | |
| fi | |
| fi | |
| rate_limit_part=" | ${icon_limit} ${rl_segments}" | |
| fi | |
| # Format token count as K or M | |
| fmt_tokens() { | |
| local n=$1 | |
| if [ "$n" -ge 1000000 ] 2>/dev/null; then | |
| awk "BEGIN {printf \"%.1fM\", $n/1000000}" | |
| elif [ "$n" -ge 1000 ] 2>/dev/null; then | |
| awk "BEGIN {printf \"%dK\", $n/1000}" | |
| else | |
| echo "${n}" | |
| fi | |
| } | |
| # Context usage with color coding and token counts | |
| ctx_part="" | |
| if [ -n "$used_pct" ]; then | |
| pct_int=$(printf "%.0f" "$used_pct" 2>/dev/null || echo "$used_pct") | |
| if [ "$pct_int" -ge 75 ] 2>/dev/null; then | |
| pct_color="$red" | |
| elif [ "$pct_int" -ge 50 ] 2>/dev/null; then | |
| pct_color="$yellow" | |
| else | |
| pct_color="$cyan" | |
| fi | |
| # Calculate used tokens from percentage if not provided directly | |
| if [ "$ctx_used" -eq 0 ] && [ "$ctx_total" -gt 0 ] 2>/dev/null; then | |
| ctx_used=$(awk "BEGIN {printf \"%d\", $used_pct/100 * $ctx_total}") | |
| fi | |
| used_fmt=$(fmt_tokens "$ctx_used") | |
| total_fmt=$(fmt_tokens "$ctx_total") | |
| ctx_part=" | ${icon_gauge} ctx ${pct_color}${pct_int}% (${used_fmt} / ${total_fmt})${reset}" | |
| fi | |
| # Load average (1-min) | |
| load_1m=$(awk '{print $1}' /proc/loadavg 2>/dev/null || sysctl -n vm.loadavg 2>/dev/null | awk '{print $2}') | |
| # Color-code load: <2 green, 2-4 yellow, >=4 red (rough heuristic) | |
| load_color="$green" | |
| load_int=$(printf "%.0f" "${load_1m:-0}" 2>/dev/null || echo 0) | |
| [ "$load_int" -ge 4 ] 2>/dev/null && load_color="$red" | |
| [ "$load_int" -ge 2 ] && [ "$load_int" -lt 4 ] 2>/dev/null && load_color="$yellow" | |
| load_part="${load_color}${load_1m}${reset}" | |
| # Time | |
| time_part=$(date +%I:%M%p | sed 's/^0//') | |
| # Effort label | |
| effort_part="" | |
| [ -n "$effort" ] && effort_part=" ${yellow}[${effort}]${reset}" | |
| # Personal branded prefix | |
| team_part="${bold}${cyan}Personal${reset}" | |
| # Divider sized to terminal width (fallback 120) | |
| dim=$'\033[2m' | |
| width=${COLUMNS:-$(tput cols 2>/dev/null || echo 120)} | |
| divider=$(printf '─%.0s' $(seq 1 "$width")) | |
| printf "%s | %s %s | %s %s%s%s\n %s%s%s%s | %s load:%s | %s %s\n%s%s%s" \ | |
| "$team_part" \ | |
| "$icon_user" \ | |
| "$host_part" \ | |
| "$icon_folder" \ | |
| "$short_dir" \ | |
| "$git_part" \ | |
| "$pr_part" \ | |
| "$model_name" \ | |
| "$effort_part" \ | |
| "$ctx_part" \ | |
| "$rate_limit_part" \ | |
| "$icon_load" \ | |
| "$load_part" \ | |
| "$icon_clock" \ | |
| "$time_part" \ | |
| "$dim" \ | |
| "$divider" \ | |
| "$reset" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment