4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeのセッション使用率をターミナルに常時表示するstatusLineスクリプトの作り方

4
Posted at

はじめに

Claude Codeを使っていて「今セッションの使用率はどれくらいだろう」と気になったことはないでしょうか。/configを開いてUsageタブを確認すれば5時間枠と7日枠の使用率がわかりますが、メニューの奥に隠れているため毎回確認するのは面倒です。

Claude CodeにはstatusLineという機能があります。任意のシェルスクリプトをClaude Codeの画面下部に常時表示させる仕組みです。JSONでセッション情報が渡されるので、それをパースして好きな形式で出力するだけ。APIトークンを消費せず、約300msごとにイベント駆動で更新されます。

この記事では、セッション使用率と週間使用率をカラー付きプログレスバーで常時表示するstatusLineスクリプトの実装方法を、WezTermを例に具体的に書いていきます。iTerm2、Alacritty、Kitty、Terminal.appなど、ANSIカラーに対応したターミナルであれば同じスクリプトが動作します。

完成イメージは以下の通りです。

blog [Opus 4.7 (1M context)] (main) session ███░░░░░░░ 32% →8am week ████████░░ 85% →Apr 24 7am
  • セッション(5時間枠)と週間(7日枠)の使用率が、/config → Usageと同じサーバーサイド値で表示される
  • リセット時刻がローカルタイムゾーンで表示される(当日なら「→8am」、別日なら「→Apr 24 7am」)
  • 60%未満は緑、85%未満は黄、85%以上は赤でカラーコード
  • WezTermなどのTruecolorターミナルは24bitパレット、それ以外は256色にフォールバック

