PROMARI JOURNAL

Claude Code の足もとに、18分類の計器盤を。ステータスラインを作りました

作成: / 公開: / 内容更新:

執筆:tamito0201 / 掲載・運営:プロマリ

Claude Code の入力欄の下に、レート制限・コンテキスト・料金・作業の密度まで18分類の指標を並べるステータスラインを作りました。数字の出どころ、率を倍率と残り時間へ翻訳する計算、端末に黙って切られない並べ方、点滅の作り方、導入と検証まで、実画面の数字を検算しながら図20点で読み解きます。

PASS IT ON

ひとつの発見を、次の会話へ。

率を、行動が決まる言葉へ翻訳する

2ページ目では、数字をどこから集めるかを見ました。このページでは、集めた数字をどう読みやすい言葉に作り変えているかを見ていきます。ステータスラインの画面には、標準入力にそのまま入っていた値もあれば、いくつかの値を組み合わせて計算した値もあります。後者を、この記事ではKPIと呼びます。

なぜ計算が必要なのか、冒頭の画面の「5h 68%」で考えてみます。5時間の枠の68%を使った、という事実は正確です。けれど、これだけでは、急ぐべきか、ゆっくりでよいかが決まりません。枠が始まって4時間半たったところの68%なら、残り30分で32%も使えるので余裕があります。始まって30分の68%なら、あと4時間半も残っているのに、枠は3分の1しか残っていません。同じ68%でも、いつの68%かで、意味が正反対になるのです。

そこでこの計器盤では、生の率を、次の行動が決まる3種類の言葉へ翻訳しています。1つ目は「時間に対して何倍の速さで使っているか」という倍率。2つ目は「このままだと、いつ尽きるか」という残り時間。3つ目は「このままだと、いくらになるか」という金額です。順に、式と、冒頭の画面の数字での検算を見ていきましょう。

Pace:時間の10%で、枠の68%を使った

1つ目の翻訳はPaceです。考え方は、マラソンのペース配分と同じです。42km のうち4km(1割)を走った地点で、体力を7割使っていたら、そのペースではゴールまでもちません。レート制限の枠も同じで、枠の時間がどれだけ過ぎたか(経過率)と、枠をどれだけ使ったか(使用率)を比べれば、速すぎるかどうかが分かります。Pace は、使用率を経過率で割った倍率です。

図3.1-1 Pace ×6.7:時間の10%で、枠の68%を使った
図3.1-1 Pace ×6.7:時間の10%で、枠の68%を使った。実画面。使用率だけでは「多いのか」が決まりません。時間の進みと比べた倍率にすると、速すぎるかどうかが一目で分かります。図3.1-1 Pace ×6.7:時間の10%で、枠の68%を使った。実画面。使用率だけでは「多いのか」が決まりません。時間の進みと比べた倍率にすると、速すぎるかどうかが一目で分かります。

冒頭の画面で確かめてみます。⚡ Claude の5時間の枠は、使用率68%、リセットまで4時間29分でした。5時間の枠で残りが4時間29分なら、過ぎた時間はおよそ30分で、経過率は 30 ÷ 300 ≒ 10% です。使用率68%をこの経過率で割ると、68 ÷ 10.2 ≒ 6.7。画面の「Pace ×6.7」と一致します。時間の1割で枠の7割近くを使った、つまり、時間の進みの約6.7倍の速さで枠を使っていたわけです。このペースが続けば、枠は始まってから45分ほどで尽きる計算になります。

7日の枠も同じように確かめられます。使用率45%、リセットまで6日7時間。7日(168時間)の枠で残りが151時間なら、過ぎた時間は約17時間で、経過率は 17 ÷ 168 ≒ 10.1% です。45 ÷ 10.1 ≒ 4.5 で、画面の「Pace ×4.5」になります。ちなみに、手で割った値が画面と少しずれることがあるのは、表示の残り時間が分や時間の単位で切り捨てられているからです。スクリプトの中では、秒単位のリセット時刻から計算しています。

Pythonstatusline.py(レート枠のチップと Pace・抜粋)
def window_seg(tag, pct, resets_at, width=5, winsec=None):
    seg = f"{C_GRAY}{tag}{R} {bar(pct, width)} {sev(pct)}{pct:.0f}%{R}"
    if resets_at:
        seg += f" {C_GRAY}🔄 {fmt_reset_short(resets_at)}{R}"
    # ペーシング: 窓の経過割合に対して使用率が先行していたら倍率を出す
    if winsec and resets_at:
        left = resets_at - time.time()
        if 0 < left < winsec:
            elapsed_pct = (winsec - left) / winsec * 100
            if elapsed_pct >= 5 and pct > 0:
                pace = pct / elapsed_pct
                if pace >= 1.05:
                    col = C_BAD if pace >= 1.5 else C_WARN
                    seg += f" {col}Pace ×{pace:.1f}{R}"
    return blinkify(seg) if pct >= WARN_TH else seg

