Claude Codeのstatuslineでコストを日本円表示にする

Claude Codeのstatuslineでコストを日本円表示にする

Claude Codeのstatuslineを改良しました。コストを日本円で表示し、モデル名を色分け、経過時間に時間表記を加えて、より実用的な表示に整えました。
2026.07.07

はじめに

以前、Windows環境でClaude Codeのstatuslineを設定する記事を書きました。

前回はコストを ドル(cost $0.012 で表示していました。しかし普段の金銭感覚は円なので、ドルだとどれくらい使ったのか直感的につかみにくい。そこで今回は コストを日本円で表示するように改良しました。あわせて、前回の記事では触れていなかった モデル名の色分け経過時間の時間(h)表記 も紹介します。

この記事は前回の続き(差分)です。基本のセットアップ(Windows特有の stdin/stdoutreconfiguresettings.json の設定など)は前回記事を参照してください。

注意: 本記事はWindows 11・PowerShell 7を想定しています。


完成イメージ

 Opus 4.8 │ my-project │ ctx ▓▓▓▓░░░░░░ 42% │ 5h ▓▓░░░░░░░░ 20% │ cost ¥1,327 ($8.234) │ elapsed 1h15m21s

前回からの差分は3点です。

差分 前回 今回
① コスト表示 cost $0.012(ドルのみ) cost ¥1,327 ($8.234)円メイン+ドル併記
② モデル名 色なし モデル種別ごとに色分け
③ 経過時間 3m42s 1h15m21s時間 h 表記を追加)

① コストを日本円で表示する(メイン)

Claude Codeがstatuslineへ渡すJSONには、コストがドル(cost.total_cost_usd)で入っています。これに USD→JPYの為替レート を掛ければ円換算できます。

cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
rate = get_usdjpy()
jpy = cost * rate
parts.append(f'cost ¥{jpy:,.0f} (${cost:.3f})')

円をメインに、ドルも括弧で併記しています(元の値も見たいので)。

為替レートは「1日1回だけ」取得してキャッシュする

