1. はじめに
仕事で Claude Code を使っていたある水曜日、「今週の利用枠が75%に達しました」という警告が出ました。
えーーーー、今週の仕事どうするのよーー、と思いました。幸い週の枠は木曜日にリセットされたので、仕事は止まらずに済みました。でも、心臓に悪いです。
利用枠は、Web の設定画面(使用量)や /usage コマンドで見られます。ただ、そこにあるのは「今何%か」と「いつリセットされるか」だけです。「このペースで使っても大丈夫か」を自分で計算するのが面倒で、結局「警告が出てから気づく」ことになっていました。
そこで、Claude Code の新しい拡張の仕組み「mod」を使って、利用枠を VS Code の横にずっと表示しておくようにしました。さらに、「今何%か」だけでなく「このペースで足りるか」の予報も出すようにしました。
この記事では、次の3つの問いに答えます。
- 利用枠(5時間・週)を、作業しながら見られないか
- 「今何%」だけでなく、「このペースで足りるか」を先に知れないか
- そのしくみ自体が、トークン(利用枠)を食わないか
mod は Claude Code 2.1.287 から標準で使えるようになった新しい仕組みで、私も今回初めて触りました。AI(Claude Code)に相談しながら作っています。間違いなどがあれば、コメントで教えていただけるとうれしいです。
2. 先に結論
VS Code の横に、利用枠とペースの予報をずっと出しておけるようになりました。
- 利用枠が見える: 「現在のセッション(5時間)」と「今週」の使用率とリセットの時刻。Web の設定画面(使用量)と同じ数字です
- ペースの予報が出る: 「このペースなら、リセット時に約47%」、足りなくなりそうなら「あと約2.9時間で上限に届きます(リセットより30分早い)」
- トークンは使わない: 表示しているのは Claude Code(プログラム)で、AI ではありません
画面の例では、5時間の枠は36%しか使っていないのに、時間の経過(32%)より速いペースなので「使いすぎのペース」と出ています。週の枠は12%で、このペースならリセット時に約47%なので「余裕あり」です。
3. 環境
| 項目 | 内容 |
|---|---|
| OS | Windows 11 Pro |
| エディタ | VS Code + Claude Code 拡張機能 2.1.319 |
| 契約 | Claude Pro(サブスクリプション) |
| 確認した日 | 2026-11-08 |
- mod は、ターミナル版なら Claude Code 2.1.287 以降、VS Code 拡張なら 2.1.299 以降で画面に描けます(公式ドキュメント)
- 利用枠の数字が取れるのは、Pro や Max などのサブスクリプションで使っているときだけです。API キーで使っている場合は空になります
- Node.js などの開発環境は要りません。mod のコード(TypeScript)は Claude Code がそのまま読み込みます
「VS Code では mod のパネルは表示されない」という解説もありますが、公式ドキュメントでは VS Code 拡張 2.1.299 以降で表示されることになっています。私の環境(2.1.319)でも、実際に表示されました。
4. 利用枠の数字はどこにあるのか
利用枠の数字は、Claude の API から、返事と一緒に Claude Code へ届いています。mod は Claude Code の中で動くので、その数字を直接読めます。
mod は、Claude Code がすでに受け取った数字を読むだけです。
利用枠を見る方法は、ほかにもあります。
| 方法 | どこに出るか | いつ新しくなるか | 向いている人 |
|---|---|---|---|
| Web の設定画面(使用量) | ブラウザ | 開いたとき | たまに確かめればよい人 |
/usage コマンド |
Claude Code の会話 | 打ったとき | その場で確かめたい人。VS Code では「Account & Usage」の画面が開き、使用率とリセットの時刻に加えて、何が枠を使っているかの内訳も出る。ペースの予報はない |
| ステータスライン | 入力欄の下の1行 | 返事のたび | ターミナルで使う人。公開されたツールも多い。VS Code 拡張(2.1.319)では表示されなかった |
| mod(今回) | VS Code の横のパネル | 返事のたび | 予報や通知まで自分で決めたい人 |
ステータスラインで利用枠を出す方法は、すでに解説やツールがたくさんあります(例: ccstatusline の解説)。この記事では、VS Code のパネルに出すことと、ペースの予報を出すことに絞ります。
ほかにも、利用枠を入力欄の上に出す mod(limits-meter)や、ペースを予測するツール(claumon など)があります。この記事の mod は、VS Code のパネルに出せることと、ほかに何も入れず mod だけでペースの予報まで出すことが違います。
トークン(利用枠)は使わない
使いません。mod は Claude Code というプログラムの中で動く部品で、AI には何も送りません。パネルの中身も AI には送られないので、利用枠を見張るために利用枠を使う、ということは起きません。
ただし、数字が新しくなるのは AI が返事をしたときだけです。何もしていない間は止まって見えます。ブラウザのチャットや別の PC で使った分も、次の返事のときに反映されます(数字はアカウント全体のものです)。
5. 作り方
作るものは、mod のファイル4つと、個人設定に足す2行だけです。
(1) ファイルを4つ作る
~/.claude/mods/context-meter/
├── .claude-plugin/
│ └── plugin.json ← mod の名前と説明
├── hooks/
│ ├── hooks.json ← 「コードは register.tsx」という指定
│ └── register.tsx ← 本体
└── types/
└── index.d.ts ← 保存する値の型
中心は register.tsx のこの部分です。AI の返事が終わるたび(turn.complete)に、Claude Code が持っている数字($.session.usage())を読んで保存します。保存すると、パネルが自動で描き直されます。
// AI の返事が1回終わるたびに呼ばれる
on('turn.complete', async ($, e, next) => {
const result = await next(e)
const usage = await $.session.usage()
// usage.rateLimits = [{ kind: 'five_hour', percentUsed: 36, resetsAt: '...' }, { kind: 'seven_day', ... }]
await update($, limits, () => ({ list: usage.rateLimits.map(r => ({ kind: r.kind, percent: r.percentUsed, resetsAt: r.resetsAt })), at: now() }))
return result
})
// 横のパネルを描く(保存した値が変わると描き直される)
on('ui.render', { component: 'Pane', requestId: 'context-meter' }, async ($, e) => {
const { Box, Text } = $.ui.resolve(e)
const lim = await read($, limits)
// ... 使用率・リセット時刻・ペースの予報を並べる
})
4つのファイルの全文は、次の折りたたみに載せます。
.claude-plugin/plugin.json
{
"name": "context-meter",
"version": "0.1.0",
"description": "会話の入れ物(コンテキスト)の埋まり具合を、返事のたびにパネルに棒グラフで出す",
"types": "./types/index.d.ts"
}
hooks/hooks.json
{ "modules": ["./register.tsx"] }
types/index.d.ts
// 1回の返事が終わったときの読み取り値
export type Reading = {
percent: number // 0〜100
tokens: number
window: number
at: string // 表示用の時刻
}
// アカウントの利用枠(設定画面の「使用量」と同じもの)
export type Limit = {
kind: string // five_hour(現在のセッション) / seven_day(今週)
percent: number
resetsAt?: string // ISO 8601
}
export type Limits = { list: Limit[]; at: string }
declare module 'claude-code' {
interface PluginState {
'context-meter': {
readings: Shaped<Reading[]>
limits: Shaped<Limits | null>
}
}
}
hooks/register.tsx
// context-meter: 会話の入れ物(コンテキスト)の埋まり具合を、返事のたびにパネルに出す
//
// しくみ:
// 先生の返事が1回終わるたび(turn.complete)に、Claude Code が持っている「今の会話の大きさ」
// ($.session.usage() の context)を読んで、パネルに棒グラフで描く
// 値を読むだけなので、トークンは使わない
//
// なぜ大事か:
// 会話が長くなると、入れ物が埋まって、古いやり取りが自動で要約(compact)される。
// その前にメモリやメモに残し、/clear で区切ると、話がぶれにくい
//
// VS Code では入力欄の上の帯(AbovePrompt)が描かれないので、横のパネル(Pane)に出す
import { atom, read, update } from 'claude-code'
import type { EngineInterface, Register } from 'claude-code'
import type { Limit, Limits, Reading } from '../types'
const PANE = 'context-meter'
const TITLE = '会話メーター'
const THRESHOLDS = [50, 75, 90]
const KEEP = 30 // 推移として覚えておく回数
const readings = atom({ plugin: 'context-meter', key: 'readings' } as const, [] as Reading[], {
shape: 'readings-1',
})
const limits = atom({ plugin: 'context-meter', key: 'limits' } as const, null as Limits | null, {
shape: 'limits-1',
})
// 利用枠: 設定画面と同じ名前で出す。この割合を超えた瞬間に通知する
const LIMIT_NAMES: Record<string, string> = { five_hour: '現在のセッション(5時間)', seven_day: '今週' }
const LIMIT_ALERTS = [50, 80, 95]
const WEEK = ['日', '月', '火', '水', '木', '金', '土']
function resetText(iso?: string): string {
if (!iso) return ''
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return ''
const p = (n: number) => String(n).padStart(2, '0')
const today = new Date()
const sameDay = d.toDateString() === today.toDateString()
return `${sameDay ? '' : `${d.getMonth() + 1}/${d.getDate()}(${WEEK[d.getDay()]}) `}${p(d.getHours())}:${p(d.getMinutes())}にリセット`
}
function limitColor(percent: number): string {
return percent < 50 ? 'green' : percent < 80 ? 'yellow' : 'red'
}
// ---- ペースの予報 ----
// 枠の長さとリセットの時刻から「枠の始まり」を逆算し、経過の割合と使った割合を比べる
// 例: 週の枠で 4日/7日(57%)経過して 45% 使用 → このペースならリセット時に 45/0.57 ≒ 79%
const WINDOW_MS: Record<string, number> = { five_hour: 5 * 3600_000, seven_day: 7 * 86400_000 }
type Pace = {
elapsedText: string // 経過 4.2日/7日(60%)
status: 'early' | 'ok' | 'tight' | 'over' // 予報はまだ / 余裕あり / ぎりぎり / 使いすぎ
forecast: string // このペースなら、リセット時に約79% など
}
function span(ms: number, kind: string): string {
if (kind === 'seven_day') {
const days = ms / 86400_000
return days >= 1 ? `${days.toFixed(1)}日` : `${Math.round(ms / 3600_000)}時間`
}
const hours = ms / 3600_000
return hours >= 1 ? `${hours.toFixed(1)}時間` : `${Math.round(ms / 60_000)}分`
}
function pace(l: Limit, nowMs: number): Pace | null {
const win = WINDOW_MS[l.kind]
if (!win || !l.resetsAt) return null
const resetMs = new Date(l.resetsAt).getTime()
if (Number.isNaN(resetMs)) return null
const elapsedMs = Math.min(win, Math.max(0, nowMs - (resetMs - win)))
const frac = elapsedMs / win
const elapsedText = `経過 ${span(elapsedMs, l.kind)}/${span(win, l.kind)}(${Math.round(frac * 100)}%)`
if (frac < 0.05) return { elapsedText, status: 'early', forecast: '始まったばかりなので、予報はまだ出せません' }
const projected = l.percent / frac // このペースのまま、リセット時に何%か
if (projected < 100) {
return {
elapsedText,
status: projected < 90 ? 'ok' : 'tight',
forecast: `このペースなら、リセット時に約${Math.round(projected)}%`,
}
}
// 上限に届くまでの残り時間 = 残りの% ÷ 1ミリ秒あたりの使用量
const rate = l.percent / Math.max(1, elapsedMs)
const leftMs = Math.max(0, (100 - l.percent) / rate)
return {
elapsedText,
status: projected < 110 ? 'tight' : 'over',
forecast: `このペースだと、あと約${span(leftMs, l.kind)}で上限に届きます(リセットより${span(resetMs - nowMs - leftMs, l.kind)}早い)`,
}
}
const PACE_LABEL: Record<Pace['status'], { text: string; color: string }> = {
early: { text: '予報はまだ', color: 'gray' },
ok: { text: 'ペースは余裕あり', color: 'green' },
tight: { text: 'ペースはぎりぎり', color: 'yellow' },
over: { text: '使いすぎのペース', color: 'red' },
}
function now(): string {
const d = new Date()
const p = (n: number) => String(n).padStart(2, '0')
return `${p(d.getHours())}:${p(d.getMinutes())}`
}
function k(n: number): string {
return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n)
}
function bar(percent: number, width = 20): string {
const filled = Math.round((percent / 100) * width)
return '█'.repeat(filled) + '░'.repeat(Math.max(0, width - filled))
}
// 推移を1文字ずつの高さで表す(▁▂▃▄▅▆▇█)
function spark(list: Reading[]): string {
const marks = '▁▂▃▄▅▆▇█'
return list.map(r => marks[Math.min(7, Math.floor((r.percent / 100) * 8))]).join('')
}
function advice(percent: number): { text: string; color: string } {
if (percent < 50) return { text: '余裕があります', color: 'green' }
if (percent < 75) return { text: '半分を超えました。区切りのよいところでメモリに残すことを意識', color: 'yellow' }
if (percent < 90) return { text: 'メモに残してから /clear を考える頃です', color: 'red' }
return { text: '自動の要約(compact)が近いかもしれません。先にメモに残してください', color: 'red' }
}
async function record($: EngineInterface): Promise<void> {
const usage = await $.session.usage()
// アカウントの利用枠(先生の返事のたびに、Claude の API が最新の値を返してくる)
if (usage.rateLimits.length > 0) {
const old = await read($, limits)
const list: Limit[] = usage.rateLimits.map(r => ({ kind: r.kind, percent: r.percentUsed, resetsAt: r.resetsAt }))
await update($, limits, () => ({ list, at: now() }))
const nowMs = Date.now()
for (const l of list) {
const oldOne = old?.list.find(o => o.kind === l.kind)
const before = oldOne?.percent ?? 0
for (const t of LIMIT_ALERTS) {
if (before < t && l.percent >= t) {
$.ui.toast(`利用枠: ${LIMIT_NAMES[l.kind] ?? l.kind}が${t}%を超えました(${resetText(l.resetsAt)})`)
}
}
// 「使いすぎのペース」に変わった瞬間だけ知らせる(75%の警告より前に気づけるように)
const p = pace(l, nowMs)
const wasOver = oldOne ? pace(oldOne, nowMs)?.status === 'over' : false
if (p?.status === 'over' && !wasOver) {
$.ui.toast(`利用枠: ${LIMIT_NAMES[l.kind] ?? l.kind}が使いすぎのペースです。${p.forecast}`)
}
}
}
const c = usage.context
if (c.percent === undefined || c.tokens === undefined) return
const reading: Reading = { percent: c.percent, tokens: c.tokens, window: c.window, at: now() }
const before = await read($, readings)
const prev = before.length > 0 ? before[before.length - 1].percent : 0
await update($, readings, list => [...list, reading].slice(-KEEP))
// しきい値を超えた瞬間だけ通知する
for (const t of THRESHOLDS) {
if (prev < t && reading.percent >= t) {
$.ui.toast(`会話メーター: ${t}%を超えました。${advice(reading.percent).text}`)
}
}
}
export const register: Register = on => {
on('session.start', async ($, e, next) => {
const result = await next(e)
await $.command.register({
name: 'context-meter',
description: '会話の埋まり具合をパネルに出す(トークンを使わない)',
})
// 体感してもらうため、セッションの開始時にパネルを開く
void $.ui.open({ id: PANE, title: TITLE })
return result
})
on('turn.complete', async ($, e, next) => {
const result = await next(e)
await record($).catch(() => {})
return result
})
on('command.run', { command: 'context-meter' }, async $ => {
await record($).catch(() => {})
await $.ui.open({ id: PANE, title: TITLE })
const list = await read($, readings)
const last = list[list.length - 1]
return { text: last ? `会話メーター: ${last.percent}%(${k(last.tokens)} / ${k(last.window)})` : '会話メーター: まだ値がありません' }
})
on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
const { Box, Text } = $.ui.resolve(e)
const list = await read($, readings)
const last = list[list.length - 1]
if (!last) {
return (
<Box flexDirection="column">
<Text dimColor>先生の次の返事が終わると、ここに会話の埋まり具合が出ます。</Text>
</Box>
)
}
const prev = list.length >= 2 ? list[list.length - 2] : undefined
const diff = prev ? last.percent - prev.percent : 0
const a = advice(last.percent)
const lim = await read($, limits)
return (
<Box flexDirection="column">
<Text bold>アカウントの利用枠</Text>
{!lim && <Text dimColor>(まだ値がありません)</Text>}
{lim &&
lim.list.map(l => {
const p = pace(l, Date.now())
return (
<Box flexDirection="column">
<Text>
{LIMIT_NAMES[l.kind] ?? l.kind}: <Text bold color={limitColor(l.percent)}>{l.percent}%</Text>{' '}
<Text dimColor>{resetText(l.resetsAt)}</Text>
</Text>
<Text color={limitColor(l.percent)}>{bar(Math.min(100, l.percent))}</Text>
{p && (
<Text>
<Text dimColor>{p.elapsedText} → </Text>
<Text bold color={PACE_LABEL[p.status].color}>{PACE_LABEL[p.status].text}</Text>
</Text>
)}
{p && <Text color={PACE_LABEL[p.status].color}> {p.forecast}</Text>}
</Box>
)
})}
{lim && <Text dimColor>最後の更新: {lim.at}(先生の返事のたびに更新。ほかの会話やチャットの分も含む)</Text>}
<Box marginTop={1}>
<Text bold>この会話の入れ物</Text>
</Box>
<Text bold>
{last.percent}% <Text dimColor>({k(last.tokens)} / {k(last.window)} トークン)</Text>
</Text>
<Text color={a.color}>{bar(last.percent)}</Text>
<Text color={a.color}>{a.text}</Text>
<Box marginTop={1} flexDirection="column">
<Text dimColor>
前の返事から: {diff >= 0 ? `+${diff}` : diff}% ・ 最後の更新: {last.at}
</Text>
<Text dimColor>直近{list.length}回の推移(左が古い):</Text>
<Text>{spark(list)}</Text>
</Box>
<Box marginTop={1} flexDirection="column">
<Text dimColor>目安: 50%で区切りを意識 / 75%でメモに残して /clear / いっぱいに近づくと自動で要約される</Text>
</Box>
</Box>
)
})
}
(2) 個人設定に2行足す
~/.claude/settings.json の env に足します。
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "C:\\Users\\<ユーザー名>\\.claude\\mods\\context-meter",
"CLAUDE_CODE_PLUGIN_DIR_WATCH": "1"
}
-
CLAUDE_CODE_PLUGIN_DIRS: mod のフォルダを読み込む設定です。プロジェクトの設定には書けず、個人の設定だけです -
CLAUDE_CODE_PLUGIN_DIR_WATCH: mod を直したときに自動で読み込み直す設定です(7章)
(3) VS Code を読み直す
Ctrl+Shift+P →「Developer: Reload Window」で読み直して、何か一言送ります。横に「会話メーター」のパネルが開き、AI の返事が終わると数字が出ます。
mod は、自分の権限そのままで動きます(サンドボックスはありません)。人が作った mod は、中身を確かめてから入れましょう。今回作った会話メーターは、利用枠の数字を読んで、パネルを描き、通知を出すだけです。ファイルの読み書きや通信はしません。
6. ペースの予報のしくみ
「時間が何%過ぎたか」と「枠を何%使ったか」を比べるだけです。使った割合の方が大きければ、このままだとリセット前に足りなくなります。
API から届くのは「使用率」と「リセットの時刻」だけなので、枠の始まりはリセットの時刻から逆算します。
\text{経過の割合} = \frac{\text{今} - (\text{リセットの時刻} - \text{枠の長さ})}{\text{枠の長さ}} \qquad \text{リセット時の見込み} = \frac{\text{使用率}}{\text{経過の割合}}
枠の長さは、5時間の枠が5時間、週の枠が7日です。2章の画面の数字で計算すると、次のようになります。
| 枠 | 経過 | 使用率 | リセット時の見込み | 表示 |
|---|---|---|---|---|
| 今週 | 1.8日 / 7日(26%) | 12% | 12 ÷ 0.26 ≒ 46% | ペースは余裕あり |
| 5時間 | 1.6時間 / 5時間(32%) | 36% | 36 ÷ 0.32 ≒ 112% | 使いすぎのペース |
見込みが100%を超えるときは、上限に届くまでの時間も出します。5時間の枠は1.6時間で36%、つまり1時間あたり約22.5%のペースです。残りの64%を使い切るまで約2.8時間で、リセットまでの残り約3.4時間より早く届きます。表とこの計算は経過を丸めているので、分単位で計算している画面の値(約47%、約2.9時間)とは少しずれます。
色は、リセット時の見込みで分けています。
| 見込み | 表示 | 色 |
|---|---|---|
| 90%未満 | ペースは余裕あり | 緑 |
| 90%以上110%未満 | ペースはぎりぎり | 黄 |
| 110%以上 | 使いすぎのペース | 赤 |
- 「使いすぎのペース」へ変わった瞬間、通知も出します。75%の警告より前に気づくためです
- 枠が始まったばかり(経過5%未満)のうちは、予報を出しません。最初の数分の使い方で、見込みが大きくぶれるからです
- 予報は「今までと同じペースで使い続けたら」という前提です。休憩したり、重い作業をしたりすると変わります
7. つまずいたところ
どれも、VS Code 拡張で使うときだけの話です。
| 起きたこと | 原因 | どうしたか |
|---|---|---|
| 入力欄の上に帯で出そうとしたが、表示されない | 入力欄の上の帯(AbovePrompt)は、ターミナル版とデスクトップアプリでしか描かれない |
横のパネル(Pane)に出すようにした |
| mod のファイルを直しても、表示が変わらない | VS Code 拡張は、裏で SDK と同じ方式で動いている。この方式では、CLAUDE_CODE_PLUGIN_DIR_WATCH=1 がないとフォルダの変更を見張らない |
個人設定に足した。それまでは、直すたびに VS Code を開き直していた |
| 設定画面と数字が1%ずれる(今週 12% と 11%) | 端数の丸め方の違いと思われる。更新されるタイミングも違う | そのまま。リセットの時刻は一致していた |
なお、会話メーターは Claude Code の画面の中(会話の右側)に表示されます。VS Code の好きな場所に単独で動かせるかは、確認できませんでした。
また、この記事の手順では出ませんが、Claude Code に mod を作ってもらうと、「Turn on the mods Claude writes in this session?(このセッションで Claude が書いた mod を読み込んで動かしますか?)」という英語の確認が出る場合もあります。mod は自分の権限そのままで動くプログラムなので、AI が書いたものを動かす前に、Claude Code が人に確かめる仕組みです。選択肢は3つです。
- How does this work?: しくみの説明を見る(英語)
- Turn them on: 今すぐ読み込んで動かす。そのあと Claude が mod を直しても、自動で読み込み直される。作ってもらった mod をすぐ試したいときはこちら
- Not now: 今は動かさない。ファイルは残っていて、次にそのセッションを開いたときに読み込まれる。中身を先に確かめたいときはこちら
8. まとめ
はじめの3つの問いへの答えです。
- 利用枠を作業しながら見られるか: 見られます。mod で VS Code の横のパネルに出せば、Web の設定画面を開かなくてもよくなります
- 「このペースで足りるか」を先に知れるか: 知れます。経過の割合と使用率を比べれば、リセット時の見込みと、上限に届くまでの時間が出せます
- トークンを食わないか: 食いません。mod は Claude Code の中で動き、AI には何も送りません
Web の設定画面や /usage コマンドでも、今の利用状況は確認できました。ただ、「このペースで使っても大丈夫かな」と計算するのは面倒でした。
今回作った mod のおかげで、利用状況を毎回確かめに行くことも、ペース配分を自分で計算することもなくなりました。便利になったと感じています。