コードの中の条件にも、それぞれ理由があります。Pace が1.05未満のときは表示しません。時間どおりか、それより遅いペースなら、わざわざ知らせる必要がないからです。1.05以上で黄色、1.5以上で赤にしています。そして、経過率が5%に満たないうちは、Pace を出しません。5時間の枠なら最初の15分です。枠が始まった直後は、経過率という分母がとても小さいので、少し使っただけで倍率が何十倍にも跳ね上がってしまうからです。この「分母が小さいうちは出さない」という考え方は、このページの最後でもう一度出てきます。

枯渇予測は、リセットより早いときだけ出す

2つ目の翻訳は、残り時間です。冒頭の画面のいちばん上にあった「📉 Forecast │ 7d 枯渇まで 6h18m (reset前)」が、その例です。Pace が「いまの速さ」を教えてくれるのに対して、この予測は「いまの速さが続いたら、いつ100%に届くか」を教えてくれます。

計算の材料は、使用率の記録です。標準入力から新しい rate_limits が届くたびに、そのときの時刻と5時間枠・7日枠の使用率を記録に積み、直近3時間より古い記録は捨てていきます。予測するときは、記録の最初と最後を比べて、1秒あたり何%ずつ増えたか(傾き)を求め、残りの%をその傾きで割ります。

Pythonstatusline.py(枯渇予測・例外処理を省いた抜粋)
hist = [h for h in hist if now_ts - h["ts"] < 3 * 3600][-200:]
for k, tag in (("five_hour", "5h"), ("seven_day", "7d")):
    pts = [(h["ts"], h[k]) for h in hist if k in h]
    w = rl.get(k) or {}
    cur_pct, rst = w.get("used_percentage"), w.get("resets_at")
    if len(pts) >= 2 and cur_pct is not None and rst:
        span_s = pts[-1][0] - pts[0][0]
        dpct = pts[-1][1] - pts[0][1]
        if span_s >= 600 and dpct > 0:
            t100 = (100 - cur_pct) / (dpct / span_s)
            if now_ts + t100 < rst:
                parts_fcast.append(blinkify(
                    f"{C_BAD}{tag} 枯渇まで {fmt_span(t100)} (reset前){R}"))

冒頭の画面の数字で、逆向きに確かめてみます。7日の枠は45%使っていて、残りは55%です。これが6時間18分(約6.3時間)で尽きるという予測なので、直近3時間の記録では、使用率が1時間あたり 55 ÷ 6.3 ≒ 8.7 ポイントずつ増えていたことになります。朝の4時台に、それだけの速さで枠を使っていたわけです。

図3.2-1 枯渇予測は、リセットより早いときだけ点滅する
図3.2-1 枯渇予測は、リセットより早いときだけ点滅する。現在のしくみ。予測そのものではなく、「リセットより先に尽きる」ときだけを知らせます。尽きない予測は、画面の場所を取りません。図3.2-1 枯渇予測は、リセットより早いときだけ点滅する。現在のしくみ。予測そのものではなく、「リセットより先に尽きる」ときだけを知らせます。尽きない予測は、画面の場所を取りません。

この予測でいちばん大事なのは、最後の if 文です。予測した「尽きる時刻」が、枠のリセットより前のときだけ表示します。7日の枠のリセットは6日7時間後で、予測の6時間18分後よりずっと先でした。だから表示されたのです。反対に、同じ速さで使っていても、リセットが1時間後に迫っていれば、尽きる前に枠が元に戻るので、何も出しません。

尽きない予測を出さないのは、画面の場所がもったいないからだけではありません。毎回「枯渇まで◯時間」が出ていると、目がその行に慣れてしまい、本当に危ないときに見落とします。いつも鳴っている警報は、警報の役目を果たしません。出るのは本当に手を打つべきときだけ、にしておくことで、この行が出たこと自体が合図になります。そのうえで、出たときは点滅させています(点滅の作り方は4ページ目で見ます)。

予測を出すための条件も、コードに書いてあります。記録が2点以上あること、最初の記録から最後の記録まで10分以上あいていること、使用率が増えていること、の3つです。記録が1点しかなければ傾きは求められませんし、数十秒の記録では、たまたま重い処理をした瞬間の傾きを、ずっと続くものとして扱ってしまいます。