問題は為替レートの取得方法です。statuslineは非常に高頻度で実行されるため、毎回ネットワークアクセスすると重くなります。そこで 1日1回だけ取得し、あとはキャッシュを使う 設計にしました。

  • キャッシュファイルに {日付, レート} を保存
  • 当日のキャッシュがあればネットワークアクセスしない(=2回目以降は高速)
  • 日付が変わった初回だけ取得しに行く(=実質「朝1回」)
  • 取得APIは frankfurter.app(ECBレート・APIキー不要
  • 取得に失敗しても、旧レート or 既定値(160円)にフォールバックして表示は壊さない
  • 失敗時も当日日付でキャッシュを書き戻し、同じ日に何度も再取得しない(ハンマリング防止)
def get_usdjpy():
    # USD→JPY 為替。1日1回だけ取得してキャッシュ
    import datetime, urllib.request
    cache_path = os.path.join(os.path.expanduser('~'), '.claude', '.statusline_fx_cache.json')
    today = datetime.date.today().isoformat()
    DEFAULT = 160.0
    cached = {}
    try:
        with open(cache_path, encoding='utf-8') as f:
            cached = json.load(f)
    except Exception:
        cached = {}
    # 当日キャッシュがあればそれを使う(ネットワークアクセスしない)
    if cached.get('date') == today and cached.get('rate'):
        return cached['rate']
    # 当日未取得:フェッチ(失敗時は旧レート or 既定値を維持)
    rate = cached.get('rate') or DEFAULT
    try:
        req = urllib.request.Request(
            'https://api.frankfurter.app/latest?from=USD&to=JPY',
            headers={'User-Agent': 'claude-statusline'})
        with urllib.request.urlopen(req, timeout=2) as resp:
            rate = json.load(resp)['rates']['JPY']
    except Exception:
        pass
    # 当日分として書き戻す(失敗しても当日は再取得しない=ハンマリング防止)
    try:
        os.makedirs(os.path.dirname(cache_path), exist_ok=True)
        with open(cache_path, 'w', encoding='utf-8') as f:
            json.dump({'date': today, 'rate': rate}, f)
    except Exception:
        pass
    return rate

日次キャッシュ+タイムアウト+フォールバックで、遅延と失敗の両方に備えています。


② モデル名を色分けする

前回はモデル名をそのまま表示していましたが、今はモデルを切り替える機会が多いので、モデル種別ごとに色を付けて一目で分かるようにしました。

def model_color(name):
    n = name.lower()
    if 'haiku' in n:   return DIM      # 淡色
    if 'sonnet' in n:  return GREEN    # 緑
    if 'opus' in n:    return RED      # 赤
    if 'fable' in n or 'mythos' in n: return MAGENTA  # マゼンタ
    return R
モデル
Haiku 淡色(DIM)
Sonnet
Opus
Fable / Mythos マゼンタ

display_name に対して部分一致で判定しているので、バージョンが上がっても動きます。


③ 経過時間に時間(h)表記を追加する

前回は elapsed 3m42s のように分・秒だけでした。長時間セッションだと「120m」のように分が膨らんで分かりにくいので、1時間以上のときは h を付けるようにしました。

dur_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
total_s = int(dur_ms // 1000)
hrs = total_s // 3600
mins = (total_s % 3600) // 60
secs = total_s % 60
if hrs > 0:
    parts.append(f'elapsed {hrs}h{mins}m{secs}s')
else:
    parts.append(f'elapsed {mins}m{secs}s')

スクリプト全文

.claude\statusline_custom.py の全文です(前回のスクリプトに①〜③を反映したもの)。

#!/usr/bin/env python3
import json, sys, os

try:
    if sys.platform == 'win32':
        sys.stdin.reconfigure(encoding='utf-8')
        sys.stdout.reconfigure(encoding='utf-8')

    data = json.load(sys.stdin)

    R       = '\033[0m'
    DIM     = '\033[2m'
    GREEN   = '\033[92m'
    YELLOW  = '\033[33m'
    RED     = '\033[31m'
    MAGENTA = '\033[95m'

    def model_color(name):
        n = name.lower()
        if 'haiku' in n:   return DIM
        if 'sonnet' in n:  return GREEN
        if 'opus' in n:    return RED
        if 'fable' in n or 'mythos' in n: return MAGENTA
        return R

    def color(pct):
        if pct <= 30:
            return GREEN
        elif pct <= 60:
            return YELLOW
        else:
            return RED

    def fmt_bar(label, pct):
        p = round(pct)
        filled = p * 10 // 100
        bar = '▓' * filled + '░' * (10 - filled)
        return f'{label} {color(p)}{bar} {p}%{R}'

    def get_usdjpy():
        # USD→JPY 為替。1日1回だけ取得してキャッシュ(朝の初回実行で更新)
        import datetime, urllib.request
        cache_path = os.path.join(os.path.expanduser('~'), '.claude', '.statusline_fx_cache.json')
        today = datetime.date.today().isoformat()
        DEFAULT = 160.0
        cached = {}
        try:
            with open(cache_path, encoding='utf-8') as f:
                cached = json.load(f)
        except Exception:
            cached = {}
        if cached.get('date') == today and cached.get('rate'):
            return cached['rate']
        rate = cached.get('rate') or DEFAULT
        try:
            req = urllib.request.Request(
                'https://api.frankfurter.app/latest?from=USD&to=JPY',
                headers={'User-Agent': 'claude-statusline'})
            with urllib.request.urlopen(req, timeout=2) as resp:
                rate = json.load(resp)['rates']['JPY']
        except Exception:
            pass
        try:
            os.makedirs(os.path.dirname(cache_path), exist_ok=True)
            with open(cache_path, 'w', encoding='utf-8') as f:
                json.dump({'date': today, 'rate': rate}, f)
        except Exception:
            pass
        return rate

    model = data.get('model', {}).get('display_name', 'Claude')
    parts = [f'{model_color(model)}{model}{R}']

    # ディレクトリ名
    cwd = data.get('workspace', {}).get('current_dir', '')
    if cwd:
        parts.append(os.path.basename(cwd))

    # コンテキスト
    pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
    parts.append(fmt_bar('ctx', pct))

    # レート制限(Enterprise以外)
    five = data.get('rate_limits', {}).get('five_hour', {}).get('used_percentage')
    if five is not None:
        parts.append(fmt_bar('5h', five))
    week = data.get('rate_limits', {}).get('seven_day', {}).get('used_percentage')
    if week is not None:
        parts.append(fmt_bar('7d', week))

    # コスト(日本円(ドル)表記。為替は日次キャッシュ)
    cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
    rate = get_usdjpy()
    jpy = cost * rate
    parts.append(f'cost ¥{jpy:,.0f} (${cost:.3f})')

    # 経過時間(h/m/s。1時間以上のときのみhを表示)
    dur_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
    total_s = int(dur_ms // 1000)
    hrs = total_s // 3600
    mins = (total_s % 3600) // 60
    secs = total_s % 60
    if hrs > 0:
        parts.append(f'elapsed {hrs}h{mins}m{secs}s')
    else:
        parts.append(f'elapsed {mins}m{secs}s')

    print(f'{DIM}{R}'.join(f' {p} ' for p in parts), end='')

except Exception:
    sys.exit(0)

まとめ

前回のstatuslineを、普段使いに寄せて3点改良しました。

差分 内容
① コストを円で表示 cost ¥1,327 ($8.234)。為替は日次キャッシュ(frankfurter・APIキー不要・失敗時は既定160円)
② モデル名の色分け Haiku=淡色/Sonnet=緑/Opus=赤/Fable・Mythos=マゼンタ
③ 経過時間の h 表記 1時間以上は 1h15m21s のように時間を表示

特に①は「高頻度実行されるstatuslineでネットワークをどう扱うか」がキモでした。日次キャッシュ+フォールバックにすれば、外部APIに依存しても表示速度と堅牢性を保てます。


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

この記事をシェアする

関連記事