Rate-limit Pace + Context + JPY Cost Gauge

TokensCostLimitsBashNetwork Access

Shows 5h/weekly rate-limit usage with early-warning pace coloring, context window gauge, and daily cost in USD + JPY.

6 copies

In Claude Desktop? Add this status line with statusline-anywhere.

Preview

Clean repo
!Opus4.8(high) Session:26%(19:00) Week:7%(2d0h) Ctx:▰▱▱▱▱22% Cost:~$0.41
New session
!Opus4.8(high) Session:- Week:-
Dirty branch
Sonnet4.6(medium) Session:40%(18:05) Week:18%(2d21h) Ctx:▰▰▱▱▱48% Cost:~$0.41
Near-full
!Opus4.8(max) Session:88%(17:11) Week:61%(19h59m) Ctx:▰▰▰▰▱91% Cost:~$4.12
1M context
!!Fable5(xhigh) Session:33%(20:23) Week:80%(4d23h) Ctx:▰▰▰▱▱64% Cost:~$0.41
Post-compact
Haiku4.5 Session:52%(20:55) Cost:~$0.41
Worktree
!Opus4.8(low) Session:44%(19:43) Week:20%(1d6h) Ctx:▰▱▱▱▱37% Cost:~$0.41
Non-git
!Opus4.8(high) Session:- Week:- Ctx:▰▱▱▱▱22% Cost:~$0.41

What it does

One line with the model and effort, 5-hour and weekly limits with reset times, a context gauge, and today's total spend, shown in yen too once an exchange rate is cached.

Written in
Bash
Network access
api.frankfurter.dev
Works in
Terminal

How it works

  1. Pulls model, effort.level, rate_limits, context_window and cost via jq.
  2. Limits turn amber at 60% and red at 80%, or sooner if projected pace tops 110% or 150%.
  3. Totals today's cost across sessions, kept in ~/.claude/cost_budget.cache.
  4. Missing limits show Session:- and Week:-; the Haiku run's 52% went red on pace alone.

Good to know

  • Needs jq; without it you only get the model name and [statusline: jq not found].
  • Yen amounts need curl and a USD to JPY rate, cached for a week. CC_STATUSLINE_JPY=0 turns that off.
  • API-key or cloud sessions with no rate_limits get a ¥500 daily budget bar (CC_STATUSLINE_BUDGET_JPY).

Source

#!/bin/bash
# Locale-proof numeric formatting (printf/awk decimal points)
export LC_NUMERIC=C

input=$(cat)
now=$(date +%s)

# === Color Palette (Claude Gradient) ===
C_RESET=$'\e[0m'
C_PURPLE=$'\e[38;2;167;139;250m'   # model name
C_GREEN=$'\e[38;2;130;180;100m'    # healthy (<60%)
C_AMBER=$'\e[38;2;229;192;123m'    # warning (>=60%) / Opus !
C_RED=$'\e[38;2;224;108;117m'      # critical (>=80%) / Fable !!
C_DIM=$'\e[38;2;92;99;112m'        # labels
SESSION_WINDOW_SEC=18000
WEEK_WINDOW_SEC=604800

color_for_pct() {
    local pct=$1
    if [ "$pct" -ge 80 ]; then printf "%s" "$C_RED"
    elif [ "$pct" -ge 60 ]; then printf "%s" "$C_AMBER"
    else printf "%s" "$C_GREEN"; fi
}

pace_pct() {
    local pct=$1
    local reset_at=$2
    local window_sec=$3
    if [ -z "$reset_at" ]; then
        echo "$pct"
        return
    fi
    local diff=$(( reset_at - now ))
    if [ "$diff" -le 0 ]; then
        echo "$pct"
        return
    fi
    local elapsed=$(( window_sec - diff ))
    if [ $(( elapsed * 20 )) -lt "$window_sec" ]; then
        echo "$pct"
        return
    fi
    local projected=$(( pct * window_sec / elapsed ))
    if [ "$projected" -gt 999 ]; then
        projected=999
    fi
    echo "$projected"
}