同じ考え方で、🧠 コンテキストの行には⏳ ETAを出しています。こちらは、コンテキストの残りのトークン数を、直近の記録から求めた増え方で割ったものです。記録の幅が60秒以上あるときだけ計算し、結果が12時間より先になるときは出しません。12時間後に尽きると言われても、今の行動は何も変わらないからです。冒頭の画面で ETA が出ていなかったのは、セッションを始めたばかりで、記録がまだそろっていなかったためです。

Est $193:このブロックの終わりの見込み

3つ目の翻訳は、金額です。💰 Cost の行の右端の Est は、いまの5時間ブロックの終わりに、料金がいくらになっていそうかの見込みです。材料は、同じ行の Blk(このブロックでここまでに使った金額)、🔥 Burn の行の $/h(1時間あたりの使う速さ)、それに Blk の横に書いてあるブロックの残り時間の3つです。

図3.3-1 Est $193:このブロックの終わりの見込み
図3.3-1 Est $193:このブロックの終わりの見込み。サンプル(計算)。「いまいくら」に「この速さが続けばいくら」を足すと、ブロックの終わりに何が起きるかが金額で見えます。図3.3-1 Est $193:このブロックの終わりの見込み。サンプル(計算)。「いまいくら」に「この速さが続けばいくら」を足すと、ブロックの終わりに何が起きるかが金額で見えます。

式はとても素直です。ここまでの金額に、いまの速さで残り時間を過ごしたときに増える金額を足します。冒頭の画面(金額はサンプル)で検算してみます。Blk が $20.00、Burnが $40.00/h、残りが4時間19分(約4.32時間)でした。$40.00 × 4.32 ≒ $173 が、残りの時間で増える見込みです。これを $20.00 に足すと、$193 になります。画面の「Est $193」と一致しました。

Pythonstatusline.py(ブロックの終わりの見込み・抜粋)
if blk_v is not None and burn_v and blk_left_s:
    est = blk_v + burn_v * blk_left_s / 3600
    parts3.append(C_MONEY + f"Est ${est:,.0f}" + R)

読むときに注意したいのは、この金額の性質です。Blk も $/h も、ccusage が利用記録から計算した推計です。Claude のサブスクリプションで使っている場合、これは実際に請求される金額ではなく、同じ量をAPIで使ったとしたらいくらか、という換算の目安になります。それでも、「このペースは、ふだんのブロックと比べて重いのか軽いのか」を比べる物差しとしては十分に役立ちます。ふだんのブロックの $/h を覚えておけば、今日の使い方が重いのか軽いのかを、見比べるだけで判断できます。

同じ画面の🚀 Perf と📈 KPI の数字も、同じように検算できます。CacheSave 53% は、📦 Cache の Hit 59% に0.9を掛けたものです(59 × 0.9 ≒ 53)。プロンプトキャッシュから読み出した部分の料金は、通常の入力のおよそ10分の1になる、という料金の構造から、「再利用できた割合 × 9割」をおおよその節約率としています。$/Turn 1.2 は、このセッションの料金 $1.20(サンプル)を、やり取りの回数1回で割ったものです。どれも複雑な計算ではありません。すでにある2つの値を割ったり掛けたりして、判断に使える形に変えているだけです。

冒頭の画面の数字を、式で検算した結果(金額はサンプル)
チップ式画面の値での計算表示
Pace(5h)使用率 ÷ 経過率68 ÷ 10.2×6.7
Pace(7d)使用率 ÷ 経過率45 ÷ 10.1×4.5
EstBlk + $/h × ブロックの残り時間20.00 + 40.00 × 4.32$193
CacheSaveHit × 900.59 × 9053%
$/TurnSess ÷ Turns1.20 ÷ 11.2
Forecast(7d)残り% ÷ 直近の傾き55 ÷ 8.7(逆算した傾き)6h18m

キャッシュと並列度を、料金の目で読む

🚀 Perf と📦 Cache の行には、料金に直接は出てこないけれど、料金を左右する数字を並べています。ここでは、そのうち3つの読み方を見ておきます。

1つ目は、📦 Cache の行の🧊 Cold です。プロンプトキャッシュは、一定の時間が過ぎると片付けられます。片付けられたあとで会話を続けると、それまでの会話の前半を、もう一度ふつうの料金で読み込み直すことになります。Cold 136k は、いまキャッシュが切れたら、13万6千トークンを読み込み直す必要があるという意味です。同じ行の「残 59m」は、キャッシュが片付けられるまでの残り時間です。長い会話をしていて、席を外す前にこの2つを見れば、「戻ってきたら、どれくらい読み直しの料金がかかるか」の見当が付きます。

