关于claude code缓存命中

佬们,有什么软件或者插件能直观的看到处理claude code的缓存命中情况吗

直接登录的话 很难看到吧?

不走登录的话 走apikey之类的话——axonhub之类的就能看

我用的是订阅,没有别的插件能看到命中率吗:joy:

那就很难整了吧… 看看有没有别的佬友 给答案

如果是调用api的话,返回的event里面会有cache字段的,这个字段的值就是命中token数。

订阅有办法嘛?

我前段时间vibecoding了一个自用工具,晚点可以公开一下:smiley:

这个我就不太清楚了

期待佬共享:joy:

cc的 /statusline 里面是可以设置的

/statusline what kind of info can i show using statusline

in the token counts section am I able to see the cached tokens?    

....

{{多问几轮}}

还行 可以加这个 Segment

我用来提醒5分钟缓存是否到期的,其他都是附带的功能,不知道是否满足你的需求

其实不用看,你就知道只要对话间隔不超过5分钟,Claude Code的缓存命中率都在99%到100%之间。超过5分钟缓存失效

楼上有几位都提到了5min缓存,我使用的是5x max订阅,我对话间隔经常超过5min,但总体缓存率还是在90%以上。最近一周使用情况如图,缓存命中率约90.7%。不知道是否有文档说明这个缓存的具体时间。

也回复一下楼主的问题,我一般使用vscode搭配cc,使用的是"Claude Code Usage"这个插件,体验还可以。

最近动手搓了一个,已经非常稳了。

#!/bin/sh
input=$(cat)
STATE="/tmp/claude-sl-state.json"

# Parse current data
echo "$input" > /tmp/claude-sl-debug.json
session_id=$(echo "$input" | jq -r '.session_id')
transcript=$(echo "$input" | jq -r '.transcript_path // ""')
total_out=$(echo "$input" | jq '.context_window.total_output_tokens')
cr=$(echo "$input" | jq '.context_window.current_usage.cache_read_input_tokens')
cw=$(echo "$input" | jq '.context_window.current_usage.cache_creation_input_tokens')
used_pct=$(echo "$input" | jq '.context_window.used_percentage')
win_size=$(echo "$input" | jq '.context_window.context_window_size')
cwd=$(echo "$input" | jq -r '.cwd')
cost_usd=$(echo "$input" | jq '.cost.total_cost_usd // 0')
user=$(whoami)
host=$(scutil --get ComputerName 2>/dev/null || hostname -s)
now=$(date +%s)