color_for_rate() {
    local pct=$1
    local reset_at=$2
    local window_sec=$3

    local raw_color
    if [ "$pct" -ge 80 ]; then raw_color="red"
    elif [ "$pct" -ge 60 ]; then raw_color="amber"
    else raw_color="green"; fi

    local projected
    projected=$(pace_pct "$pct" "$reset_at" "$window_sec")

    local pace_color
    if [ "$projected" -ge 150 ]; then pace_color="red"
    elif [ "$projected" -ge 110 ]; then pace_color="amber"
    else pace_color="green"; fi

    if [ "$raw_color" = "red" ] || [ "$pace_color" = "red" ]; then
        printf "%s" "$C_RED"
    elif [ "$raw_color" = "amber" ] || [ "$pace_color" = "amber" ]; then
        printf "%s" "$C_AMBER"
    else
        printf "%s" "$C_GREEN"
    fi
}

# 5-segment gauge; emits "filled + C_DIM + empty" (caller wraps with its own color/reset)
draw_bar() {
    local pct=$1 filled empty i fb="" eb=""
    filled=$(( pct / 20 ))
    [ "$filled" -gt 5 ] && filled=5
    [ "$filled" -lt 0 ] && filled=0
    empty=$(( 5 - filled ))
    for ((i=0; i<filled; i++)); do fb="${fb}▰"; done
    for ((i=0; i<empty; i++)); do eb="${eb}▱"; done
    printf '%s' "${fb}${C_DIM}${eb}"
}

stat_mtime() { stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || echo 0; }
add_commas() { printf '%d' "$1" | rev | sed 's/\([0-9]\{3\}\)/\1,/g' | sed 's/,$//' | rev; }