2つ目は、Parallel です。これは、AI の応答を待っていた時間の合計(API の実働時間)を、セッションの経過時間で割った倍率です。1回に1つずつ応答を待っているなら、この値は1を超えません。1を超えていたら、複数の応答を同時に待っていた、つまり、サブエージェントのような並列の処理が動いていた証拠になります。冒頭の画面の ×0.09 は、4分のうち応答を待っていたのは20秒ほど、という意味で、まだ準備の段階だったことが分かります。

3つ目は、Thruput です。これまでに読み込んだトークンの合計を、API の実働時間の秒数で割ったもので、1秒あたりに何トークンを処理したかの目安です。冒頭の画面では、13万6千トークンを約21秒で処理して、6,451 tok/s でした。会話の最初は、読み込むトークンのほとんどがキャッシュから読み出せる大きな前提(ルールやファイルの中身)なので、この値は大きく出やすくなります。どの数字も、単独で良い悪いを決めるものではなく、「いつもと比べてどうか」を見るための物差しです。

ちなみに、📊 Tokens の行には、会話の量が20万トークンを超えたときだけ「🚧 200k超割増」というチップが出ます。モデルによっては、長い入力に割り増しの料金がかかるためで、標準入力の exceeds_200k_tokens という項目を、そのまま表示しています。これも、条件を満たしたときだけ現れる、黙っている計器の1つです。

描画の間隔を、手が動いていたかのセンサーにする

次は、少し変わった測り方の話です。🔥 Burn の行には、Active・Streak という作業時間のチップがあります。Active は、このセッションで実際に手が動いていた時間、Streak は、最後に休んでから続けて作業している時間です。では、「手が動いていた」ことを、どうやって測っているのでしょうか。

答えは、ステータスラインが描画された間隔です。1ページ目で、ステータスラインは会話が動いているあいだだけ、何度も呼ばれると確かめました。裏返せば、描画が細かく続いているあいだは会話が動いていて、描画が長く途切れたら、会話が止まっていたということです。この性質を、そのまま作業のセンサーとして使いました。

図3.5-1 描画の間隔を、手が動いていたかのセンサーにする
図3.5-1 描画の間隔を、手が動いていたかのセンサーにする。現在のしくみ。描画は会話が動いているあいだしか起きません。その性質を逆手に取って、描画の間隔から作業の密度を測ります。図3.5-1 描画の間隔を、手が動いていたかのセンサーにする。現在のしくみ。描画は会話が動いているあいだしか起きません。その性質を逆手に取って、描画の間隔から作業の密度を測ります。
Pythonstatusline.py(Active と Idle の積み上げ・抜粋)
# 稼働/アイドルの積算: 描画間隔が5分を超えたらアイドルとみなす
now_s = time.time()
last_ts = st.get("last_ts")
if last_ts is not None:
    gap = now_s - last_ts
    if gap <= 300:
        st["work_s"] = st.get("work_s", 0) + gap
    else:
        st["idle_s"] = st.get("idle_s", 0) + gap
        st["streak_start"] = now_s
else:
    st["streak_start"] = now_s
st["last_ts"] = now_s

描画のたびに、前回の描画からの間隔(gap)を測ります。5分(300秒)以下なら、その間隔を Active の時間に足します。5分を超えていたら、その間隔は Idle(休み)の時間に足し、Streak の起点をいまに置き直します。この記録は、2ページ目で見たセッションごとのファイルに入っているので、同時に開いている別の会話の時間が混ざることはありません。

この Active と Idle から、📈 KPI の行の Focus(集中していた割合)も計算しています。Active ÷(Active + Idle)です。ただし、これはあくまで近似の測り方です。画面を見ながら5分以上じっくり考えて、何も送らなかった時間は、実際には頭を使っていても Idle に数えられます。反対に、Claude Code が長い処理を自動で進めているあいだは、私が画面を見ていなくても Active に数えられます。測っているのは「人の集中」ではなく「会話の動き」のほうだ、と分かったうえで読む数字です。

冒頭の画面で Active 0m・Streak 0m だったのは、セッションを始めてまだ数分で、記録が積み上がっていなかったからです。1ページ目の表で「Active 0m」を見て不思議に思った方もいるかもしれませんが、分の単位で切り捨てているので、1分に届くまでは0mと表示されます。

