|
#!/usr/bin/env bash |
|
input=$(cat) |
|
|
|
model=$(echo "$input" | jq -r '.model.display_name // "?"') |
|
effort=$(echo "$input" | jq -r '.effort.level // empty') |
|
thinking=$(echo "$input" | jq -r '.thinking.enabled // false') |
|
|
|
project=$(basename "$(echo "$input" | jq -r '.workspace.project_dir // .cwd')") |
|
branch=$(git --git-dir="$(echo "$input" | jq -r '.workspace.project_dir // .cwd')/.git" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null) |
|
cache_dir="$HOME/.claude/_statusline" |
|
mkdir -p "$cache_dir" |
|
|
|
# Rate limits: account-wide, cached globally. Prefer incoming; fall back to |
|
# cached only while its resets_at is still in the future. |
|
rate_cache="$cache_dir/_rate_limits.json" |
|
rate_current=$(echo "$input" | jq '{ |
|
h5: .rate_limits.five_hour.used_percentage, |
|
h5_reset: .rate_limits.five_hour.resets_at, |
|
d7: .rate_limits.seven_day.used_percentage, |
|
d7_reset: .rate_limits.seven_day.resets_at |
|
}') |
|
now_epoch=$(date +%s) |
|
if [ -f "$rate_cache" ]; then |
|
rate_merged=$(jq -n --argjson a "$rate_current" --slurpfile b "$rate_cache" --argjson now "$now_epoch" ' |
|
($b[0]) as $c | |
|
(if (($c.h5_reset // 0) > $now) then $c else $c + {h5: null, h5_reset: null} end) as $c | |
|
(if (($c.d7_reset // 0) > $now) then $c else $c + {d7: null, d7_reset: null} end) as $c | |
|
{ |
|
h5: ($a.h5 // $c.h5), |
|
h5_reset: ($a.h5_reset // $c.h5_reset), |
|
d7: ($a.d7 // $c.d7), |
|
d7_reset: ($a.d7_reset // $c.d7_reset) |
|
}') |
|
else |
|
rate_merged="$rate_current" |
|
fi |
|
printf '%s' "$rate_merged" > "$rate_cache.tmp" && mv "$rate_cache.tmp" "$rate_cache" |
|
|
|
h5=$(echo "$rate_merged" | jq -r '.h5 // "–" | if type == "number" then round | tostring else . end') |
|
d7=$(echo "$rate_merged" | jq -r '.d7 // "–" | if type == "number" then round | tostring else . end') |
|
h5_reset=$(echo "$rate_merged" | jq -r '.h5_reset // empty') |
|
d7_reset=$(echo "$rate_merged" | jq -r '.d7_reset // empty') |
|
|
|
# Cumulative session fields (cost/duration/lines) are merged with a per-session |
|
# cache via max so they survive a transient 0 at resume. Context-window state |
|
# (ctx/in_tok/out_tok/exceeds_200k) is NOT cumulative — it drops after /compact — |
|
# so those are taken from the live payload, never the stale cached peak. |
|
session_id=$(echo "$input" | jq -r '.session_id // empty') |
|
cache_file="" |
|
current=$(echo "$input" | jq '{ |
|
cost: (.cost.total_cost_usd // 0), |
|
dur: (.cost.total_duration_ms // 0), |
|
api_dur: (.cost.total_api_duration_ms // 0), |
|
lines_add: (.cost.total_lines_added // 0), |
|
lines_del: (.cost.total_lines_removed // 0), |
|
in_tok: (.context_window.total_input_tokens // 0), |
|
out_tok: (.context_window.total_output_tokens // 0), |
|
ctx: (.context_window.used_percentage // 0), |
|
exceeds_200k: (.exceeds_200k_tokens // false) |
|
}') |
|
|
|
if [ -n "$session_id" ]; then |
|
cache_file="$cache_dir/${session_id}.json" |
|
fi |
|
|
|
if [ -n "$cache_file" ] && [ -f "$cache_file" ]; then |
|
merged=$(jq -n --argjson a "$current" --slurpfile b "$cache_file" ' |
|
($b[0]) as $c | |
|
{ |
|
cost: ([$a.cost, $c.cost] | max), |
|
dur: ([$a.dur, $c.dur] | max), |
|
api_dur: ([$a.api_dur, $c.api_dur] | max), |
|
lines_add: ([$a.lines_add, $c.lines_add] | max), |
|
lines_del: ([$a.lines_del, $c.lines_del] | max), |
|
in_tok: $a.in_tok, |
|
out_tok: $a.out_tok, |
|
ctx: $a.ctx, |
|
exceeds_200k: $a.exceeds_200k |
|
}') |
|
else |
|
merged="$current" |
|
fi |
|
|
|
if [ -n "$cache_file" ]; then |
|
printf '%s' "$merged" > "$cache_file.tmp" && mv "$cache_file.tmp" "$cache_file" |
|
fi |
|
|
|
cost=$(echo "$merged" | jq -r '.cost | . * 100 | round | . / 100') |
|
ctx=$(echo "$merged" | jq -r '.ctx | round') |
|
exceeds_200k=$(echo "$merged" | jq -r '.exceeds_200k') |
|
lines_add=$(echo "$merged" | jq -r '.lines_add') |
|
lines_del=$(echo "$merged" | jq -r '.lines_del') |
|
dur=$(echo "$merged" | jq -r '.dur') |
|
api_dur=$(echo "$merged" | jq -r '.api_dur') |
|
in_tok=$(echo "$merged" | jq -r '.in_tok') |
|
out_tok=$(echo "$merged" | jq -r '.out_tok') |
|
|
|
fmt_duration() { |
|
local ms="$1" total_mins days hrs mins |
|
total_mins=$(( ms / 60000 )) |
|
days=$(( total_mins / 1440 )) |
|
hrs=$(( (total_mins % 1440) / 60 )) |
|
mins=$(( total_mins % 60 )) |
|
if [ "$days" -gt 0 ]; then echo "${days}d${hrs}h${mins}m" |
|
elif [ "$hrs" -gt 0 ]; then echo "${hrs}h${mins}m" |
|
else echo "${mins}m"; fi |
|
} |
|
|
|
fmt_tokens() { |
|
local val="$1" |
|
if [ "$val" -ge 1000000 ]; then |
|
printf "%.1fM" "$(echo "$val" | awk '{printf "%.1f", $1/1000000}')" |
|
elif [ "$val" -ge 1000 ]; then |
|
printf "%.1fk" "$(echo "$val" | awk '{printf "%.1f", $1/1000}')" |
|
else |
|
printf "%s" "$val" |
|
fi |
|
} |
|
|
|
elapsed=$(fmt_duration "$dur") |
|
api_elapsed=$(fmt_duration "$api_dur") |
|
|
|
# Colors |
|
G='\033[32m' Y='\033[33m' R='\033[31m' BR='\033[1;31m' # pct thresholds |
|
X='\033[0m' # reset |
|
CYAN='\033[36m' # model |
|
MAG='\033[35m' # repo/branch |
|
BLUE='\033[34m' # uptime |
|
YEL='\033[33m' # cost |
|
D='\033[90m' # labels/separators |
|
|
|
fmt_reset_countdown() { |
|
local epoch="$1" |
|
if [ -z "$epoch" ]; then printf "${D}–${X}"; return; fi |
|
local now diff h m |
|
now=$(date +%s) |
|
diff=$(( epoch - now )) |
|
if [ "$diff" -le 0 ]; then printf "${G}now${X}"; return; fi |
|
h=$(( diff / 3600 )) |
|
m=$(( (diff % 3600) / 60 )) |
|
if [ "$h" -gt 0 ]; then printf "${D}%dh%dm${X}" "$h" "$m" |
|
else printf "${D}%dm${X}" "$m"; fi |
|
} |
|
|
|
fmt_reset_date() { |
|
local epoch="$1" |
|
if [ -z "$epoch" ]; then printf "${D}–${X}"; return; fi |
|
if [ "$epoch" -le "$(date +%s)" ] 2>/dev/null; then printf "${G}now${X}"; return; fi |
|
printf "${D}%s${X}" "$(date -r "$epoch" '+%a %H:%M')" |
|
} |
|
|
|
color_pct() { |
|
local val="$1" |
|
[ "$val" = "–" ] && printf "${D}–%%${X}" && return |
|
if [ "$val" -ge 80 ]; then printf "${R}%s%%${X}" "$val" |
|
elif [ "$val" -ge 50 ]; then printf "${Y}%s%%${X}" "$val" |
|
else printf "${G}%s%%${X}" "$val"; fi |
|
} |
|
|
|
color_ctx() { |
|
local val="$1" over="$2" |
|
[ "$over" = "true" ] && printf "${BR}%s%%${X}" "$val" && return |
|
color_pct "$val" |
|
} |
|
|
|
wt_name=$(echo "$input" | jq -r '.worktree.name // empty') |
|
wt_branch=$(echo "$input" | jq -r '.worktree.branch // empty') |
|
|
|
if [ -n "$wt_name" ]; then |
|
# In worktree mode, resolve actual repo name from git's commondir |
|
proj_dir=$(echo "$input" | jq -r '.workspace.project_dir // .cwd') |
|
orig=$(basename "$(dirname "$(git -C "$proj_dir" rev-parse --git-common-dir 2>/dev/null)")") |
|
repo="${orig:-$project}[wt:${wt_name}]" |
|
else |
|
repo="${project}" |
|
[ -n "$branch" ] && repo="${project}:${branch}" |
|
fi |
|
|
|
model_display="${CYAN}${model}${X}" |
|
if [ "$thinking" = "true" ] && [ -n "$effort" ]; then |
|
model_display="${CYAN}${model}${X} ${Y}${effort}${X}" |
|
fi |
|
|
|
printf "%b ${D}│${X} 5h:%b${D}→${X}%b ${D}│${X} 7d:%b${D}→${X}%b ${D}│${X} ctx:%b ${D}↑${X}%s ${D}↓${X}%s ${D}│${X} ${YEL}\$%s${X} ${D}│${X} ${BLUE}up:%s api:%s${X} ${D}│${X} ${G}+%s${X}${D}/${X}${R}-%s${X} ${D}│${X} ${MAG}%s${X}" \ |
|
"$model_display" \ |
|
"$(color_pct "$h5")" "$(fmt_reset_countdown "$h5_reset")" \ |
|
"$(color_pct "$d7")" "$(fmt_reset_date "$d7_reset")" \ |
|
"$(color_ctx "$ctx" "$exceeds_200k")" "$(fmt_tokens "$in_tok")" "$(fmt_tokens "$out_tok")" \ |
|
"$cost" "$elapsed" "$api_elapsed" \ |
|
"$lines_add" "$lines_del" \ |
|
"$repo" |