# Transcript metrics: turns, nreq, fr, fr_dip, tw (parsed with jq to avoid grep false positives)
turns=0; _t_nreq=0; _t_fr=0; _t_fr_dip=0; _t_tw=0
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
  _metrics=$(jq -s '
    . as $arr |
    ([$arr[] | select(.type == "user" and (.message.content | type) == "string")] | length) as $turns |
    (reduce range($arr | length) as $i (null;
      if $arr[$i].type == "user" and ($arr[$i].message.content | type) == "string"
      then $i else . end
    )) as $last_user |
    if $last_user == null then {turns: $turns, nreq: 0, fr: 0, fr_dip: false, tw: 0}
    else
      # All assistant responses after the last user turn, deduped by message.id.
      # fr defaults to first request cache_read ("1R"); if any later request
      # within the turn reads meaningfully less, fr drops to the low-water-mark
      # and fr_dip flips the label to "∧R".
      #
      # Threshold = N_BLOCKS * BLOCK_SIZE (default 3 * 4096 = 12288 tokens).
      # Rationale: Anthropic caches at a fixed block granularity (~4k for Opus
      # 4.x). Claude Code reshuffles its cache_control breakpoints between
      # consecutive requests in a turn, which routinely orphans 1–2 blocks
      # (observed ≤8k drops in real transcripts — normal reorg, not degradation).
      # A drop of ≥3 blocks is outside the normal reorg band and signals real
      # backend cache loss (TTL expiry, LRU eviction). A percentage component
      # was intentionally dropped: reorg cost scales with block_size, not cache
      # size, so a relative threshold would mis-flag routine 1-block reorgs on
      # large caches. Bump N_BLOCKS if you see false positives; lower it if
      # real eviction events are slipping through.
      [$arr[($last_user + 1):] | .[] | select(.type == "assistant" and .message.id)]
      | unique_by(.message.id) as $msgs |
      ($msgs[0].message.usage.cache_read_input_tokens // 0) as $first |
      ([$msgs[] | .message.usage.cache_read_input_tokens // 0] | min // 0) as $mincr |
      (($first - $mincr) >= 12288) as $dip |
      {turns: $turns, nreq: ($msgs | length),
       fr: (if $dip then $mincr else $first end),
       fr_dip: $dip,
       tw: ([$msgs[] | .message.usage.cache_creation_input_tokens] | add // 0)}
    end
  ' "$transcript" 2>/dev/null)
  if [ -n "$_metrics" ]; then
    turns=$(echo "$_metrics" | jq '.turns')
    _t_nreq=$(echo "$_metrics" | jq '.nreq')
    _t_fr=$(echo "$_metrics" | jq '.fr')
    _t_fr_dip=$(echo "$_metrics" | jq 'if .fr_dip then 1 else 0 end')
    _t_tw=$(echo "$_metrics" | jq '.tw')
  fi
fi

# Load state
if [ -f "$STATE" ]; then
  prev_sid=$(jq -r '.sid // ""' "$STATE")
  prev_out=$(jq '.out // 0' "$STATE")
  nreq=$(jq '.nreq // 0' "$STATE")
  fr=$(jq '.fr // 0' "$STATE")
  fr_dip=$(jq '.fr_dip // 0' "$STATE")
  tw=$(jq '.tw // 0' "$STATE")
  ttl=$(jq -r '.ttl // ""' "$STATE")
  ttl_exp=$(jq '.ttl_exp // 0' "$STATE")
  prev_turns=$(jq '.turns // 0' "$STATE")
  last_t=$(jq '.t // 0' "$STATE")
  prev_cost=$(jq '.prev_cost // 0' "$STATE")
  tc=$(jq '.tc // 0' "$STATE")
else
  prev_sid=""; prev_out=0; nreq=0; fr=0; fr_dip=0; tw=0; ttl=""; ttl_exp=0; prev_turns=0; last_t=0; prev_cost=0; tc=0
fi

# Session change → full reset
if [ "$session_id" != "$prev_sid" ]; then
  prev_out=0; nreq=0; fr=0; fr_dip=0; tw=0; ttl=""; ttl_exp=0; prev_turns=$turns; last_t=$now; tc=$cost_usd; prev_cost=$cost_usd
fi

# New turn detected → update turn-start cost baseline
if [ "$turns" -gt "$prev_turns" ] 2>/dev/null; then
  tc=$prev_cost
fi

# Apply transcript-based metrics (overrides state values)
if [ "$_t_nreq" -gt 0 ] 2>/dev/null; then
  nreq=$_t_nreq; fr=$_t_fr; fr_dip=$_t_fr_dip; tw=$_t_tw
elif [ "$turns" -gt "$prev_turns" ] 2>/dev/null; then
  # New turn started but no assistant response yet → reset
  nreq=0; fr=0; fr_dip=0; tw=0
fi

# Turn cost = current session total - turn start baseline
turn_cost=$(echo "$cost_usd - $tc" | bc -l 2>/dev/null || echo "0")

# Detect new API request (for trace logging and TTL extraction)
if [ "$total_out" != "$prev_out" ]; then
  elapsed=$((now - last_t))
  last_t=$now
  # Extract cache TTL tier from transcript (last assistant message)
  if [ -n "$transcript" ] && [ -f "$transcript" ]; then
    _tier=$(tail -20 "$transcript" | tail -r | while IFS= read -r _line; do
      _t=$(echo "$_line" | jq -r '.type // empty' 2>/dev/null)
      if [ "$_t" = "assistant" ]; then
        _1h=$(echo "$_line" | jq '.message.usage.cache_creation.ephemeral_1h_input_tokens // 0')
        _5m=$(echo "$_line" | jq '.message.usage.cache_creation.ephemeral_5m_input_tokens // 0')
        if [ "$_1h" -gt 0 ] 2>/dev/null; then echo "1h"
        elif [ "$_5m" -gt 0 ] 2>/dev/null; then echo "5m"
        fi
        break
      fi
    done)
    [ -n "$_tier" ] && ttl="$_tier"
  fi
  # Refresh cache expiration anchor whenever a new request lands — Anthropic
  # refreshes prompt-cache TTL on every read, so `now + tier_duration` is the
  # real-world expiry. Rendered as wall-clock time in the output block below.
  case "$ttl" in
    1h) ttl_exp=$((now + 3600)) ;;
    5m) ttl_exp=$((now + 300)) ;;
  esac
  # Trace: capture full current_usage + cost
  uncached=$(echo "$input" | jq '.context_window.current_usage.input_tokens')
  out_tokens=$(echo "$input" | jq '.context_window.current_usage.output_tokens')
  total_in=$(echo "$input" | jq '.context_window.total_input_tokens')
  echo "$(date '+%Y-%m-%d %H:%M:%S') REQ#${nreq} elapsed=${elapsed}s total_out=${prev_out}->${total_out} | current_usage: R=${cr} W=${cw} U=${uncached} out=${out_tokens} | totals: in=${total_in} out=${total_out} | cost=\$${cost_usd} | state: fr=${fr} tw=${tw}" >> /tmp/claude-sl-trace.log
fi

# Save state (t = last request time, not last poll time)
jq -n --arg sid "$session_id" --argjson out "$total_out" \
  --argjson nreq "$nreq" --argjson fr "$fr" --argjson fr_dip "$fr_dip" --argjson tw "$tw" \
  --arg ttl "$ttl" --argjson ttl_exp "$ttl_exp" \
  --argjson turns "$turns" --argjson t "$last_t" \
  --argjson prev_cost "$cost_usd" --argjson tc "$tc" \
  '{sid:$sid, out:$out, nreq:$nreq, fr:$fr, fr_dip:$fr_dip, tw:$tw, ttl:$ttl, ttl_exp:$ttl_exp, turns:$turns, t:$t, prev_cost:$prev_cost, tc:$tc}' > "$STATE"

# Format path: abbreviate each component except the last to its first letter
# /Users/justin/dev26/colab-runtime-2/colab-cli → ~/d/c/colab-cli
fmt_path() {
  _p="$1"
  _home="$HOME"
  case "$_p" in
    "$_home") echo "~"; return ;;
    "$_home"/*) _p="~/${_p#$_home/}" ;;
  esac
  case "$_p" in
    */*) : ;;
    *) echo "$_p"; return ;;
  esac
  _base="${_p##*/}"
  _dir="${_p%/*}"
  _out=""
  case "$_p" in
    /*) _out="/" ;;
  esac
  _dir="${_dir#/}"
  while [ -n "$_dir" ]; do
    case "$_dir" in
      */*) _seg="${_dir%%/*}"; _dir="${_dir#*/}" ;;
      *)   _seg="$_dir"; _dir="" ;;
    esac
    _first=$(printf '%s' "$_seg" | cut -c1)
    _out="${_out}${_first}/"
  done
  echo "${_out}${_base}"
}

# Format token count (19317 → 19.3k)
fmt() {
  if [ "$1" -ge 1000 ] 2>/dev/null; then
    printf "%.1fk" "$(echo "$1 / 1000" | bc -l)"
  else
    echo "${1:-0}"
  fi
}

# Format cost (0.1234 → 12¢, 1.234 → $1.23)
fmt_cost() {
  echo "$1" | awk '{c=$1; if(c<0.01) printf "0¢"; else if(c<1) printf "%d¢",c*100+0.5; else printf "$%.2f",c}'
}

# Context: approximate used tokens from percentage
used_tokens=$(( win_size * used_pct / 100 ))

# Output

# TTL tag format: "<tier> <expiry>" — e.g. "1h 17:36:" or "5m :41:40".
# 1h tier shows HH:MM: (wall-clock hour:minute with trailing colon);
# 5m tier shows :MM:SS (leading colon + minute:second of expiry wall-clock).
# Expiry suffix is suppressed if the anchor has already passed (shows bare
# tier instead).
ttl_tag=""
if [ -n "$ttl" ]; then
  ttl_tag=" ${ttl}"
  if [ "$ttl_exp" -gt "$now" ] 2>/dev/null; then
    case "$ttl" in
      1h) ttl_tag="${ttl_tag} $(date -r "$ttl_exp" "+%H:%M:")" ;;
      5m) ttl_tag="${ttl_tag} $(date -r "$ttl_exp" "+:%M:%S")" ;;
    esac
  fi
fi

# Cache-read label: "1R" = first request of the turn (warm baseline);
# "∧R" = low-water-mark, flipped when any later request in the turn reads
# fewer cached tokens than the first (signals mid-turn cache degradation).
if [ "$nreq" -gt 0 ]; then
  r_label="1R"
  [ "$fr_dip" = "1" ] && r_label="∧R"
  echo "${user}@${host} $(fmt_path "$cwd") | ctx $(fmt "$used_tokens")${ttl_tag} | ${r_label} $(fmt "$fr") - Rq ${nreq} - ΣW $(fmt "$tw") - $(fmt_cost "$turn_cost")"
else
  echo "${user}@${host} $(fmt_path "$cwd") | ctx $(fmt "$used_tokens")"
fi

保存为 statusline-command.sh 放在 ~/.claude/ 路径下,然后 chmod u+x ~/.claude/statusline.command.sh 就装上了。

感谢佬的脚本!我在这个脚本的基础上做了点 linux 适配,并提高了 ctx 精度之后就直接用上了 :smiling_face_with_three_hearts:

#!/bin/sh
input=$(cat)
STATE="/tmp/claude-sl-state.json"

# Parse current data
echo "$input" > /tmp/claude-sl-debug.json
session_id=$(echo "$input" | jq -r '.session_id')
transcript=$(echo "$input" | jq -r '.transcript_path // ""')
total_out=$(echo "$input" | jq '.context_window.total_output_tokens')
cr=$(echo "$input" | jq '.context_window.current_usage.cache_read_input_tokens')
cw=$(echo "$input" | jq '.context_window.current_usage.cache_creation_input_tokens')
used_pct=$(echo "$input" | jq '.context_window.used_percentage')
win_size=$(echo "$input" | jq '.context_window.context_window_size')
cwd=$(echo "$input" | jq -r '.cwd')
cost_usd=$(echo "$input" | jq '.cost.total_cost_usd // 0')
user=$(whoami)
host=$(scutil --get ComputerName 2>/dev/null || hostname -s)
now=$(date +%s)

# Transcript metrics: turns, nreq, fr, fr_dip, tw (parsed with jq to avoid grep false positives)
turns=0; _t_nreq=0; _t_fr=0; _t_fr_dip=0; _t_tw=0
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
  _metrics=$(jq -s '
    . as $arr |
    # A "real user turn" excludes Claude Code meta/slash-command entries.
    # Slash commands (/effort, /fast, etc.) and local-command-caveat blocks are
    # written to transcript as type=user string-content entries, and without
    # filtering they inflate $turns and can shift $last_user to a command entry
    # with no assistant response after it — causing the right half of the
    # statusline to collapse to just "ctx" after a user runs /effort.
    def is_real_user: .type == "user"
      and (.message.content | type) == "string"
      and ((.isMeta // false) == false)
      and ((.message.content | startswith("<")) | not);
    ([$arr[] | select(is_real_user)] | length) as $turns |
    (reduce range($arr | length) as $i (null;
      if ($arr[$i] | is_real_user) then $i else . end
    )) as $last_user |
    if $last_user == null then {turns: $turns, nreq: 0, fr: 0, fr_dip: false, tw: 0}
    else
      # All assistant responses after the last user turn, deduped by message.id.
      # fr defaults to first request cache_read ("1R"); if any later request
      # within the turn reads meaningfully less, fr drops to the low-water-mark
      # and fr_dip flips the label to "∧R".
      #
      # Threshold = N_BLOCKS * BLOCK_SIZE (default 3 * 4096 = 12288 tokens).
      # Rationale: Anthropic caches at a fixed block granularity (~4k for Opus
      # 4.x). Claude Code reshuffles its cache_control breakpoints between
      # consecutive requests in a turn, which routinely orphans 1–2 blocks
      # (observed ≤8k drops in real transcripts — normal reorg, not degradation).
      # A drop of ≥3 blocks is outside the normal reorg band and signals real
      # backend cache loss (TTL expiry, LRU eviction). A percentage component
      # was intentionally dropped: reorg cost scales with block_size, not cache
      # size, so a relative threshold would mis-flag routine 1-block reorgs on
      # large caches. Bump N_BLOCKS if you see false positives; lower it if
      # real eviction events are slipping through.
      [$arr[($last_user + 1):] | .[] | select(.type == "assistant" and .message.id)]
      | unique_by(.message.id) as $msgs |
      ($msgs[0].message.usage.cache_read_input_tokens // 0) as $first |
      ([$msgs[] | .message.usage.cache_read_input_tokens // 0] | min // 0) as $mincr |
      (($first - $mincr) >= 12288) as $dip |
      {turns: $turns, nreq: ($msgs | length),
       fr: (if $dip then $mincr else $first end),
       fr_dip: $dip,
       tw: ([$msgs[] | .message.usage.cache_creation_input_tokens] | add // 0)}
    end
  ' "$transcript" 2>/dev/null)
  if [ -n "$_metrics" ]; then
    turns=$(echo "$_metrics" | jq '.turns')
    _t_nreq=$(echo "$_metrics" | jq '.nreq')
    _t_fr=$(echo "$_metrics" | jq '.fr')
    _t_fr_dip=$(echo "$_metrics" | jq 'if .fr_dip then 1 else 0 end')
    _t_tw=$(echo "$_metrics" | jq '.tw')
  fi
fi

# Load state
if [ -f "$STATE" ]; then
  prev_sid=$(jq -r '.sid // ""' "$STATE")
  prev_out=$(jq '.out // 0' "$STATE")
  nreq=$(jq '.nreq // 0' "$STATE")
  fr=$(jq '.fr // 0' "$STATE")
  fr_dip=$(jq '.fr_dip // 0' "$STATE")
  tw=$(jq '.tw // 0' "$STATE")
  ttl=$(jq -r '.ttl // ""' "$STATE")
  ttl_exp=$(jq '.ttl_exp // 0' "$STATE")
  prev_turns=$(jq '.turns // 0' "$STATE")
  last_t=$(jq '.t // 0' "$STATE")
  prev_cost=$(jq '.prev_cost // 0' "$STATE")
  tc=$(jq '.tc // 0' "$STATE")
else
  prev_sid=""; prev_out=0; nreq=0; fr=0; fr_dip=0; tw=0; ttl=""; ttl_exp=0; prev_turns=0; last_t=0; prev_cost=0; tc=0
fi

# Session change → full reset
if [ "$session_id" != "$prev_sid" ]; then
  prev_out=0; nreq=0; fr=0; fr_dip=0; tw=0; ttl=""; ttl_exp=0; prev_turns=$turns; last_t=$now; tc=$cost_usd; prev_cost=$cost_usd
fi

# New turn detected → rebase cost baseline immediately so turn_cost starts at 0.
# We keep nreq/fr/tw at the PREVIOUS turn's values during the "wait gap" (no
# response yet) so the statusline doesn't collapse to just "ctx". Mixed signal
# during the gap (cost=0¢ but stats from last turn) is acceptable: each field
# still reflects something meaningful on its own.
if [ "$turns" -gt "$prev_turns" ] 2>/dev/null; then
  tc=$prev_cost
fi

# Apply transcript-based metrics only when current turn has responses. If no
# responses yet (gap between user submit and first assistant reply), keep
# the previous turn's values visible.
if [ "$_t_nreq" -gt 0 ] 2>/dev/null; then
  nreq=$_t_nreq; fr=$_t_fr; fr_dip=$_t_fr_dip; tw=$_t_tw
fi

# Turn cost = current session total - turn start baseline
turn_cost=$(awk -v a="$cost_usd" -v b="$tc" 'BEGIN{printf "%.6f", a-b}' 2>/dev/null || echo "0")

# Detect new API request (for trace logging and TTL extraction)
if [ "$total_out" != "$prev_out" ]; then
  elapsed=$((now - last_t))
  last_t=$now
  # Extract cache TTL tier from transcript (last assistant message)
  if [ -n "$transcript" ] && [ -f "$transcript" ]; then
    _tier=$(tail -20 "$transcript" | tac | while IFS= read -r _line; do
      _t=$(echo "$_line" | jq -r '.type // empty' 2>/dev/null)
      if [ "$_t" = "assistant" ]; then
        _1h=$(echo "$_line" | jq '.message.usage.cache_creation.ephemeral_1h_input_tokens // 0')
        _5m=$(echo "$_line" | jq '.message.usage.cache_creation.ephemeral_5m_input_tokens // 0')
        if [ "$_1h" -gt 0 ] 2>/dev/null; then echo "1h"
        elif [ "$_5m" -gt 0 ] 2>/dev/null; then echo "5m"
        fi
        break
      fi
    done)
    [ -n "$_tier" ] && ttl="$_tier"
  fi
  # Refresh cache expiration anchor whenever a new request lands — Anthropic
  # refreshes prompt-cache TTL on every read, so `now + tier_duration` is the
  # real-world expiry. Rendered as wall-clock time in the output block below.
  case "$ttl" in
    1h) ttl_exp=$((now + 3600)) ;;
    5m) ttl_exp=$((now + 300)) ;;
  esac
  # Trace: capture full current_usage + cost
  uncached=$(echo "$input" | jq '.context_window.current_usage.input_tokens')
  out_tokens=$(echo "$input" | jq '.context_window.current_usage.output_tokens')
  total_in=$(echo "$input" | jq '.context_window.total_input_tokens')
  echo "$(date '+%Y-%m-%d %H:%M:%S') REQ#${nreq} elapsed=${elapsed}s total_out=${prev_out}->${total_out} | current_usage: R=${cr} W=${cw} U=${uncached} out=${out_tokens} | totals: in=${total_in} out=${total_out} | cost=\$${cost_usd} | state: fr=${fr} tw=${tw}" >> /tmp/claude-sl-trace.log
fi

# Save state (t = last request time, not last poll time)
jq -n --arg sid "$session_id" --argjson out "$total_out" \
  --argjson nreq "$nreq" --argjson fr "$fr" --argjson fr_dip "$fr_dip" --argjson tw "$tw" \
  --arg ttl "$ttl" --argjson ttl_exp "$ttl_exp" \
  --argjson turns "$turns" --argjson t "$last_t" \
  --argjson prev_cost "$cost_usd" --argjson tc "$tc" \
  '{sid:$sid, out:$out, nreq:$nreq, fr:$fr, fr_dip:$fr_dip, tw:$tw, ttl:$ttl, ttl_exp:$ttl_exp, turns:$turns, t:$t, prev_cost:$prev_cost, tc:$tc}' > "$STATE"

# Format path: abbreviate each component except the last to its first letter
# /Users/justin/dev26/colab-runtime-2/colab-cli → ~/d/c/colab-cli
fmt_path() {
  _p="$1"
  _home="$HOME"
  case "$_p" in
    "$_home") echo "~"; return ;;
    "$_home"/*) _p="~/${_p#$_home/}" ;;
  esac
  case "$_p" in
    */*) : ;;
    *) echo "$_p"; return ;;
  esac
  _base="${_p##*/}"
  _dir="${_p%/*}"
  _out=""
  case "$_p" in
    /*) _out="/" ;;
  esac
  _dir="${_dir#/}"
  while [ -n "$_dir" ]; do
    case "$_dir" in
      */*) _seg="${_dir%%/*}"; _dir="${_dir#*/}" ;;
      *)   _seg="$_dir"; _dir="" ;;
    esac
    _first=$(printf '%s' "$_seg" | cut -c1)
    _out="${_out}${_first}/"
  done
  echo "${_out}${_base}"
}

# Format token count (19317 → 19.3k)
fmt() {
  if [ "$1" -ge 1000 ] 2>/dev/null; then
    printf "%.1fk" "$(awk -v n="$1" 'BEGIN{print n/1000}')"
  else
    echo "${1:-0}"
  fi
}

# Format cost (0.1234 → 12¢, 1.234 → $1.23)
fmt_cost() {
  echo "$1" | awk '{c=$1; if(c<0.01) printf "0¢"; else if(c<1) printf "%d¢",c*100+0.5; else printf "$%.2f",c}'
}

# Context: exact used tokens from current_usage.
# Includes output_tokens because the last response is already part of the conversation
# and will be fed as input on the next request. Matches Claude Code's official ctx count.
used_tokens=$(echo "$input" | jq '
  (.context_window.current_usage.input_tokens // 0)
  + (.context_window.current_usage.output_tokens // 0)
  + (.context_window.current_usage.cache_creation_input_tokens // 0)
  + (.context_window.current_usage.cache_read_input_tokens // 0)')

# Output

# TTL tag format: "<tier> <expiry>" — e.g. "1h 17:36:" or "5m :41:40".
# 1h tier shows HH:MM: (wall-clock hour:minute with trailing colon);
# 5m tier shows :MM:SS (leading colon + minute:second of expiry wall-clock).
# Expiry suffix is suppressed if the anchor has already passed (shows bare
# tier instead).
ttl_tag=""
if [ -n "$ttl" ]; then
  ttl_tag=" ${ttl}"
  if [ "$ttl_exp" -gt "$now" ] 2>/dev/null; then
    case "$ttl" in
      1h) ttl_tag="${ttl_tag} $(date -d "@$ttl_exp" "+%H:%M:")" ;;
      5m) ttl_tag="${ttl_tag} $(date -d "@$ttl_exp" "+:%M:%S")" ;;
    esac
  fi
fi

# Cache-read label: "1R" = first request of the turn (warm baseline);
# "∧R" = low-water-mark, flipped when any later request in the turn reads
# fewer cached tokens than the first (signals mid-turn cache degradation).
if [ "$nreq" -gt 0 ]; then
  r_label="1R"
  [ "$fr_dip" = "1" ] && r_label="∧R"
  echo "${user}@${host} $(fmt_path "$cwd") | ctx $(fmt "$used_tokens")${ttl_tag} | ${r_label} $(fmt "$fr") - Rq ${nreq} - ΣW $(fmt "$tw") - $(fmt_cost "$turn_cost")"
else
  echo "${user}@${host} $(fmt_path "$cwd") | ctx $(fmt "$used_tokens")"
fi