この記事はmacOS環境を前提としています。スクリプト内で/usr/bin/python3(macOS標準搭載)を使用しているため、Linuxの場合はパスの読み替えが必要です。Windowsユーザーは、公式ドキュメントにPowerShellおよびGit Bashでの設定例が記載されています。

  • macOS Sequoia 26.4(Apple Silicon / arm64)
  • WezTerm(Truecolor対応ターミナル)
  • Claude Code v2.1.x(Pro/Maxサブスクリプション)
  • Python 3.9.6(macOS標準の/usr/bin/python3

すぐに使いたい場合:/statuslineコマンド

スクリプトを手動で書かなくても、Claude Codeの/statuslineコマンドを使えば自然言語で指示するだけでステータスバーが設定できます。

/statusline show model name and context percentage with a progress bar

Claude Codeが~/.claude/にスクリプトを生成し、settings.jsonも自動で更新してくれます。これだけで動きます。

細かいカスタマイズや動作原理を理解したい場合は、以下の手動セットアップに進んでください。

statusLineの仕組み

statusLinetype"command"に設定すると、Claude Codeは指定されたシェルコマンドを数秒ごとに起動し、stdinにJSON payloadをパイプします。コマンドがstdoutに書いた内容がそのままステータスバーになります。ANSIエスケープコードが使えるので、カラーやスタイリングも自由です。

更新タイミングは、アシスタントメッセージの後、パーミッションモードの変更時、vimモードの切り替え時。300msのデバウンスが入るため、高速な連続更新はバッチされます。

ステータスラインはローカルで実行され、APIトークンを一切消費しません。

JSONペイロードの構造

statusLineスクリプトがstdinで受け取るJSONの主要フィールドです。

{
  "cwd": "/Users/you/project",
  "workspace": {
    "current_dir": "/Users/you/project",
    "project_dir": "/Users/you/project"
  },
  "model": {
    "id": "claude-opus-4-7",
    "display_name": "Opus 4.7 (1M context)"
  },
  "context_window": {
    "used_percentage": 12,
    "remaining_percentage": 88,
    "context_window_size": 200000
  },
  "rate_limits": {
    "five_hour": {
      "used_percentage": 32,
      "resets_at": 1776812400
    },
    "seven_day": {
      "used_percentage": 85,
      "resets_at": 1776981600
    }
  },
  "cost": {
    "total_cost_usd": 0.55,
    "total_duration_ms": 379700
  }
}

rate_limitsについて2つの注意点があります。

1つ目は、Pro/Maxサブスクリプションの場合のみ表示されること。APIキー認証ではこのフィールドは送信されません。

2つ目は、セッション内の最初のAPIラウンドトリップ後に初めてデータが入ること。新しいセッションを開始した直後はまだ空です。1メッセージ送れば表示されます。

スクリプトの実装

~/.claude/statusline-command.shとして保存します。jqは使わず、macOSに標準搭載されている/usr/bin/python3でJSONをパースします。Homebrewもpyenvも不要で、実行は約50msです。

#!/bin/sh
# Claude Code status line — session/week usage with colored progress bars.
# Dumps raw payload to /tmp/claude-statusline-last.json for debugging.
# To disable the dump: export CLAUDE_STATUSLINE_NO_DUMP=1

CLAUDE_STATUSLINE_INPUT=$(cat)
export CLAUDE_STATUSLINE_INPUT

if [ -z "$CLAUDE_STATUSLINE_NO_DUMP" ]; then
  printf '%s\n' "$CLAUDE_STATUSLINE_INPUT" > /tmp/claude-statusline-last.json 2>/dev/null
fi

/usr/bin/python3 <<'PYEOF'
import sys, os, json, subprocess
from datetime import datetime

try:
    d = json.loads(os.environ.get("CLAUDE_STATUSLINE_INPUT", "") or "{}")
except Exception:
    d = {}

ws    = d.get("workspace") or {}
cwd   = ws.get("current_dir") or d.get("cwd") or ""
model = (d.get("model") or {}).get("display_name") or ""
rl    = d.get("rate_limits") or {}

def first(obj, *keys):
    for k in keys:
        v = obj.get(k) if isinstance(obj, dict) else None
        if v not in (None, ""):
            return v
    return None

session_block = first(rl, "session", "five_hour", "current_session") or {}
week_block    = first(rl, "week",    "seven_day", "current_week")    or {}

session_pct   = first(session_block, "used_percentage", "percentage", "used")
session_reset = first(session_block, "resets_at", "reset_at", "resets", "reset")
week_pct      = first(week_block,    "used_percentage", "percentage", "used")
week_reset    = first(week_block,    "resets_at", "reset_at", "resets", "reset")

branch = ""
if cwd and os.path.isdir(cwd):
    try:
        branch = subprocess.check_output(
            ["git", "-C", cwd, "branch", "--show-current"],
            stderr=subprocess.DEVNULL, timeout=0.2,
        ).decode().strip()
    except Exception:
        branch = ""

is_wez = os.environ.get("TERM_PROGRAM") == "WezTerm"
RESET = "\033[0m"
DIM   = "\033[2m"
DIR_C = "\033[38;2;137;180;250m" if is_wez else "\033[36m"
MOD_C = "\033[38;2;166;227;161m" if is_wez else "\033[32m"

def color_for(pct):
    if pct < 60:   return "\033[32m"   # green
    elif pct < 85: return "\033[33m"   # yellow
    else:          return "\033[31m"   # red

def bar(pct, width=10):
    if pct is None:
        return None
    try:
        pct = float(pct)
    except Exception:
        return None
    pct = max(0.0, min(100.0, pct))
    filled = int(round(pct * width / 100.0))
    return f"{color_for(pct)}{'█'*filled}{'░'*(width-filled)}{RESET} {int(round(pct)):>3}%"

def fmt_reset(ts):
    if ts in (None, ""):
        return ""
    try:
        epoch = float(ts)
        if epoch > 1e12:
            epoch /= 1000.0
        dt = datetime.fromtimestamp(epoch)
    except Exception:
        return ""
    now = datetime.now()
    hour = dt.strftime("%-I%p").lower()
    if dt.date() == now.date():
        return f"→{hour}"
    return f"→{dt.strftime('%b %-d')} {hour}"

parts = []
if cwd:    parts.append(f"{DIR_C}{os.path.basename(cwd)}{RESET}")
if model:  parts.append(f"{MOD_C}[{model}]{RESET}")
if branch: parts.append(f"{DIM}({branch}){RESET}")

def add(label, pct, reset_ts):
    b = bar(pct)
    if b is None:
        return
    tail = fmt_reset(reset_ts)
    suffix = f" {DIM}{tail}{RESET}" if tail else ""
    parts.append(f"{DIM}{label}{RESET} {b}{suffix}")

add("session", session_pct, session_reset)
add("week",    week_pct,    week_reset)

sys.stdout.write(" ".join(parts))
PYEOF

スクリプトの構造を解説します。

first()関数は、JSONのフィールド名がClaude Codeのバージョンによって異なる可能性に対応するヘルパーです。five_hoursessionseven_dayweekのどちらの名前でも動作します。

bar()関数は、パーセンテージを(使用済み)と(空き)の10文字のバーに変換します。60%未満は緑、85%未満は黄、85%以上は赤。

fmt_reset()関数は、Unixエポック秒をローカルタイムゾーンの読みやすい形式に変換します。当日のリセットなら「→8am」、別日なら「→Apr 24 7am」。ミリ秒エポックにも対応しています。

WezTermの場合は24bitカラー(\033[38;2;R;G;Bm形式)を使い、それ以外のターミナルでは基本的なANSIカラーにフォールバックします。この検出は$TERM_PROGRAM環境変数で行っています。

Claude Codeに接続する

スクリプトを実行可能にします。

chmod +x ~/.claude/statusline-command.sh

~/.claude/settings.jsonstatusLineを追加します。ユーザーレベル(~/.claude/settings.json)に設定すれば、すべてのプロジェクトで共通のステータスバーが表示されます。

{
  "statusLine": {
    "type": "command",
    "command": "sh /Users/YOUR_USERNAME/.claude/statusline-command.sh"
  }
}

2つの注意点があります。

commandには絶対パスを使うこと。~はClaude Codeがコマンドをスポーンするときに展開が保証されていません。

statusLineはユーザーレベルにだけ設定すること。設定はuser < project < localの順でカスケードするため、プロジェクトレベルの.claude/settings.jsonに別のstatusLineを定義すると、そちらが優先されます。

動作確認

Claude Codeを再起動する前に、フェイクのペイロードで動作確認できます。

echo '{"cwd":"/tmp","workspace":{"current_dir":"/tmp"},"model":{"display_name":"Opus 4.7"},"rate_limits":{"five_hour":{"used_percentage":32,"resets_at":1776812400},"seven_day":{"used_percentage":85,"resets_at":1776981600}}}' \
  | TERM_PROGRAM=WezTerm sh ~/.claude/statusline-command.sh; echo

カラー付きのステータスラインが表示されれば成功です。Claude Codeを再起動し、1メッセージ送信すれば、プログレスバーが画面下部に常時表示されます。

デバッグ

バーが表示されない場合の確認手順です。

スクリプトは実行のたびに/tmp/claude-statusline-last.jsonにペイロードをダンプしています(CLAUDE_STATUSLINE_NO_DUMP=1で無効化可能)。以下のコマンドで内容を確認できます。

cat /tmp/claude-statusline-last.json | python3 -m json.tool

rate_limitsキーが存在しない場合は、APIキー認証を使っているか、まだ1メッセージも送信していないセッションです。rate_limitsは存在するがフィールド名が異なる場合は、first()ヘルパーに新しい名前を追加してください。

ペイロードで使える全フィールド

この記事ではセッション使用率と週間使用率に焦点を当てましたが、JSONペイロードにはさらに多くのフィールドが含まれています。

フィールド 用途
context_window.used_percentage 現在のセッションでモデルのトークンウィンドウがどれだけ使用されているか
context_window.context_window_size 最大コンテキストウィンドウサイズ(200,000 or 1,000,000)
cost.total_cost_usd セッション中のAPI呼び出しの推定USDコスト
cost.total_duration_ms セッションの経過時間
cost.total_lines_added / total_lines_removed 変更されたコード行数
vim.mode vimモード有効時のINSERT/NORMAL
output_style.name 現在の出力スタイル
agent.name アクティブなサブエージェント名
session_id / session_name セッション識別子
worktree.name / worktree.path ワークツリー情報(--worktreeセッション時)

スクリプト末尾のadd(...)呼び出しを追加すれば、これらのフィールドも表示できます。例えば、コンテキストウィンドウの使用率も表示したい場合は以下を追加します。

ctx_pct = (d.get("context_window") or {}).get("used_percentage")
add("ctx", ctx_pct, None)

他のターミナルでの動作

このスクリプトのターミナル依存部分は、24bitカラーの分岐だけです。検出は2つの環境変数で行っています。

  • $COLORTERM == "truecolor": WezTerm、Kitty、Alacritty、ほとんどのモダンターミナルが設定
  • $TERM_PROGRAM: iTerm2とWezTermが設定

これらがない環境では、256色のANSIカラー(\033[36m\033[32m等)にフォールバックします。Terminal.appやtmuxを含め、ANSIを理解するすべてのターミナルで動作します。

tmuxユーザーは、$COLORTERMが伝播しない場合、~/.tmux.confに以下を追加してください。

set -ga terminal-overrides ",*256col*:Tc"
set -g default-terminal "tmux-256color"

おわりに

Claude Codeの使用量管理は、メニューを開いて確認するものではなく、視界の端に常に見えているべきものです。statusLineスクリプトは一度設定すれば、すべてのセッションで自動的に動作し、サーバーサイドと同じ値をリアルタイムに表示し、APIトークンを一切消費しません。

特にWezTermのようなTruecolor対応ターミナルでは、24bitカラーのプログレスバーが視認性を高めます。残量が黄色に変わったらタスクを切り替え、赤になったらセッションを整理する。この習慣だけで、使用量の上限にぶつかる頻度は大幅に減るのではないでしょうか。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?