statuslin.es

Rate-limit Pace + Context + JPY Cost Gauge

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

Updated 2026-07-18

Shows:TokensCostLimitsBurn RateBashNetwork Access

Preview

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

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"

What it shows

  • Active model and effort level, with stronger markers for Opus and Fable models
  • Five-hour session and seven-day usage percentages with reset times and pace-aware warning colors
  • Context window usage as a five-segment gauge and percentage
  • Daily cost in USD, with an optional JPY conversion and budget gauge

Requirements

  • Bash with jq, curl, awk, sed, date, stat, rev, cksum, head, and tail available
  • Network access to api.frankfurter.dev for the optional USD-to-JPY exchange rate
  • Read access to the Claude Code transcript path for fallback cost estimates
  • Write access to ~/.claude for gauge, exchange-rate, and cost cache files

Behavior notes

  • Usage colors can warn early when projected consumption is ahead of the reset-window pace
  • When rate-limit data is unavailable for a subscription session, Session and Week display a dash
  • The JPY rate is cached for a week and refreshed in the background; USD cost still displays when the rate is unavailable
  • When Claude Code omits cost data, the script estimates it from the local transcript in the background, so cost may be absent on the first render
  • CC_STATUSLINE_JPY=0 disables JPY conversion, and CC_STATUSLINE_BUDGET_JPY controls the daily JPY budget gauge

More status lines

app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0h
app · main · OPUS-4.8 ▓▓░░░░░░ 22% · ▓▓░░░░░ 26% · ▓░░░░░░ 7%
Opus 4.8 ✍️ 22% app (main) ● high current ●●○○○○○○○○ 26% 8:49pm weekly ○○○○○○○○○○ 7% jul 8, 7:42pm
███████████▋ 73% 🗝 Ultima Weapon ✦ app ∙ main ████████████████████████ 100% 「SAVE POINT」 ✶ Limit Form ◉ 41
╭─ CONTEXT ────────────────────────────────────────────╮ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Opus 4.8 · main · 17:27 ╰──────────────────────────────────────────────────────╯
Opus 4.8 | main | ██░░░░░░░░ 22.0% | 44k / 200k | 02:07 26% | Wed 7%