分母が小さい比率は、暴れる

このページの最後は、比率の表示で実際に踏んだ失敗です。最初は、📈 KPI の行に Lines/h(1時間あたりに追加した行数)を、いつでも表示していました。追加した行数を Active の時間で割るだけの、単純な比率です。ところが、作業を始めてしばらくたったとき、画面に12,170 L/hという値が出ました。1時間に1万2千行を書くペースです。もちろん、そんなはずはありません。

図3.6-1 分母が小さい比率は、暴れる
図3.6-1 分母が小さい比率は、暴れる。実測。比率は、分母が育つまで表示しません。出さない時間を決めておくことも、数字の設計のうちです。図3.6-1 分母が小さい比率は、暴れる。実測。比率は、分母が育つまで表示しません。出さない時間を決めておくことも、数字の設計のうちです。

原因は分母でした。作業を始めた直後は、Active の時間がまだ数分しかありません。その短い時間に、Claude Code がまとめてファイルを書き出すと、数百行が一気に追加されます。数分で数百行を、1時間あたりに引き延ばすと、数千から1万を超える値になります。計算は正しいのに、分母が小さいせいで、値が実態とかけ離れて暴れていたのです。

直し方は、比率には、分母が育つまで表示しない条件を付けることでした。Lines/h は、Active が15分に満たないうちは表示しません。冒頭の画面に Lines/h が無かったのも、この条件のためです。

Pythonstatusline.py(Lines/h に付けた条件)
if la and work_s and work_s > 900:   # 計測15分未満は分母が小さすぎて暴れる
    parts_kpi.append(C_INFO + f"Lines/h {la / (work_s / 3600):,.0f}" + R)

振り返ると、このページで見た計算には、どれも同じ種類の条件が付いていました。Pace は経過5%以上、枯渇予測は記録の幅10分以上、ETA は60秒以上、Focus は合計60秒以上。割り算をするところには、必ず「分母が小さいうちは出さない」という条件が要る。これは、ステータスラインに限らず、ダッシュボードや集計表で比率を扱うとき、いつでも使える考え方だと思います。

比率のチップと、表示するための条件
チップ分母表示する条件条件が無いと起きること
Pace枠の経過率経過5%以上枠が始まった直後に、何十倍もの倍率が出る
枯渇予測記録の時間の幅2点以上・10分以上・増えている一瞬の重い処理を、ずっと続く速さとして予測する
ETA記録の時間の幅60秒以上・結果が12時間未満意味の無い遠い未来の時刻が出続ける
FocusActive + Idle合計60秒以上数秒の記録で0%か100%になる
Lines/hActive の時間15分以上12,170 L/h のような値が出る(実測)
考えてみる:Pace が1を超えていたら、必ず使いすぎなのだろうか?

必ずしもそうではありません。Pace は、枠の時間のあいだ、同じペースで使い続けることを基準にした倍率です。けれど実際の作業は、集中して一気に進める時間と、会議や休憩で手が止まる時間が交互に来ます。朝に Pace ×6.7 でも、そのあと数時間まったく使わなければ、枠は尽きません。だからこそ、この計器盤では Pace とは別に、「リセットより先に尽きる」ときだけ出る枯渇予測を置いています。Pace は「いまの速さ」、枯渇予測は「このままだと困るか」。2つを並べて読むと、手を打つべきかどうかが判断しやすくなります。

COMMENTS
コメント…

このページ(3ページ目)の感想・質問・設計へのコメント

PASS IT ON

この気づきを、誰かにも。

この記事を書いた人

Takaomi Murasaki

Promari SNS Shareの開発を通して、Webの仕組みや設計の選び方を紹介しています。動く実装と、その判断に至るまでを記事に残しています。

公開記事 10 件

プロフィールを見る
THANK YOU FOR READING.すべての記事へ ↗

FROM INSIGHT TO IMPACT

「できたらいいな」を、
動く仕組みに。

記事で見つけたヒントを、あなたの事業へ。
新しいサービスも、手間のかかる業務も。
いまの課題から、つくるべきものを一緒に考えます。

開発・AI・研修の実績を見る

まだ、仕様書はいりません。

「何から始める?」から、ご一緒に。

課題がまとまっていなくても大丈夫。テーマを選ぶと相談文をご用意します。
連絡先の必須入力は、お名前とメールアドレスだけ。

まずは課題の整理から相談する

ご相談後の流れ

  1. 01 内容を確認
  2. 02 メールでご連絡
  3. 03 課題・進め方をご相談
送信だけで契約やお申込みが確定することはありません。