feat(statusline): show context-window usage % in the status line

Adds .claude/statusline.sh (reads context_window.used_percentage +
context_window_size straight from the statusLine JSON; green<70/yellow/red
bar) and wires it via .claude/settings.json statusLine. Committed in-repo so
it follows boma to any clone, matching how .claude/ already tracks hooks +
plugins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-17 17:35:47 +02:00
parent 5d14efc864
commit 959f9b30b5
2 changed files with 68 additions and 0 deletions

View file

@ -69,5 +69,10 @@
] ]
} }
] ]
},
"statusLine": {
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR:-.}/.claude/statusline.sh\"",
"padding": 0
} }
} }

63
.claude/statusline.sh Executable file
View file

@ -0,0 +1,63 @@
#!/usr/bin/env bash
#
# Claude Code statusLine — shows working dir, model, and context-window usage.
# Wired via .claude/settings.json (statusLine.command). Receives the statusLine
# JSON on stdin; first stdout line is rendered (ANSI colour supported).
#
# Context usage comes straight from the input JSON — no transcript parsing:
# .context_window.used_percentage pre-calculated % of the window in use (input side)
# .context_window.context_window_size window size in tokens (1000000 for the 1M models)
# verified: Claude Code statusLine schema · code.claude.com/docs/en/statusline · 2026-06-17
#
# Fails soft: any parse problem prints nothing and exits 0 (never breaks the prompt).
set -uo pipefail
input=$(cat 2>/dev/null) || exit 0
command -v jq >/dev/null 2>&1 || exit 0
# pct<TAB>window<TAB>dir-basename<TAB>model-name (used_percentage preferred,
# else derived from current_usage, else 0). @tsv keeps spaces in the dir safe.
parsed=$(printf '%s' "$input" | jq -r '
(.workspace.current_dir // .cwd // "" | sub(".*/"; "")) as $dir
| (.model.display_name // "?") as $model
| (.context_window.context_window_size // 200000) as $win
| (
if (.context_window.used_percentage // null) != null then
.context_window.used_percentage
elif (.context_window.current_usage // null) != null then
((.context_window.current_usage.input_tokens
+ (.context_window.current_usage.cache_creation_input_tokens // 0)
+ (.context_window.current_usage.cache_read_input_tokens // 0)) / $win * 100)
else 0 end | floor
) as $pct
| [$pct, $win, $dir, $model] | @tsv
' 2>/dev/null) || exit 0
[ -z "$parsed" ] && exit 0
IFS=$'\t' read -r pct win dir model <<<"$parsed"
# Human window label: 1000000 -> 1M, 200000 -> 200k, else Nk.
case "$win" in
1000000) wlabel="1M" ;;
*) wlabel="$((win / 1000))k" ;;
esac
# Colour the bar/percentage by pressure: green <70, yellow 7089, red >=90.
if [ "$pct" -ge 90 ]; then col=$'\033[31m' # red
elif [ "$pct" -ge 70 ]; then col=$'\033[33m' # yellow
else col=$'\033[32m' # green
fi
dim=$'\033[2m'; rst=$'\033[0m'
# 10-cell bar; clamp fill to [0,10] so an over-100 reading can't overflow.
filled=$((pct / 10)); [ "$filled" -gt 10 ] && filled=10; [ "$filled" -lt 0 ] && filled=0
bar=""
for ((i = 0; i < 10; i++)); do
if [ "$i" -lt "$filled" ]; then bar+="█"; else bar+="░"; fi
done
printf '%s%s%s · %s · %s%s %d%%%s %sctx/%s%s\n' \
"$dim" "$dir" "$rst" \
"$model" \
"$col" "$bar" "$pct" "$rst" \
"$dim" "$wlabel" "$rst"