Skip to content

Instantly share code, notes, and snippets.

@jwarkentin
Last active June 4, 2026 07:07
Show Gist options
  • Select an option

  • Save jwarkentin/855dc8e92d5144c6966fcaebdafa181d to your computer and use it in GitHub Desktop.

Select an option

Save jwarkentin/855dc8e92d5144c6966fcaebdafa181d to your computer and use it in GitHub Desktop.
Claude Code status line and related script(s)
#!/usr/bin/env python3
"""Quick cost summary for statusline session state.
Usage:
./session-cost-summary.py [path-to-state.json] [--top N]
Each session's total cost is `prior` (accumulated before the last update)
plus `last` (the most recent segment).
"""
import argparse
import json
import os
import sys
from datetime import datetime
DEFAULT_STATE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"statusline-session-state.json")
def main():
p = argparse.ArgumentParser(description="Summarize session costs.")
p.add_argument("state", nargs="?", default=DEFAULT_STATE,
help="Path to statusline-session-state.json")
p.add_argument("--top", type=int, default=5,
help="How many top sessions to list (default 5)")
args = p.parse_args()
try:
with open(args.state) as f:
data = json.load(f)
except (OSError, json.JSONDecodeError) as e:
sys.exit(f"Could not read {args.state}: {e}")
rows = []
for sid, v in data.items():
total = v.get("prior", 0) + v.get("last", 0)
rows.append((sid, total, v.get("ts", 0)))
grand = sum(r[1] for r in rows)
active = [r for r in rows if r[1] > 0]
print(f"Sessions: {len(rows)} ({len(active)} with cost)")
print(f"Total cost: ${grand:,.2f}")
if active:
print(f"Average (active): ${grand / len(active):,.2f}")
rows.sort(key=lambda r: r[1], reverse=True)
n = max(0, args.top)
if n:
print(f"\nTop {min(n, len(rows))} sessions:")
for sid, total, ts in rows[:n]:
when = datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M") if ts else "—"
print(f" ${total:>9,.2f} {sid} {when}")
if __name__ == "__main__":
main()
#!/usr/bin/env bash
# Claude Code status line — two-line layout.
# Line 1: dir · branch │ thinking · model · effort · agents · fast │ duration │ edits │ session
# Line 2: tokens(↑sent ↓recv) · cache% · cost │ context bar (pct, used/size) │ limits (5h/7d + resets)
# Reads Claude Code's Status JSON on stdin (schema v2.1.x). Needs jq + a Nerd Font.
# Cost is cumulative across resumes via ~/.claude/statusline-session-state.json, which
# tracks each session's running total from Claude Code's exact per-run cost (a drop
# signals a resume), seeded once from a per-model list-price estimate of the transcript.
input=$(cat)
j() { printf '%s' "$input" | jq -r "$1" 2>/dev/null; }
# ── 256-colour palette ─────────────────────────────────────────────
RESET='\033[0m'; BOLD='\033[1m'; DIM='\033[2m'
C_SESS='\033[38;5;245m' # grey — session name
C_MODEL='\033[38;5;141m' # violet
C_EFFORT='\033[38;5;180m' # tan
C_THINK='\033[38;5;176m' # orchid
C_FAST='\033[38;5;220m' # yellow — fast mode
C_DIR='\033[38;5;75m' # steel blue
C_BRANCH='\033[38;5;179m' # warm gold
C_EMPTY='\033[38;5;238m' # empty bar
C_PCT='\033[38;5;250m'
C_DUR='\033[38;5;109m'
C_COST='\033[38;5;150m'
C_SENT='\033[38;5;110m'
C_RECV='\033[38;5;150m'
C_TOK='\033[38;5;244m'
C_CACHE='\033[38;5;108m' # sage — cache hit
C_EDIT='\033[38;5;215m'
C_AGENT='\033[38;5;80m'
C_LIMIC='\033[38;5;244m'
C_SEP='\033[38;5;240m'
sev() { [ "$1" -ge 80 ] && printf '\033[38;5;167m' || { [ "$1" -ge 50 ] && printf '\033[38;5;179m' || printf '\033[38;5;77m'; }; }
# ── Nerd Font icons (literal glyphs) ───────────────────────────────
I_MODEL='󰠱'; I_THINK=''; I_FAST=''; I_FOLDER='󰉋'; I_BRANCH=''
I_TOK='󰆼'; I_CACHE='󰍛'; I_TIMER='󰔛'; I_COST=''; I_EDIT='󰤌'; I_AGENT=''; I_LIMIT='󰓅'; SEP='│'
# ── Status JSON fields ─────────────────────────────────────────────
session=$(j '.session_name // empty')
model=$(j '.model.display_name // .model.id // "Claude"')
effort=$(j '.effort.level // empty')
thinking=$(j '.thinking.enabled // false')
fast=$(j '.fast_mode // false')
cwd=$(j '.workspace.current_dir // .cwd // ""')
wt_branch=$(j '.worktree.branch // empty')
cost=$(j '.cost.total_cost_usd // 0')
dur_ms=$(j '.cost.total_duration_ms // 0')
add=$(j '.cost.total_lines_added // 0')
rem=$(j '.cost.total_lines_removed // 0')
transcript=$(j '.transcript_path // ""')
pct=$(j '.context_window.used_percentage // empty')
size=$(j '.context_window.context_window_size // empty')
ctx_used=$(j '.context_window.total_input_tokens // empty')
rl5=$(j '.rate_limits.five_hour.used_percentage // empty')
rl5_reset=$(j '.rate_limits.five_hour.resets_at // empty')
rl7=$(j '.rate_limits.seven_day.used_percentage // empty')
rl7_reset=$(j '.rate_limits.seven_day.resets_at // empty')
# ── Git branch: walk up from cwd reading .git/HEAD directly (no subprocess) ──
branch="$wt_branch"
if [ -z "$branch" ]; then
d="$cwd"
while [ -n "$d" ] && [ "$d" != "/" ]; do
if [ -f "$d/.git/HEAD" ]; then
h=$(cat "$d/.git/HEAD" 2>/dev/null)
case "$h" in
"ref: refs/heads/"*) branch="${h#ref: refs/heads/}" ;;
*) branch=$(printf '%s' "$h" | cut -c1-7) ;;
esac
break
fi
d="${d%/*}"
done
fi
# ── Transcript pass: cumulative sent/received, cache reads, running agents, cost ──
# Cost is summed from token usage × per-model list price; it is cumulative across
# resumes (same transcript file) — unlike the process-local Status cost field.
sent=0; recv=0; cache=0; agents=0; cost_calc=0
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
read -r sent recv cache agents cost_calc <<EOF
$(jq -rs '
def rates(m):
if (m|test("opus")) then {i:15, o:75, w:18.75, r:1.5}
elif (m|test("haiku")) then {i:1, o:5, w:1.25, r:0.1}
elif (m|test("sonnet")) then {i:3, o:15, w:3.75, r:0.3}
else {i:3, o:15, w:3.75, r:0.3} end;
def U: map(select(.type=="assistant" and ((.isSidechain//false)|not) and .message.usage!=null) | .message.usage);
[ (U | map((.input_tokens//0)+(.cache_creation_input_tokens//0)+(.cache_read_input_tokens//0)) | add // 0),
(U | map(.output_tokens//0) | add // 0),
(U | map(.cache_read_input_tokens//0) | add // 0),
( (map(select(.type=="assistant") | .message.content[]? | select(.type=="tool_use" and .name=="Agent") | .id)) as $c
| (map(select(.type=="user") | .message.content[]? | select(.type=="tool_result") | .tool_use_id)) as $d
| [$c[] | select(. as $x | ($d | index($x)) | not)] | length ),
(map(select(.type=="assistant" and .message.usage!=null)
| rates(.message.model // "") as $p | .message.usage
| ((.input_tokens//0)*$p.i + (.cache_creation_input_tokens//0)*$p.w
+ (.cache_read_input_tokens//0)*$p.r + (.output_tokens//0)*$p.o) / 1000000) | add // 0)
] | @tsv' "$transcript" 2>/dev/null)
EOF
fi
[ -z "$agents" ] && agents=0
# Context fallback for pre-2.1 schemas.
if [ -z "$pct" ]; then
size=200000; [ "$(j '.exceeds_200k_tokens // false')" = "true" ] && size=1000000
ctx_used=$(jq -rs 'map(select(.type=="assistant" and ((.isSidechain//false)|not) and .message.usage!=null))
| last | if .==null then 0 else (.message.usage|(.input_tokens//0)+(.cache_creation_input_tokens//0)+(.cache_read_input_tokens//0)) end' "$transcript" 2>/dev/null)
[ -z "$ctx_used" ] && ctx_used=0
pct=$(( ctx_used * 100 / size ))
fi
# Cache-hit %: cached input ÷ total input sent.
cache_pct=""
[ "${sent:-0}" -gt 0 ] && cache_pct=$(( cache * 100 / sent ))
# ── Cumulative cost across resumes ─────────────────────────────────
# Exact per-run Status cost is accumulated into a shared session-state file,
# ~/.claude/statusline-session-state.json, keyed by session id: { prior, last, ts }
# (extra per-session state can be added alongside these keys later).
# A drop in the Status cost means a resume, so the finished run's cost is folded
# into `prior`. New entries are seeded from the transcript token estimate so
# pre-existing sessions keep their history; deltas after that are exact. The
# read-modify-write is serialised with flock; entries untouched > 6 months are
# pruned on write. Falls back to the estimate when no session id is present.
status_cost="$cost"
sid=$(j '.session_id // empty')
if [ -n "$sid" ]; then
cfile="$HOME/.claude/statusline-session-state.json"; now=$(date +%s); ttl=15552000 # 180 days
exec 9>"$HOME/.claude/.statusline-session-state.lock" 2>/dev/null; flock -w 1 9 2>/dev/null
cur=$(cat "$cfile" 2>/dev/null); [ -z "$cur" ] && cur='{}'
newdoc=$(printf '%s' "$cur" | jq -c \
--arg sid "$sid" --argjson c "$status_cost" --argjson est "${cost_calc:-0}" \
--argjson now "$now" --argjson ttl "$ttl" '
. as $d
| ($d[$sid] // {prior: (if $est-$c>0 then $est-$c else 0 end), last: $c}) as $e
| (if ($c + 1e-7) < ($e.last // 0) then ($e.prior // 0)+($e.last // 0) else ($e.prior // 0) end) as $prior
| $d | .[$sid] = {prior: $prior, last: $c, ts: $now}
| with_entries(select(.value.ts >= ($now - $ttl)))' 2>/dev/null)
if [ -n "$newdoc" ]; then
printf '%s\n' "$newdoc" > "$cfile.tmp" 2>/dev/null && mv -f "$cfile.tmp" "$cfile" 2>/dev/null
cost=$(printf '%s' "$newdoc" | jq -r --arg sid "$sid" --argjson c "$status_cost" '(.[$sid].prior // 0) + $c')
fi
flock -u 9 2>/dev/null; exec 9>&- 2>/dev/null
elif [ -n "$cost_calc" ] && awk -v c="$cost_calc" 'BEGIN{exit !(c>0)}'; then
cost="$cost_calc"
fi
# ── Formatting helpers ─────────────────────────────────────────────
fmt() { awk -v n="${1:-0}" 'BEGIN{ if(n>=1000000){v=n/1000000; printf (v==int(v)?"%dM":"%.1fM"),v}
else if(n>=1000){v=n/1000; printf (v==int(v)?"%dk":"%.1fk"),v} else printf "%d", n }'; }
dur=$(awk -v ms="$dur_ms" 'BEGIN{ s=int(ms/1000); h=int(s/3600); m=int((s%3600)/60); x=s%60;
if(h>0)printf"%dh%dm",h,m; else if(m>0)printf"%dm%ds",m,x; else printf"%ds",x }')
until_reset() { [ -z "$1" ] && return; awk -v t="$1" 'BEGIN{ s=t-systime(); if(s<=0){print"";exit}
dd=int(s/86400); h=int((s%86400)/3600); m=int((s%3600)/60);
if(dd>0)printf"%dd%dh",dd,h; else if(h>0)printf"%dh%dm",h,m; else printf"%dm",(m>0?m:1) }'; }
rl5_in=$(until_reset "$rl5_reset"); rl7_in=$(until_reset "$rl7_reset")
# Context bar (10 cells; fill coloured by severity, empties grey).
filled=$(awk -v p="$pct" 'BEGIN{f=int(p/10+0.5); print (f>10?10:f)}')
bar_f=""; i=0; while [ "$i" -lt "$filled" ]; do bar_f="${bar_f}▓"; i=$((i+1)); done
bar_e=""; while [ "$i" -lt 10 ]; do bar_e="${bar_e}░"; i=$((i+1)); done
CTXC=$(sev "$pct")
cost_h=$(awk -v c="$cost" 'BEGIN{printf "%.2f", c}')
sep=" ${C_SEP}${SEP}${RESET} "
# ── Line 1: dir·branch │ thinking/model/effort/agents/fast │ duration │ edits │ session ──
L1="$(printf "${C_DIR}${BOLD}${I_FOLDER} %s${RESET}" "$(basename "$cwd")")"
[ -n "$branch" ] && L1="$L1$(printf " ${C_BRANCH}${I_BRANCH} %s${RESET}" "$branch")"
L1="$L1${sep}"
[ "$thinking" = "true" ] && L1="$L1$(printf "${C_THINK}${I_THINK}${RESET} ")"
L1="$L1$(printf "${C_MODEL}${BOLD}${I_MODEL} %s${RESET}" "$model")"
[ -n "$effort" ] && L1="$L1$(printf " ${C_EFFORT}%s${RESET}" "$effort")"
[ "${agents:-0}" -gt 0 ] && L1="$L1$(printf " ${C_AGENT}${I_AGENT} %s${RESET}" "$agents")"
[ "$fast" = "true" ] && L1="$L1$(printf " ${C_FAST}${I_FAST}${RESET}")"
L1="$L1${sep}$(printf "${C_DUR}${I_TIMER} %s${RESET}" "$dur")"
if [ "${add:-0}" -gt 0 ] || [ "${rem:-0}" -gt 0 ]; then
L1="$L1${sep}$(printf "${C_EDIT}${I_EDIT} +%s/-%s${RESET}" "$add" "$rem")"
fi
[ -n "$session" ] && L1="$L1${sep}$(printf "${C_SESS}%s${RESET}" "$session")"
# ── Line 2: tokens(↑↓ cache$) │ context bar │ limits ───────────────
L2="$(printf "${C_TOK}${I_TOK}${RESET} ${C_SENT}↑%s${RESET} ${C_RECV}↓%s${RESET}" "$(fmt "$sent")" "$(fmt "$recv")")"
[ -n "$cache_pct" ] && L2="$L2$(printf " ${C_CACHE}${I_CACHE} %d%%${RESET}" "$cache_pct")"
L2="$L2$(printf " ${C_COST}\$%s${RESET}" "$cost_h")"
L2="$L2${sep}$(printf "${CTXC}%s${C_EMPTY}%s${RESET} ${C_PCT}%d%% %s/%s${RESET}" "$bar_f" "$bar_e" "$pct" "$(fmt "$ctx_used")" "$(fmt "$size")")"
if [ -n "$rl5" ] || [ -n "$rl7" ]; then
L2="$L2${sep}$(printf "${C_LIMIC}${I_LIMIT}${RESET}")"
if [ -n "$rl5" ]; then
L2="$L2$(printf " $(sev "$rl5")5h %d%%${RESET}" "$rl5")"
[ -n "$rl5_in" ] && L2="$L2$(printf " ${C_SEP}↻%s${RESET}" "$rl5_in")"
fi
if [ -n "$rl7" ]; then
L2="$L2$(printf " ${C_SEP}·${RESET} $(sev "$rl7")7d %d%%${RESET}" "$rl7")"
[ -n "$rl7_in" ] && L2="$L2$(printf " ${C_SEP}↻%s${RESET}" "$rl7_in")"
fi
fi
printf '%b\n%b' "$L1" "$L2"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment