Tsukutta

📊Claude Codeのコストとプラン枠を常時見える化する

― statusline実装

Lily2026年6月20日公開
約18分で読めます5

前作「Claude Codeを無人で自律改善させる ― autopilot」でautopilotの暴走対策を書いたとき、「プラン枠をstatuslineに出す」部分を一段落で流した。今回はその実装を丸ごと解説する。

認証もエンドポイント呼び出しも不要。Claude Codeがstatuslineスクリプトへstdinで渡すJSONを jq で読むだけで、5h/7dプラン枠・コンテキスト使用率・セッションコストと当日累計(円換算)が3行で常時見える。

困りごと:消費量が「後でビックリ」方式

Claude MaxのAPIにはソフトレート制限がある。5時間ブロックと7日窓で出力トークンが上限に近づくとモデルの挙動が変わり、使い切ると以降のスロットがskipされる。問題はこの消費状況が普段は見えないこと。

  • 「なんかモデルの返答が薄くなった?」→ 実は5h枠が埋まっていた
  • autopilotが裏で食った分も合算されると気づかず手作業セッションを始める
  • コスト感覚が狂い、月末にccusageを見てようやく事態を把握する

「別タブでダッシュボードを開く」運用は三日で廃れる。作業中の画面に常に数字が出ていれば、意識しなくても残量が目に入る。

stdinから直接取れる値

Claude Codeはstatuslineスクリプトを起動するたびにstdinへJSONを流す。そこに必要な値がほぼ揃っている。

text
# ~/.claude/scripts/statusline.sh
INPUT="$(cat 2>/dev/null || echo '{}')"

j() { printf '%s' "$INPUT" | jq -r "$1" 2>/dev/null; }

CTX=$(j '.context_window.used_percentage // empty')
H5=$(j '.rate_limits.five_hour.used_percentage // empty')
H5R=$(j '.rate_limits.five_hour.resets_at // empty')
D7=$(j '.rate_limits.seven_day.used_percentage // empty')
D7R=$(j '.rate_limits.seven_day.resets_at // empty')
SESSION_USD=$(j '.cost.total_cost_usd // 0')

.contextwindow.usedpercentage はClaude Code側が事前計算して渡してくれる値で、自分でトークンを数える必要はない。rate_limits はサブスクリプション契約のユーザーが最初のAPI応答を受け取った後にのみ現れるため、// empty でフォールバックしておかないとセッション開始直後にエラーが出る。

rate_limits が現れるのは「サブスクで最初のAPI応答後にだけ」という仕様がある。開始直後は -- 表示にフォールバックし、最初のターンが返ってきたタイミングで数字が出始める。

rate_limits が現れるのは「サブスクで最初のAPI応答後にだけ」という仕様がある。開始直後は -- 表示にフォールバックし、最初のターンが返ってきたタイミングで数字が出始める。

リセット時刻は epoch 秒で来るので date でローカル時刻に変換する。

text
clk()  { [ -n "$1" ] && date -r "$1" "+%H:%M" 2>/dev/null; }         # → HH:MM
mdhm() { [ -n "$1" ] && date -r "$1" "+%-m/%-d %H:%M" 2>/dev/null; } # → M/D HH:MM

3行レイアウト

出力は固定の3行構成。

text
line1: 📁 dir   ⌥ branch
line2: 🤖 model·effort   🧠 ctx%   🕐 5h N% ⏪HH:MM   📅 7d N% ⏪M/D HH:MM
line3: 💴 session ≈¥x   今日 ≈¥y   <health>

実際のフォーマット呼び出しはこう。

text
L2=$(printf "${MAG}🤖 %s%s${R}  ${DIM}·${R}  ${CTXC}🧠 ctx %s${R}  ${DIM}·${R}  ${C5}🕐 5h %s${R}%b  ${DIM}·${R}  ${C7}📅 7d %s${R}%b" \
  "$MODEL" "$EFF" "$(pct "$CTX")" "$(pct "$H5")" "$R5" "$(pct "$D7")" "$R7")
L3=$(printf "${DIM}💴 session${R} %s   ${DIM}今日${R} %s  %s%b" \
  "$(yen "$SESSION_USD")" "$(yen "$DAY_USD")" "$HEALTH" "$PROJ_HEALTH_SEG")

パーセンテージには色がついており、数字を読まなくても状態がわかる。

text
# pct -> color (green <50, yellow 50-79, red >=80)
pcolor() { local n="${1%%.*}"; [ -z "$n" ] && { printf '%s' "$DIM"; return; }
  if [ "$n" -ge 80 ] 2>/dev/null; then printf '%s' "$RED"
  elif [ "$n" -ge 50 ] 2>/dev/null; then printf '%s' "$YEL"
  else printf '%s' "$GRN"; fi; }

当日コストを2分キャッシュで取る

SESSION_USD はstdinから直でリアルタイムに来るが、「今日のトータルコスト」はccusageのdaily集計が必要になる。毎回呼ぶとstatuslineの描画が詰まるので、2分キャッシュ+バックグラウンド更新にしている。

text
CCUSAGE=$(command -v ccusage 2>/dev/null || echo "$HOME/.nvm/versions/node/v24.13.0/bin/ccusage")
DAY_CACHE="/tmp/cc-daily-cache.json"
NEED=true
if [ -f "$DAY_CACHE" ]; then
  AGE=$(($(date +%s) - $(stat -f %m "$DAY_CACHE" 2>/dev/null || echo 0)))
  [ "$AGE" -lt 120 ] && NEED=false
fi
[ "$NEED" = true ] && ( "$CCUSAGE" daily --json 2>/dev/null > "${DAY_CACHE}.tmp" && mv "${DAY_CACHE}.tmp" "$DAY_CACHE" ) &
DAY_USD=0
if [ -f "$DAY_CACHE" ]; then
  TODAY=$(date "+%Y-%m-%d")
  DAY_USD=$(jq -r --arg d "$TODAY" '(.daily[] | select(.date==$d) | .totalCost) // (.daily[-1].totalCost) // 0' "$DAY_CACHE" 2>/dev/null)
fi

ポイントは & でサブシェルに投げる点。キャッシュが切れていても、statuslineはその瞬間に古い値を返してから裏でccusageを走らせる。2分古い数字でも実運用上の支障はない。

円換算は先頭で設定したレートのawk1行。

text
JPY_RATE=150   # Edit to taste / use $ instead.

yen() { awk -v u="$1" -v r="$JPY_RATE" 'BEGIN{
  v=u*r; n=sprintf("%.0f", v); s=""; len=length(n); c=0
  for(i=len;i>=1;i--){ s=substr(n,i,1) s; c++; if(c%3==0 && i>1) s="," s }
  printf "¥%s", s }'; }

3桁カンマ区切りで ¥1,234 と表示する。ドル表示が好みならyenを呼んでいる箇所をそのまま $SESSION_USD に変えるだけ。

ヘルス1文字(5分キャッシュ)

L3の末尾に🟢/🟡/🔴/⚫の1文字でautomationの死活を出している。automation-health.sh の起動コストが高いため、こちらは5分キャッシュ。

text
HEALTH_CACHE="/tmp/cc-health-cache"; HEALTH=""
if [ -f "$HEALTH_CACHE" ]; then
  HAGE=$(($(date +%s) - $(stat -f %m "$HEALTH_CACHE" 2>/dev/null || echo 0)))
  [ "$HAGE" -lt 300 ] && HEALTH=$(cat "$HEALTH_CACHE" 2>/dev/null)
fi
if [ -z "$HEALTH" ]; then
  ( h=$(~/.claude/scripts/automation-health.sh 2>&1 | grep -oE "ALL GREEN|WARN|FAIL" | head -1)
    case "$h" in
      "ALL GREEN") echo "🟢" > "$HEALTH_CACHE" ;;
      "WARN")      echo "🟡" > "$HEALTH_CACHE" ;;
      "FAIL")      echo "🔴" > "$HEALTH_CACHE" ;;
      *)           echo "⚫" > "$HEALTH_CACHE" ;;
    esac ) &
  HEALTH="·"
fi

キャッシュが切れているときは ·(ドット)を返しつつバックグラウンドで更新を開始する。次の描画サイクルでキャッシュが完成していれば絵文字に切り替わる。statuslineの描画はいかなる場合もブロックしないのが設計の核。

token-budget-advisor.sh:精密なクロス検証

