
Claude Codeのstatuslineでコストを日本円表示にする
はじめに
以前、Windows環境でClaude Codeのstatuslineを設定する記事を書きました。
前回はコストを ドル(cost $0.012) で表示していました。しかし普段の金銭感覚は円なので、ドルだとどれくらい使ったのか直感的につかみにくい。そこで今回は コストを日本円で表示するように改良しました。あわせて、前回の記事では触れていなかった モデル名の色分け と 経過時間の時間(h)表記 も紹介します。
この記事は前回の続き(差分)です。基本のセットアップ(Windows特有の stdin/stdout の reconfigure、settings.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に依存しても表示速度と堅牢性を保てます。







