Two-line: dir/git/model/advisor/effort on top + Caveman, RTK, context %, 5h/7d rate limits, and session cost below
Updated 2026-08-18
2 copies
Preview
Clean repo
app · main · Opus 4.8 · high
ctx 22% 44k · 5h 26% ↻2h6m · 7d 7% ↻2d0h · cost $0.41 · msg $0.757New session
app · main · Opus 4.8 · high
ctx 0% · cost $0.00Dirty branch
app · feat/auth* · Sonnet 4.6 · med
ctx 48% 96k · 5h 40% ↻1h11m · 7d 18% ↻2d21h · cost $0.41 · msg $0.307Near-full
app · main · Opus 4.8 · max · Explanatory
ctx 91% 182k · 5h 88% ↻17m · 7d 61% ↻19h59m · cost $4.12 · msg $2.8271M context
app · main · Fable 5 · xhigh
ctx 64% 640k · 5h 33% ↻3h29m · 7d 80% ↻4d23h · cost $0.41 · msg $1.939Post-compact
app · main · Haiku 4.5
ctx 0% · 5h 52% ↻4h1m · cost $0.41Worktree
feature · worktree-feature · Opus 4.8 · low
ctx 37% 74k · 5h 44% ↻2h49m · 7d 20% ↻1d6h · cost $0.41 · msg $1.207Non-git
scratch · Opus 4.8 · high
ctx 22% 44k · cost $0.41 · msg $0.757Source
#!/bin/bash
input=$(cat)
dir=$(echo "$input" | jq -r '.workspace.current_dir')
model=$(echo "$input" | jq -r '.model.display_name')
model_id=$(echo "$input" | jq -r '.model.id')
output_style=$(echo "$input" | jq -r '.output_style.name')
transcript=$(echo "$input" | jq -r '.transcript_path // empty')
dim="\033[2m"
reset="\033[0m"
bold="\033[1m"
cyan="\033[36m"
yellow="\033[33m"
green="\033[32m"
red="\033[31m"
magenta="\033[35m"
blue="\033[34m"
# Color helper: green <50, yellow 50-79, red >=80
color_for_pct() {
local p=$1
if [ "$p" -ge 80 ]; then echo "$red"
elif [ "$p" -ge 50 ]; then echo "$yellow"
else echo "$green"; fi
}
# Format a token count → "1.2M" / "347k" / "812"
fmt_tokens() {
local t=$1
if [ "$t" -ge 1000000 ]; then
printf "%d.%dM" $((t / 1000000)) $(((t % 1000000) / 100000))
elif [ "$t" -ge 1000 ]; then
printf "%dk" $(((t + 500) / 1000))
else
printf "%d" "$t"
fi
}
# Format a raw model id → the display name Claude itself uses.
# "claude-opus-5" → "Opus 5", "claude-haiku-4-5-20251001" → "Haiku 4.5",
# "claude-opus-5[1m]" → "Opus 5", bare alias "opus" → "Opus".
fmt_model_id() {
local m=$1
m=${m%%\[*} # drop [1m] context-variant suffix
m=${m#claude-} # drop vendor prefix
m=$(printf '%s' "$m" | sed -E 's/-20[0-9]{6}$//') # drop trailing date stamp
local family=${m%%-*}
local ver=""
[ "$m" != "$family" ] && ver=$(printf '%s' "${m#*-}" | tr '-' '.')
family="$(printf '%s' "${family:0:1}" | tr '[:lower:]' '[:upper:]')${family:1}"
if [ -n "$ver" ]; then printf '%s %s' "$family" "$ver"; else printf '%s' "$family"; fi
}
# True when two model identifiers name the same model. Handles the case where one
# side is a bare alias with no version ("sonnet", as written in settings.json) and
# the other is a fully resolved id ("claude-sonnet-5", as stamped in transcripts):
# comparing the rendered labels alone would call those two different models.
same_model() {
local a b fa fb
a=$(fmt_model_id "$1")
b=$(fmt_model_id "$2")
[ "$a" = "$b" ] && return 0
fa=${a%% *} # family alone; whole string when unversioned
fb=${b%% *}
# Unversioned on either side → that side is an alias, which can only mean the
# current model of its family, so family equality is all we can require.
if [ "$a" = "$fa" ] || [ "$b" = "$fb" ]; then
[ "$fa" = "$fb" ] && return 0
fi
return 1
}
# Format a duration in seconds → "2h13m" / "47m" / "3d4h"
fmt_duration() {
local s=$1
[ "$s" -lt 0 ] && s=0
local d=$((s / 86400))
local h=$(((s % 86400) / 3600))
local m=$(((s % 3600) / 60))
if [ "$d" -gt 0 ]; then
printf "%dd%dh" "$d" "$h"
elif [ "$h" -gt 0 ]; then
printf "%dh%dm" "$h" "$m"
else
printf "%dm" "$m"
fi
}
# --- Line 1: orientation ---
dir_name=$(basename "$dir")
git_info=""
if git -C "$dir" rev-parse --git-dir &>/dev/null; then
branch=$(git -C "$dir" -c core.useBuiltinFSMonitor=false rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')
if [ -n "$branch" ]; then
if git -C "$dir" -c core.useBuiltinFSMonitor=false diff-index --quiet HEAD -- 2>/dev/null; then
git_info="$branch"
else
git_info="${branch}*"
fi
fi
fi
# Claude's own display name, verbatim apart from the redundant vendor prefix:
# "Claude Opus 5 (1M context)" → "Opus 5 (1M context)". Nothing is derived or
# abbreviated here, so the label always matches what /model shows.
short_model=$(echo "$model" | sed -E 's/^Claude //')
if [[ "$model_id" == *"opus"* ]]; then
model_str="${bold}${yellow}${short_model}${reset}"
elif [[ "$model_id" == *"sonnet"* ]]; then
model_str="${cyan}${short_model}${reset}"
elif [[ "$model_id" == *"haiku"* ]]; then
model_str="${blue}${short_model}${reset}"
else
model_str="${short_model}"
fi
# --- Advisor model, appended to the model segment as "Sonnet 5 + Opus 5". ---
# Claude Code stamps every assistant transcript entry with the advisor model it
# actually resolved for that request, so the transcript — not settings.json — is
# the authoritative source: it already accounts for /advisor session-only
# overrides, the --advisor flag, AND the capability gate (advisor must be at
# least as capable as the main model; when it isn't, the field is simply absent
# and no badge should be shown).
# Settings are consulted ONLY when the transcript has no assistant entry yet
# (fresh session, first render) — otherwise an absent stamp would be misread as
# "unknown" and the badge would claim an advisor that isn't running.
advisor_model=""
if [ -n "$transcript" ] && [ -r "$transcript" ]; then
# 1 MB tail keeps this O(1) regardless of transcript size; the last complete
# assistant line reflects the current state.
adv_line=$(tail -c 1000000 "$transcript" 2>/dev/null | grep '"type":"assistant"' | tail -1)
if [ -n "$adv_line" ]; then
advisor_model=$(printf '%s' "$adv_line" | jq -r '.advisorModel // empty' 2>/dev/null)
else
advisor_model=$(jq -r '.advisorModel // empty' "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" 2>/dev/null)
fi
fi
if [ -n "$advisor_model" ]; then
# Whitelist-strip before rendering: never let a transcript field reach the
# terminal with escape bytes intact.
advisor_model=$(printf '%s' "$advisor_model" | tr -cd 'a-zA-Z0-9._][-')
if [ -n "$advisor_model" ]; then
# Only render when the advisor differs from the main model — "Opus 5 +
# Opus 5" carries no information. same_model() absorbs both the
# 1M-context suffix and the bare-alias form settings.json uses.
same_model "$advisor_model" "$model_id" \
|| model_str+="${dim} + $(fmt_model_id "$advisor_model")${reset}"
fi
fi
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
ctx_color=$(color_for_pct "$ctx_pct")
now=$(date +%s)
fh_pct_raw=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
sd_pct_raw=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
effort=$(echo "$input" | jq -r '.effort.level // empty')
# "ultracode" is not an effort level — it is a session flag meaning "xhigh effort
# plus standing dynamic-workflow orchestration", and Claude Code collapses it to
# plain "xhigh" before the payload is built (settings schema: "Session-scoped —
# interactive toggles never persist it"), so neither this payload nor any
# settings file can tell the two apart.
# The one place it does survive is the transcript: /effort records its result as
# a user entry whose whole content is "<local-command-stdout>Set effort level to
# <x> ...</local-command-stdout>". Take the last such entry and, only when the
# payload already says xhigh, upgrade the label.
# Matched with jq on the message content rather than a plain grep on purpose: any
# session that merely *discusses* effort levels ends up with that sentence sitting
# inside some tool output, and a raw grep happily matches it. Requiring the
# content to START with the tag keeps only genuine command results.
# Costs a full-transcript jq pass (~23 ms on a 2 MB transcript, measured — and
# faster than the equivalent grep, which has to scan for a longer literal). Only
# runs when the level is xhigh, since that is the sole ambiguous case.
# A later "/effort xhigh" appends a newer marker and self-corrects. Known hole:
# on a resumed session an old ultracode marker outlives the state that set it,
# so a persisted-xhigh default with no newer /effort would read as ultracode.
if [ "$effort" = "xhigh" ] && [ -n "$transcript" ] && [ -r "$transcript" ]; then
eff_marker=$(jq -r 'select(.type=="user") | .message.content | strings
| select(startswith("<local-command-stdout>Set effort level to "))' \
"$transcript" 2>/dev/null | tail -1)
eff_marker=${eff_marker#*Set effort level to }
[ "${eff_marker%% *}" = "ultracode" ] && effort="ultracode"
fi
case "$effort" in
ultracode) effort_str="${bold}${magenta}ultracode${reset}" ;;
max) effort_str="${bold}${red}max${reset}" ;;
xhigh) effort_str="${bold}${red}xhigh${reset}" ;;
high) effort_str="${red}high${reset}" ;;
medium) effort_str="${yellow}med${reset}" ;;
low) effort_str="${green}low${reset}" ;;
"") effort_str="" ;;
*) effort_str="${dim}${effort}${reset}" ;;
esac
sep="${dim} · ${reset}"
# --- Line 1: orientation (dir · git · model · effort · output_style) ---
line1="${bold}${dir_name}${reset}"
[ -n "$git_info" ] && line1+="${sep}${magenta}${git_info}${reset}"
line1+="${sep}${model_str}"
[ -n "$effort_str" ] && line1+="${sep}${effort_str}"
if [ "$output_style" != "null" ] && [ "$output_style" != "default" ]; then
line1+="${sep}${dim}${output_style}${reset}"
fi
# --- Caveman badge: reads the flag file the caveman plugin's SessionStart
# hook writes. Same hardening as the plugin's own caveman-statusline.sh —
# reject symlinks, hard-cap read, strip to a whitelist charset — so a
# malicious flag file can't inject ANSI/OSC escapes into the statusline.
cvm_label=""
caveman_flag="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.caveman-active"
if [ -f "$caveman_flag" ] && [ ! -L "$caveman_flag" ]; then
cmode=$(head -c 64 "$caveman_flag" 2>/dev/null | tr -d '\n\r' | tr '[:upper:]' '[:lower:]')
cmode=$(printf '%s' "$cmode" | tr -cd 'a-z0-9-')
case "$cmode" in
off|lite|full|ultra|wenyan-lite|wenyan|wenyan-full|wenyan-ultra|commit|review|compress)
if [ -z "$cmode" ] || [ "$cmode" = "full" ]; then
cvm_label="CVM"
else
csuffix=$(printf '%s' "$cmode" | tr '[:lower:]' '[:upper:]')
cvm_label="CVM:${csuffix}"
fi
;;
esac
fi
# Current-session tokens-saved estimate, straight from the plugin's own
# per-session aggregator (parses $transcript, the active session's own
# transcript — no --all, so this is scoped to this session, not lifetime).
# find() is scoped to the plugin's own cache dir and capped at depth 6, so
# the walk is a handful of stat() calls, not a tree scan.
if [ -n "$cvm_label" ] && [ -n "$transcript" ] && [ "${CAVEMAN_STATUSLINE_SAVINGS:-1}" != "0" ]; then
cvm_stats_js=$(find "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/plugins/cache/caveman" -maxdepth 6 -iname 'caveman-stats.js' 2>/dev/null | head -1)
if [ -n "$cvm_stats_js" ]; then
# Node's toLocaleString() groups thousands with U+00A0 (non-breaking
# space), not ASCII space, and the line also carries a trailing
# "(~65% of output)" — so strip everything from the first '(' onward
# first, THEN drop all non-digit bytes (handles any separator byte).
cvm_raw=$(node "$cvm_stats_js" --session-file "$transcript" 2>/dev/null | grep 'Est. tokens saved:')
cvm_saved=$(printf '%s' "${cvm_raw%%(*}" | tr -cd '0-9')
if [ -n "$cvm_saved" ] && [ "$cvm_saved" -gt 0 ]; then
cvm_saved_fmt=$(fmt_tokens "$cvm_saved")
cvm_label+=" ${bold}${cvm_saved_fmt}"
fi
fi
fi
caveman_badge=""
[ -n "$cvm_label" ] && caveman_badge="\033[38;5;172m${cvm_label}\033[0m"
[ -n "$caveman_badge" ] && line1+="${sep}${caveman_badge}"
# --- RTK badge: token savings scoped to the current project (cwd). ---
rtk_badge=""
if command -v rtk &>/dev/null; then
rtk_saved=$(cd "$dir" 2>/dev/null && rtk gain --project --format json 2>/dev/null | jq -r '.summary.total_saved // empty' 2>/dev/null)
case "$rtk_saved" in
''|*[!0-9]*) rtk_saved="" ;;
esac
if [ -n "$rtk_saved" ] && [ "$rtk_saved" -gt 0 ]; then
rtk_saved_fmt=$(fmt_tokens "$rtk_saved")
rtk_badge="\033[38;5;208mRTK ${bold}${rtk_saved_fmt}\033[0m"
fi
fi
[ -n "$rtk_badge" ] && line1+="${sep}${rtk_badge}"
# --- Line 2: usage (ctx + tokens · 5h · 7d) ---
ctx_tokens=$(echo "$input" | jq -r '.context_window.total_input_tokens // empty')
ctx_tok_str=""
[ -n "$ctx_tokens" ] && [ "$ctx_tokens" -gt 0 ] && ctx_tok_str=" ${dim}$(fmt_tokens "$ctx_tokens")${reset}"
line2="${dim}ctx${reset} ${ctx_color}${ctx_pct}%${reset}${ctx_tok_str}"
# awk, not bash's builtin printf — same locale trap as the cost block below:
# under LC_NUMERIC=sk_SK.UTF-8 the builtin rejects jq's dot-decimal "31.4" as an
# "invalid number" and the percentage silently collapses to empty.
if [ -n "$fh_pct_raw" ]; then
fh_pct=$(LC_ALL=C awk -v n="$fh_pct_raw" 'BEGIN{printf "%.0f", n}')
fh_color=$(color_for_pct "$fh_pct")
fh_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
fh_suffix=""
[ -n "$fh_reset" ] && fh_suffix=" ${dim}↻$(fmt_duration $((fh_reset - now)))${reset}"
line2+="${sep}${dim}5h${reset} ${fh_color}${fh_pct}%${reset}${fh_suffix}"
fi
if [ -n "$sd_pct_raw" ]; then
sd_pct=$(LC_ALL=C awk -v n="$sd_pct_raw" 'BEGIN{printf "%.0f", n}')
sd_color=$(color_for_pct "$sd_pct")
sd_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
sd_suffix=""
[ -n "$sd_reset" ] && sd_suffix=" ${dim}↻$(fmt_duration $((sd_reset - now)))${reset}"
line2+="${sep}${dim}7d${reset} ${sd_color}${sd_pct}%${reset}${sd_suffix}"
fi
# Session total: Claude Code ships the authoritative figure in the payload
# (cost.total_cost_usd), so no transcript arithmetic is needed. This also counts
# subagent and workflow spend, which a per-message token sum over the main
# transcript silently misses.
sess_cost=$(echo "$input" | jq -r '.cost.total_cost_usd // empty' 2>/dev/null)
# Per-message cost still has to be estimated — the payload reports the current
# context's token usage but no price for it. Rates are USD per million tokens,
# standard <200k tier; w5/w1 = 5-minute / 1-hour prompt-cache write, r = cache read.
rates_def='def rates(m): if (m|test("opus")) then {i:15,o:75,w5:18.75,w1:30,r:1.5} elif (m|test("haiku")) then {i:1,o:5,w5:1.25,w1:2,r:0.1} elif (m|test("sonnet")) then {i:3,o:15,w5:3.75,w1:6,r:0.3} else {i:3,o:15,w5:3.75,w1:6,r:0.3} end;'
# Current context: cost of the most recent API call (context_window.current_usage,
# no 5m/1h split → treat cache creation as 5m write). No transcript read needed.
cur_cost=$(echo "$input" | jq -r "$rates_def"'
rates(.model.id // "") as $R
| (.context_window.current_usage) as $u
| if $u == null then empty else
( (($u.input_tokens//0)*$R.i + ($u.output_tokens//0)*$R.o + ($u.cache_creation_input_tokens//0)*$R.w5 + ($u.cache_read_input_tokens//0)*$R.r) / 1000000 )
end' 2>/dev/null)
# awk, not bash's builtin printf: under a non-C LC_NUMERIC (e.g. sk_SK.UTF-8,
# comma decimal), bash's printf rejects jq's dot-decimal numbers outright
# ("invalid number" -> silently prints $0.00) because it validates the string
# against the locale's decimal separator before the LC_ALL=C override can take
# effect for a builtin. awk's own strtod-based number parsing isn't picky
# about the separator, so it converts correctly regardless of locale; LC_ALL=C
# only pins the OUTPUT formatting to a dot, for a consistent "$3.41" look.
if [ -n "$sess_cost" ]; then
line2+="${sep}${dim}cost${reset} \$$(LC_ALL=C awk -v n="$sess_cost" 'BEGIN{printf "%.2f", n}')"
fi
if [ -n "$cur_cost" ]; then
line2+="${sep}${dim}msg \$$(LC_ALL=C awk -v n="$cur_cost" 'BEGIN{printf "%.3f", n}')${reset}"
fi
printf "%b\n%b" "$line1" "$line2"
What it shows
- Current directory name on the first line
- Git branch, with a * when the working tree is dirty
- Model display name, colored for Opus, Sonnet, and Haiku
- Effort level (low, med, high, xhigh, max), omitted when the payload has no effort field
- Non-default output style, such as Explanatory
- Advisor model beside the main model when it differs (from the transcript, or from settings.json on a fresh session)
- Optional Caveman badge (CVM or CVM:MODE) and session tokens saved
- Optional RTK project token-savings badge, if rtk is on PATH
- Context window usage as a percentage and abbreviated token count
- 5-hour and 7-day quota percentages, with time until reset
- Session cost from Claude Code's payload
- Estimated cost of the latest API call, labeled msg
Requirements
- Bash
- jq, to parse the status line JSON on stdin
- git, for the branch segment (omitted in a non-git directory)
- awk, so percentages and dollar amounts format under non-C locales
- Optional: the Caveman plugin and node, for the CVM savings figure
- Optional: the rtk CLI, for the RTK badge
Behavior notes
- Prints two lines in every previewed session.
- A dirty feature branch shows feat/auth*. A scratch directory drops the git segment.
- Worktree sessions use the worktree folder name and its git branch, not the original project dir.
- Haiku post-compact has no effort field, so that segment is gone. medium renders as med. max and xhigh are bold red.
- Explanatory appears only when output_style is not default.
- Fresh sessions show ctx 0% and cost $0.00, and hide the 5h/7d windows and the msg estimate.
- After compact, context is 0% and the 7-day window is absent, matching a payload that only has the 5-hour limit.
- Percentages turn yellow from 50 and red from 80. Near-full hits red on ctx 91% and 5h 88%. The 1M-context preview hits red on 7d 80%.
- Session cost follows cost.total_cost_usd ($4.12 when near-full, $0.41 otherwise).
- The msg figure is an estimate from hardcoded rates. Families other than opus, haiku, and sonnet (Fable in the 1M preview) are priced like Sonnet.
- Caveman, RTK, and advisor do not appear in these previews. The sandbox has no plugin, no rtk binary, and no advisorModel stamp on the transcript.
More status lines
Dreambase Panel
150 copies
╭─ CONTEXT ────────────────────────────────────────────╮
│ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │
├─ STATS ──────────────────────────────────────────────┤
│ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │
├─ REPO ───────────────────────────────────────────────┤
│ ◆ Opus 4.8 · main · 16:21 │
╰──────────────────────────────────────────────────────╯
Single-line usage
87 copies
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0hPace Dot Meters
63 copies
Opus 4.8 [high] ~/app main
16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Catppuccin Frappé
38 copies
Opus 4.8 │ main │ app │ ▓▓░░░░░░░░ 44k/200k │ $0.41
Activity Feed
36 copies
Opus 4.8 high ✦ │ ctx 22% │ app (main) │ PR #1287 ● │ $0.41 · 10m +128 -34
Session ● ● ● ● ● ● ● ● ○ ○ ○ 74% left Resets in 2h 6m
Weekly ● ● ● ● ● ● ● ● ● ● ○ 93% left Resets in 2d 0h
Fable ● ● ● ● ● ● ● ● ● ○ ○ 85% left Resets in 2d 0h
◐ Read:index.ts ✓ Read×1 ✓ Bash×1 ✓ Edit×1 ✓ Grep×1
◐ code-reviewer Review the change for regressions
Dreambase Flat
29 copies
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │ ◆ Opus 4.8 · main · 16:22