statuslineの表示は「今どのくらいか」を視覚化するためのもの。autopilotが「走るか・止まるか」を判断するには精度が必要で、その役割は token-budget-advisor.sh が担う。

ccusageの公式ブロック集計を優先しつつ、独自の ~/.claude/logs/cost-log.jsonl 集計と両方計算して差分率(sourcediffpct)を出す。

text
# ccusage が居れば5hブロックのoutput tokenを取る(transcript計算より公式)
if command -v ccusage >/dev/null 2>&1; then
  CC_JSON=$(ccusage blocks --json 2>/dev/null || true)
  # ... activeブロックを抽出してoutputTokens/costUSDを取り出す
  CC_OUTPUT_TOK="${EXTRACTED%|*}"
  CC_COST_5H="${EXTRACTED#*|}"
fi

判定しきい値はこう設定されている。

text
THRESH_5H_WARN      = 800_000   # output tokens → warn
THRESH_5H_CRIT      = 1_200_000 # → critical
THRESH_WEEK_WARN    = 3000      # USD / 7d
THRESH_SESS_PER_DAY = 5         # 直近3d平均 > 5 → burst

--short モードで1行出力になり、autopilotがここを見て先行判断する。

text
# autopilot側のコードより(前作で紹介)
BUDGET=$(~/.claude/scripts/token-budget-advisor.sh --short)
if echo "$BUDGET" | grep -qE '🔴|critical|cap-near'; then
  log "ABORT: budget critical"; exit 0
fi
autopilot記事で「ラベル判定だけで残量マイナスが素通りした」事故を書いた。そのため token-budget-advisor.sh はラベルとは別に残量を数値で再計算し、0以下なら止める二重チェックになっている。

cost-summary.sh:期間別集計

text
~/.claude/scripts/cost-summary.sh           # 7日分(デフォルト)
~/.claude/scripts/cost-summary.sh 30        # 30日分
~/.claude/scripts/cost-summary.sh today     # 今日だけ

per day: はUnicode棒グラフで使用量を可視化する。

text
bar = "█" * min(40, int(d["cost"] * 4))
print(f"    {day}: ${d['cost']:5.2f} ({d['n']}s) {bar}")

per model: でモデル別メッセージ数も出る。「Sonnetが多い日は何をしたか」を振り返るのに使っている。

踏んだ落とし穴

  • rate_limits がセッション開始直後に存在しない → // empty でフォールバック。最初のターンが返るまで -- 表示が正常
  • ccusage daily がstatuslineをブロックする → 120秒キャッシュ+& で非同期。古くなっていても描画は止めない
  • ヘルスチェックのたびに重い → 5分キャッシュ+&。更新中は · を返すだけ
  • stat -f %m はmacOS専用 → Linuxでは stat -c %Y に変える。スクリプトはmacOS前提で書いている
  • ccusageと独自集計の乖離に気づかなかった → sourcediffpct を出力して常にクロス検証できるようにした
  • rate_limits がないと勘違いしてエンドポイントを叩こうとした → stdin JSONをdebugログ(/tmp/cc-statusline-last.json)に吐き出す行を最初から入れておくと早く気づける
text
# デバッグ用。消してもよい
printf '%s' "$INPUT" > /tmp/cc-statusline-last.json 2>/dev/null || true

まとめ

  • Claude Codeのstatuslineスクリプトのstdinに .ratelimits.* / .contextwindow.usedpercentage / .cost.totalcost_usd が来る。認証不要
  • 3行で「ブランチ / 5h・7d・ctx% / セッション+今日のコスト円換算+ヘルス」を常時表示
  • 当日コストは2分キャッシュ+バックグラウンド更新でブロックしない
  • ヘルス1文字は5分キャッシュで軽量化
  • token-budget-advisor.sh がccusage公式値と独自集計をクロス検証し、autopilotの走り判断に使う

次は、このstatuslineとautopilotの監視を活かしながら Claude Codeに毎朝GitHubを巡回させてOSSとスキルを自動採点する 話 ―― **Claude Codeに毎朝GitHubを巡回させる ― 有用なOSSとスキルを自動採点**を書いています。

この記事が良かったら

「チップをリクエスト」で著者にチップの受け取り設定をお願いできます

シェア