# Degraded fallback: without jq we cannot parse the payload — show the model name
# (best-effort sed extraction) plus an explicit hint instead of a silent blank line.
if ! command -v jq >/dev/null 2>&1; then
    md=$(printf '%s' "$input" | sed -n 's/.*"display_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
    out=""
    [ -n "$md" ] && out="${C_PURPLE}$(printf '%s' "$md" | tr -d ' ')${C_RESET} "
    printf '%s' "${out}${C_DIM}[statusline: jq not found]${C_RESET}"
    exit 0
fi

compute_cost_estimate() {
    # Single streaming pass (no slurp): constant memory even on huge transcripts
    jq -Rn --arg today "$(date +%Y-%m-%d)" '
        {
          "claude-opus-4-8": {in:5.00, out:25.00, cwrite:6.25, cread:0.50},
          "claude-opus-4-7": {in:5.00, out:25.00, cwrite:6.25, cread:0.50},
          "claude-opus-4-6": {in:5.00, out:25.00, cwrite:6.25, cread:0.50},
          "claude-opus-4-5": {in:5.00, out:25.00, cwrite:6.25, cread:0.50},
          "claude-opus-4-1": {in:5.00, out:25.00, cwrite:6.25, cread:0.50},
          "claude-opus-4-0": {in:5.00, out:25.00, cwrite:6.25, cread:0.50},
          "claude-sonnet-5": (if $today < "2026-09-01" then {in:2.00, out:10.00, cwrite:2.50, cread:0.20} else {in:3.00, out:15.00, cwrite:3.75, cread:0.30} end),
          "claude-sonnet-4-6": {in:3.00, out:15.00, cwrite:3.75, cread:0.30},
          "claude-sonnet-4-5": {in:3.00, out:15.00, cwrite:3.75, cread:0.30},
          "claude-sonnet-4-0": {in:3.00, out:15.00, cwrite:3.75, cread:0.30},
          "claude-haiku-4-5": {in:1.00, out:5.00, cwrite:1.25, cread:0.10},
          "claude-fable-5": {in:10.00, out:50.00, cwrite:12.50, cread:1.00},
          "claude-mythos-5": {in:10.00, out:50.00, cwrite:12.50, cread:1.00}
        } as $p |
        def norm_model:
            # us.anthropic.claude-*-20250929-v1:0 (Bedrock cross-region), anthropic.claude-*,
            # claude-*@20250929 (Vertex), claude-*-20250929 (API) all normalize to the bare id
            sub("^[a-z]+\\.anthropic\\."; "") | sub("^anthropic\\."; "") |
            sub("@.*$"; "") | sub("-v[0-9]+:[0-9]+$"; "") | sub("-[0-9]{8}$"; "");
        reduce (inputs | fromjson? | objects | select(.type == "assistant") | .message // {} | select(.usage)
                | { model: ((.model // "claude-sonnet-5") | norm_model), u: .usage }) as $m
        (0;
            . + (($p[$m.model] // $p["claude-sonnet-5"]) as $r |
                (($m.u.input_tokens // 0) * $r.in +
                 ($m.u.output_tokens // 0) * $r.out +
                 ($m.u.cache_creation_input_tokens // 0) * $r.cwrite +
                 ($m.u.cache_read_input_tokens // 0) * $r.cread) / 1000000)
        )
    ' "$1" 2>/dev/null
}

# === Single jq call for all JSON fields ===
eval "$(echo "$input" | jq -r '
    "model_id="      + (.model.id // "" | @sh) + "\n" +
    "model_display=" + (.model.display_name // "" | @sh) + "\n" +
    "effort="        + (.effort.level // "" | @sh) + "\n" +
    "session_id="    + (.session_id // "" | @sh) + "\n" +
    "h5_pct="        + (if .rate_limits.five_hour.used_percentage != null then (.rate_limits.five_hour.used_percentage | floor | tostring) else "" end | @sh) + "\n" +
    "h5_reset="      + (if .rate_limits.five_hour.resets_at != null then (.rate_limits.five_hour.resets_at | tostring) else "" end | @sh) + "\n" +
    "d7_pct="        + (if .rate_limits.seven_day.used_percentage != null then (.rate_limits.seven_day.used_percentage | floor | tostring) else "" end | @sh) + "\n" +
    "d7_reset="      + (if .rate_limits.seven_day.resets_at != null then (.rate_limits.seven_day.resets_at | tostring) else "" end | @sh) + "\n" +
    "ctx_pct="       + (if .context_window.used_percentage != null then (.context_window.used_percentage | floor | tostring) else "" end | @sh) + "\n" +
    "cost_usd="      + (if .cost.total_cost_usd != null then (.cost.total_cost_usd | tostring) else "" end | @sh) + "\n" +
    "cwd="           + (.cwd // "" | @sh) + "\n" +
    "transcript_path=" + (.transcript_path // "" | @sh) + "\n" +
    "has_rl="        + (if .rate_limits != null then "1" else "" end | @sh)
' 2>/dev/null)"

# === Determine subscriber status ===
# cost.total_cost_usd is no longer always 0 for subscription plans (Pro/Max).
# We classify as subscriber if has_rl is set (Pro/Team) OR if has_rl is empty but none of the 4 billed env vars are present (Max/OAuth plan).
is_subscriber=""
if [ -n "$has_rl" ] || { [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_USE_BEDROCK" ] && [ -z "$CLAUDE_CODE_USE_VERTEX" ] && [ -z "$CLAUDE_CODE_USE_FOUNDRY" ]; }; then
    is_subscriber="1"
fi


# === Gauge fallback cache (Claude Code omits context_window/rate_limits briefly after a mid-session model switch) ===
# Multi-line, keyed by session_id (cwd fallback) so concurrent sessions in the same
# directory never pick up each other's gauges.
GAUGE_CACHE="$HOME/.claude/statusline_gauges.cache"
GAUGE_TTL=20
gauge_key="${session_id:-$cwd}"
g_ctx="" g_h5="" g_h5r="" g_d7="" g_d7r="" g_ts=""
if [ -n "$gauge_key" ] && [ -f "$GAUGE_CACHE" ]; then
    IFS='|' read -r _ g_ctx g_h5 g_h5r g_d7 g_d7r g_ts <<< "$(awk -F'|' -v k="$gauge_key" '$1==k {print; exit}' "$GAUGE_CACHE" 2>/dev/null)"
    if [[ "$g_ts" =~ ^[0-9]+$ ]] && [ $(( now - g_ts )) -lt $GAUGE_TTL ]; then
        [ -z "$ctx_pct" ] && [ -n "$g_ctx" ] && ctx_pct="$g_ctx"
        [ -z "$h5_pct" ] && [ -n "$g_h5" ] && h5_pct="$g_h5" && h5_reset="$g_h5r"
        [ -z "$d7_pct" ] && [ -n "$g_d7" ] && d7_pct="$g_d7" && d7_reset="$g_d7r"
    fi
fi
if [ -n "$gauge_key" ] && { [ -n "$ctx_pct" ] || [ -n "$h5_pct" ] || [ -n "$d7_pct" ]; }; then
    # skip the disk write while values are unchanged and the entry is still fresh
    if [ "$g_ctx" != "$ctx_pct" ] || [ "$g_h5" != "$h5_pct" ] || [ "$g_h5r" != "${h5_reset:-0}" ] || \
       [ "$g_d7" != "$d7_pct" ] || [ "$g_d7r" != "${d7_reset:-0}" ] || \
       ! [[ "$g_ts" =~ ^[0-9]+$ ]] || [ $(( now - g_ts )) -ge $(( GAUGE_TTL / 2 )) ]; then
        {
            awk -F'|' -v k="$gauge_key" -v now="$now" 'NF==7 && $1 != k && (now - $7) < 3600' "$GAUGE_CACHE" 2>/dev/null
            printf '%s|%s|%s|%s|%s|%s|%s\n' "$gauge_key" "$ctx_pct" "$h5_pct" "${h5_reset:-0}" "$d7_pct" "${d7_reset:-0}" "$now"
        } > "${GAUGE_CACHE}.tmp.$$" && mv "${GAUGE_CACHE}.tmp.$$" "$GAUGE_CACHE"
    fi
fi

fmt_reset_hm() {
    [ -z "$1" ] && echo "soon" && return
    local diff=$(( $1 - now ))
    [ $diff -le 0 ] && echo "soon" && return
    date -d "@$1" "+%H:%M" 2>/dev/null || date -r "$1" "+%H:%M" 2>/dev/null || echo "soon"
}

fmt_reset_dh() {
    [ -z "$1" ] && echo "soon" && return
    local diff=$(( $1 - now ))
    [ $diff -le 0 ] && echo "soon" && return
    local d=$(( diff / 86400 ))
    local h=$(( (diff % 86400) / 3600 ))
    local m=$(( (diff % 3600) / 60 ))
    if [ $d -gt 0 ]; then echo "${d}d${h}h"; else echo "${h}h${m}m"; fi
}

# === JPY rate (weekly cache, background refresh; CC_STATUSLINE_JPY=0 disables) ===
JPY_CACHE="$HOME/.claude/jpy_rate.cache"
JPY_LOCK="$HOME/.claude/jpy_rate.lock"
JPY_FAIL="$HOME/.claude/jpy_rate.fail"
jpy_rate=""
if [ "${CC_STATUSLINE_JPY:-1}" != "0" ]; then
    jpy_fresh=""
    if [ -f "$JPY_CACHE" ]; then
        IFS=: read -r cached_ts cached_rate < "$JPY_CACHE"
        if [ -n "$cached_rate" ]; then
            jpy_rate="$cached_rate"   # a stale rate still beats no rate; refreshed below
            [[ "$cached_ts" =~ ^[0-9]+$ ]] && [ $(( now - cached_ts )) -lt 604800 ] && jpy_fresh=1
        fi
    fi
    if [ -z "$jpy_fresh" ]; then
        fail_age=$(( now - $(stat_mtime "$JPY_FAIL") ))
        lock_age=$(( now - $(stat_mtime "$JPY_LOCK") ))
        if [ "$fail_age" -gt 3600 ] && [ "$lock_age" -gt 30 ]; then
            touch "$JPY_LOCK" 2>/dev/null
            (
                fetched=$(curl -sf --max-time 5 "https://api.frankfurter.dev/v1/latest?from=USD&to=JPY" | jq -r '.rates.JPY // empty')
                if [ -n "$fetched" ]; then
                    printf '%s:%s' "$now" "$fetched" > "${JPY_CACHE}.tmp.$$" && mv "${JPY_CACHE}.tmp.$$" "$JPY_CACHE"
                    rm -f "$JPY_FAIL"
                else
                    touch "$JPY_FAIL" 2>/dev/null   # back off retries for 1h (offline/blocked)
                fi
                rm -f "$JPY_LOCK"
            ) >/dev/null 2>&1 &
        fi
    fi
fi

# === Build output ===
out=""

# Model prefix with effort level
if [ -n "$model_display" ]; then
    model_short=$(echo "$model_display" | tr -d ' ')
    model_str="${model_short}"
    [ -n "$effort" ] && model_str="${model_str}(${effort})"
    if [[ "$model_id" == *"opus"* ]]; then
        out="${C_AMBER}!${model_str}${C_RESET}"
    elif [[ "$model_id" == *"fable"* ]]; then
        out="${C_RED}!!${model_str}${C_RESET}"
    else
        out="${C_PURPLE}${model_str}${C_RESET}"
    fi
fi

# Session rate limit
if [ -n "$h5_pct" ]; then
    rst=$(fmt_reset_hm "$h5_reset")
    c=$(color_for_rate "$h5_pct" "$h5_reset" $SESSION_WINDOW_SEC)
    [ -n "$out" ] && out="$out "
    out="${out}${C_DIM}Session:${C_RESET}${c}${h5_pct}%${C_DIM}(${rst})${C_RESET}"
elif [ -z "$has_rl" ] && [ -n "$model_display" ] && [ -n "$is_subscriber" ]; then
    [ -n "$out" ] && out="$out "
    out="${out}${C_DIM}Session:-${C_RESET}"
fi

# Week rate limit
if [ -n "$d7_pct" ]; then
    rst=$(fmt_reset_dh "$d7_reset")
    c=$(color_for_rate "$d7_pct" "$d7_reset" $WEEK_WINDOW_SEC)
    [ -n "$out" ] && out="$out "
    out="${out}${C_DIM}Week:${C_RESET}${c}${d7_pct}%${C_DIM}(${rst})${C_RESET}"
elif [ -z "$has_rl" ] && [ -n "$model_display" ] && [ -n "$is_subscriber" ]; then
    [ -n "$out" ] && out="$out "
    out="${out}${C_DIM}Week:-${C_RESET}"
fi

# Context window
if [ -n "$ctx_pct" ]; then
    c=$(color_for_pct "$ctx_pct")
    [ -n "$out" ] && out="$out "
    out="${out}${C_DIM}Ctx:${C_RESET}${c}$(draw_bar "$ctx_pct")${C_RESET}${c}${ctx_pct}%${C_RESET}"
fi

# transcript_path fallback: some Claude Code versions/auth paths omit transcript_path
# from the statusLine hook JSON. Locate the most recently modified transcript in the
# project-specific directory (derived from cwd, same convention Claude Code itself uses)
# as a best-effort substitute.
if [ -z "$transcript_path" ] && [ -n "$cwd" ]; then
    proj_dir="$HOME/.claude/projects/$(printf '%s' "$cwd" | tr '/' '-')"
    if [ -d "$proj_dir" ]; then
        transcript_path=$(ls -t "$proj_dir"/*.jsonl 2>/dev/null | head -n 1)
    fi
fi

# === Fallback cost estimate (Claude Code omits cost.total_cost_usd for Azure/Bedrock/Vertex-routed sessions) ===
# Always computed in the background — a large transcript must never block a render.
# Cache holds one line per transcript so concurrent sessions don't thrash each other.
cost_is_estimate=""
if [ -z "$cost_usd" ] && [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
    EST_CACHE="$HOME/.claude/cost_estimate.cache"
    EST_LOCK="$HOME/.claude/cost_estimate.$(printf '%s' "$transcript_path" | cksum | cut -d' ' -f1).lock"
    t_mtime=$(stat_mtime "$transcript_path")
    IFS='|' read -r _ cached_mtime cached_val <<< "$(awk -F'|' -v k="$transcript_path" '$1==k {print; exit}' "$EST_CACHE" 2>/dev/null)"
    if [ "$cached_mtime" != "$t_mtime" ]; then
        lock_age=$(( now - $(stat_mtime "$EST_LOCK") ))
        if [ "$lock_age" -gt 30 ]; then
            touch "$EST_LOCK" 2>/dev/null
            (
                computed=$(compute_cost_estimate "$transcript_path")
                if [ -n "$computed" ]; then
                    {
                        awk -F'|' -v k="$transcript_path" '$1 != k' "$EST_CACHE" 2>/dev/null | tail -n 7
                        printf '%s|%s|%s\n' "$transcript_path" "$t_mtime" "$computed"
                    } > "${EST_CACHE}.tmp.$$" && mv "${EST_CACHE}.tmp.$$" "$EST_CACHE"
                fi
                rm -f "$EST_LOCK"
            ) >/dev/null 2>&1 &
        fi
    fi
    if [ -n "$cached_val" ]; then
        # show the last known value while the background refresh catches up;
        # first render of a brand-new transcript just skips the cost segment once
        cost_usd="$cached_val"
        cost_is_estimate=1
    fi
fi

# Daily cost — per-session ledger so concurrent sessions can't inflate the total.
# Cache format: line 1 = date, then "<session_key>|<baseline_usd>|<banked_usd>|<latest_session_usd>".
# baseline is the session's cumulative cost_usd as of the start of "today" (0 for a
# session that began today, or its last known cost_usd from a previous day for a
# session still running across a midnight boundary) so only the delta actually
# spent today is counted, not the whole session-lifetime total. A separate
# cross-day cache (SESSION_STATE_CACHE) is never wiped daily and supplies that
# baseline the first time a still-running session is seen on a new date.
# NOTE: jpy_rate is best-effort (fetched over the network); the $ cost must still
# display even when the JPY conversion is unavailable (offline, blocked host, etc.)
if [ -n "$cost_usd" ]; then
    BUDGET_CACHE="$HOME/.claude/cost_budget.cache"
    SESSION_STATE_CACHE="$HOME/.claude/cost_session_state.cache"
    cur_date=$(date +%Y-%m-%d)
    session_key="${session_id:-${transcript_path:-default}}"
    baseline="0"
    accum="0"
    last="0"
    others=""
    own_found=""
    old_content=""
    [ -f "$BUDGET_CACHE" ] && old_content=$(cat "$BUDGET_CACHE" 2>/dev/null)
    if [ "${old_content%%$'\n'*}" = "$cur_date" ]; then
        own_line=$(printf '%s\n' "$old_content" | awk -F'|' -v k="$session_key" 'NR>1 && $1==k {print; exit}')
        if [ -n "$own_line" ]; then
            own_found=1
            IFS='|' read -r _ f2 f3 f4 <<< "$own_line"
            if [ -n "$f4" ]; then
                # current 4-field format: key|baseline|banked|latest
                baseline="$f2"; accum="$f3"; last="$f4"
            else
                # legacy 3-field format: key|banked|latest (baseline implicitly 0)
                baseline="0"; accum="$f2"; last="$f3"
            fi
            [ -n "$baseline" ] || baseline="0"
            [ -n "$accum" ] || accum="0"
            [ -n "$last" ] || last="0"
        fi
        others=$(printf '%s\n' "$old_content" | awk -F'|' -v k="$session_key" 'NR>1 && NF>=3 && $1!=k')
    fi

    if [ -z "$own_found" ]; then
        # First render of this session today (brand-new session, or the day just
        # rolled over while this session kept running). Look up its last known
        # cost from the cross-day cache to use as today's starting offset.
        if [ -f "$SESSION_STATE_CACHE" ]; then
            state_line=$(awk -F'|' -v k="$session_key" '$1==k {print; exit}' "$SESSION_STATE_CACHE" 2>/dev/null)
            if [ -n "$state_line" ]; then
                IFS='|' read -r _ _ state_cost <<< "$state_line"
                [ -n "$state_cost" ] && baseline="$state_cost"
            fi
        fi
        accum="0"
        last="$cost_usd"
    fi

    # cost dropped => this session restarted (/clear, resume): Claude Code's own
    # cost_usd counter already restarts at zero in this case, so bank the
    # contribution accrued so far and reset baseline to zero (not cost_usd —
    # a nonzero baseline is only for the day-rollover case, where the counter
    # keeps counting rather than resetting)
    if awk -v cur="$cost_usd" -v last="$last" 'BEGIN {exit !(cur < last)}' 2>/dev/null; then
        accum=$(awk -v a="$accum" -v l="$last" -v b="$baseline" 'BEGIN {print a + (l - b)}')
        baseline="0"
    fi

    new_content="${cur_date}"$'\n'"${session_key}|${baseline}|${accum}|${cost_usd}"
    [ -n "$others" ] && new_content="${new_content}"$'\n'"${others}"
    if [ "$new_content" != "$old_content" ]; then
        printf '%s\n' "$new_content" > "${BUDGET_CACHE}.tmp.$$" && mv "${BUDGET_CACHE}.tmp.$$" "$BUDGET_CACHE"
    fi

    # Persist this session's latest cost (cross-day, never wiped) so a future day
    # rollover can compute the correct baseline for this session if it is still running.
    {
        [ -f "$SESSION_STATE_CACHE" ] && awk -F'|' -v k="$session_key" '$1!=k' "$SESSION_STATE_CACHE" 2>/dev/null | tail -n 49
        printf '%s|%s|%s\n' "$session_key" "$cur_date" "$cost_usd"
    } > "${SESSION_STATE_CACHE}.tmp.$$" && mv "${SESSION_STATE_CACHE}.tmp.$$" "$SESSION_STATE_CACHE"

    others_sum=$(printf '%s\n' "$others" | awk -F'|' '
        NF>=4 {s += $3 + ($4 - $2); next}
        NF==3 {s += $2 + $3}
        END {printf "%.6f", s+0}')
    total_usd=$(awk -v a="$accum" -v c="$cost_usd" -v b="$baseline" -v o="$others_sum" 'BEGIN {print a + (c - b) + o}')
    cost_fmt=$(printf "%.2f" "$total_usd")
    est_prefix=""; { [ -n "$cost_is_estimate" ] || [ -n "$is_subscriber" ]; } && est_prefix="~"

    budget_jpy="${CC_STATUSLINE_BUDGET_JPY:-500}"
    [[ "$budget_jpy" =~ ^[0-9]+$ ]] || budget_jpy=500

    if [ -n "$jpy_rate" ]; then
        total_jpy=$(awk -v tot="$total_usd" -v rate="$jpy_rate" 'BEGIN {printf "%d", tot * rate + 0.5}')
        session_jpy=$(awk -v cur="$cost_usd" -v base="$baseline" -v rate="$jpy_rate" 'BEGIN {printf "%d", (cur - base) * rate + 0.5}')

        if [ "${total_jpy:-0}" -gt 0 ] 2>/dev/null; then
            if [ -n "$is_subscriber" ]; then
                [ -n "$out" ] && out="$out "
                out="${out}${C_DIM}Cost:${C_RESET}${C_GREEN}~\$${cost_fmt}${C_DIM}(${C_RESET}${C_GREEN}~¥$(add_commas "$total_jpy")${C_DIM})${C_RESET}"
            elif [ "$budget_jpy" -gt 0 ]; then
                pct=$(( total_jpy * 100 / budget_jpy ))
                [ $pct -gt 100 ] && pct=100
                c=$(color_for_pct "$pct")
                warn=""
                [ $pct -ge 100 ] && warn="!!"
                [ -n "$out" ] && out="$out "
                out="${out}${C_DIM}Cost:${C_RESET}${c}${warn}$(draw_bar "$pct")${C_RESET}${c}${est_prefix}\$${cost_fmt}${C_RESET}${C_DIM}(${C_RESET}${c}¥$(add_commas "$session_jpy")${C_RESET} ${C_DIM}Today:${C_RESET}${c}¥$(add_commas "$total_jpy")${C_DIM}/¥$(add_commas "$budget_jpy"))${C_RESET}"
            else
                # budget disabled (CC_STATUSLINE_BUDGET_JPY=0): amounts only, no bar
                [ -n "$out" ] && out="$out "
                out="${out}${C_DIM}Cost:${C_RESET}${C_GREEN}${est_prefix}\$${cost_fmt}${C_DIM}(${C_RESET}${C_GREEN}¥$(add_commas "$session_jpy")${C_RESET} ${C_DIM}Today:${C_RESET}${C_GREEN}¥$(add_commas "$total_jpy")${C_DIM})${C_RESET}"
            fi
        fi
    elif awk -v tot="$total_usd" 'BEGIN {exit !(tot > 0)}' 2>/dev/null; then
        # JPY rate not yet cached (offline / blocked / disabled) — show plain $ amount, no bar/budget
        [ -n "$out" ] && out="$out "
        out="${out}${C_DIM}Cost:${C_RESET}${C_GREEN}${est_prefix}\$${cost_fmt}${C_RESET}"
    fi
fi

printf "%s" "$out"

More status lines

Activity Feed

51 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
🌿 main │ Opus 4.8/hi │ 🟢 Ctx ▓▓░░░░ 22% │ ⏳ 🟢 ▓▓░░░░ 26% ↻ 2h7m │ $0.41 ⏱ 10m
Model: Opus 4.8 | Thinking: high | Context: [▰▰▱▱▱▱▱▱▱▱] 45k/200k (22%) | cwd: ~/app | ⎇ main | (+0,-0) Cost: $0.41 | Block: 2hr 6m | Weekly: 7.0% | Session: 10m | In: 916.7 t/s | Out: 29.2 t/s | Cached: 2k | Session: [▰▱▱▱▱] 26.0% Mem: 0.8G/1.0G | (*゚∀゚*) | Session ID: b8e1c0d2-4a6f-4e2a-9c1b-3f5d7a9e2c10
Opus 4.8 | app@main | ctx 44k/200k (22%) | cost $0.41 | 5h 26% | 7d 7%
󰚩 Opus 4.8 │ 󰍛 ▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱ 22% │ 󰄉 $0.41 󰝰 app │ 󰘬 main
Opus 4.8 [high] ~/app main 16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m

Related mods

Terminal

❯ /deck demo ⎿ deck: Sample plan running above the prompt, with sample routing until real ones arrive. [-] 0: deck ▾ · 5 rows 22:11:41 ⇄ Model route opus = opus jev: haiku · not applied effort ▁▃·· medi = kept ✕ ✓ Orders module Done · 12/12 · 2/2 agents 100% ✕ ● Context 44k / 200k 22% ✕ ◔ Session · 5h 5h · 26% used ↻ 2:04:24 ✕ ◔ Weekly · 7d 7d · 7% used ↻ 2d 0h 57m ✕ ❯  ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents [ Deck 1 ]

Claude Desktop

Deck in Claude Desktop

A cockpit for Claude Code: plan bars and subagent strips, context, usage limits with reset countdowns and burn rates, spend, the model route and a pet. Six bar styles, three collapsed looks. CLI and Desktop.

Terminal

❯ Run the tests again ● The repo is clean and the tests pass. ✻ Cooked for 0s · done 10:12 PM ☀ 44k │ 5h 26% │ 7d 7% │ cache 5% [-] ❯ 

Claude Desktop

Token Weather Usage in Claude Desktop

One line above the prompt: context tokens with their weather, a bar per recent prompt, your 5-hour and 7-day limits against the time elapsed, the prompt cache with its time left, the session cost with the last prompt's share in dollars and in points of the 5-hour limit, and the subagents running. Labels in English, French or Russian. After Token Weather (Anthropic, Apache-2.0) and the ideas of usage-meter (HolyGrail) and prompt-cache-control (Daniel San).

Terminal

BURN ▁ ▂ *▂▆ ▃ $0.41 = 0.04 burritos [-] 5h 26% in 2h 5m ▅▄█▅▅▆█▆███▅█ opus 4.8 44k in 1k out wk 7% in 2d 0h █████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ lifetime $0.41 hide ❯ 

Claude Desktop

Burn Meter in Claude Desktop

Live session cost odometer above the prompt: a burning fuse, real-world comparisons, threshold alerts

Terminal

[-] zZ ▀▄ ▄▀ ▄▄▀▀▀▀▀▀▀▀▀▀ ▀▀▀▄▄▄ ▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ctx 22% [ Pet ♥ ] [ ⚙ ] [ ↻ Adventurer ] [ Hide ] ❯ 

Claude Desktop

Pixel Cat in Claude Desktop

A little pixel cat that wanders above the Claude Code prompt, watches your usage limits and announces when work finishes; or switch packs for an adventurer who mines, chops trees and fights zombies