GitHub Copilotの使いすぎを防ぐ、消費ペースガイド付きstatuslineを作ってみた
月末になると、プレミアムリクエストが足りなくなることありませんか!?
自分は月半ばで全部消費してしまったり、逆に節約しすぎて、月末に慌ててOpusで一気に消化したりしてました...
なので、プレミアムリクエストの消費ペースガイドを statusline に表示して、計画的にプレミアムリクエスト消費するためのカスタムステータスラインを作ってみました!
※本記事はGitHub Copilot CLI(v1.0.41)のお話です。
こんな感じで表示
いいペースで消費している時

ちょっと消費ペースが早い時

消費ペースが早すぎる時

| 表示 | 内容 |
|---|---|
36/300 (12.0%) |
現在の消費量(消費数とパーセンテージ) |
26d left |
次回リセットまでの残り日数 |
pace guide:10 req/day |
残数から逆算した1日あたり安全に使える数 |
CHILL / CAUTION / DANGER |
消費ペースのステータス |
- 色で消費ペースが早すぎないか分かるようにしてみました。
- ペースガイドがついているので、1日どれくらいずつ使えば月末まで持つかの目安が簡単に分かります。
そもそも /statusline とは?
GitHub Copilot CLI には /statusline というコマンドがあります。
CLI の入力欄周辺に表示する情報をカスタマイズできます。
GitHub Copilot CLI v1.0.41 時点では、以下の項目を ON/OFF 可能です。
❯ [✓] directory Show the current working directory in the status line
[✓] branch Show the current git branch in the status line
[✓] effort Show the current model effort when supported
[✓] context-used Percentage of context window used
[✓] quota Remaining usage on daily limit
[✓] agent Show the active custom agent name
[ ] changes Show added/removed line counts for the session
[ ] custom Show your custom configured statusLine.command
Preview はこんな感じ
~/workspace/demo-project [⎇ feature/footer-preview] Remaining reqs.: 24%
──────────────────────────────────────────────────────────────
❯ Summarize the footer preview changes
──────────────────────────────────────────────────────────────
@ files · # issues Agent · GPT-5.4 · medium(42%)
quota をONにすれば、現状でも残りの使用量は分かるようになっていますが、個人的にはプレミアムリクエスト数自体を教えて欲しいなあと思っていました。
今回はこの custom を利用して、オリジナル statusline を追加していきます。
やり方
追加・編集するファイルはこの3つです。
~/.copilot/
├── settings.json
├── statusline.sh
└── extensions/
└── premium-quota/
└── extension.mjs
1. ファイルを配置する
2つのファイルのコードをコピーして下記場所に配置してください。
~/.copilot/statusline.sh
~/.copilot/extensions/premium-quota/extension.mjs
statusline.sh
#!/bin/bash
python3 - <<'STATUSLINE_PY'
import json
from datetime import date, datetime, time
from pathlib import Path
ANSI_RESET = "\033[0m"
ANSI_DIM = "\033[90m"
ANSI_GREEN = "\033[32m"
ANSI_YELLOW = "\033[33m"
ANSI_RED = "\033[31m"
ANSI_BLACK = "\033[30m"
ANSI_WHITE = "\033[97m"
BG_GREEN = "\033[42m"
BG_YELLOW = "\033[43m"
BG_RED = "\033[41m"
BAR_WIDTH = 12
PERCENT_FULL = 100
WARNING_PACE_RATIO = 1.4
DEFAULT_QUOTA_FILE = Path.home() / ".copilot" / "premium-quota.json"
# quota情報を読み込み
def load_quota():
try:
with open(DEFAULT_QUOTA_FILE, "r") as quota_file:
return json.load(quota_file)
except (OSError, json.JSONDecodeError):
return None
# リセット日を基準に現在の請求サイクルを計算
def get_billing_cycle(reset_date):
cycle_end_date = reset_date
if cycle_end_date.month == 1:
cycle_start_date = cycle_end_date.replace(year=cycle_end_date.year - 1, month=12)
else:
cycle_start_date = cycle_end_date.replace(month=cycle_end_date.month - 1)
cycle_start_at = datetime.combine(cycle_start_date, time.min)
cycle_end_at = datetime.combine(cycle_end_date, time.min)
now = datetime.now()
today = date.today()
return {
"total_seconds": (cycle_end_at - cycle_start_at).total_seconds(),
"elapsed_seconds": (now - cycle_start_at).total_seconds(),
"days_remaining": (cycle_end_date - today).days,
}
# 現在の利用ペースを計算
def calculate_pacing(remaining_percentage, monthly_limit, total_used, cycle):
actual_fraction = min((PERCENT_FULL - remaining_percentage) / PERCENT_FULL, 1.0)
ideal_fraction = 1.0
if cycle["total_seconds"] > 0:
ideal_fraction = cycle["elapsed_seconds"] / cycle["total_seconds"]
ideal_fraction = min(max(ideal_fraction, 0.0), 1.0)
pace_ratio = (
actual_fraction / ideal_fraction
if ideal_fraction > 0
else (0 if actual_fraction == 0 else 2.0)
)
daily_budget = (
(monthly_limit - total_used) / cycle["days_remaining"]
if cycle["days_remaining"] > 0
else 0
)
return actual_fraction, pace_ratio, daily_budget
# quota情報から最終的なステータスライン文字列を作成
def render_statusline(quota):
total_used = quota.get("usedRequests", 0)
monthly_limit = quota.get("entitlementRequests")
if quota.get("isUnlimitedEntitlement", False) or not monthly_limit:
return f"{ANSI_GREEN}⚡ {total_used} reqs (unlimited){ANSI_RESET}"
reset_date_text = quota.get("resetDate")
if not reset_date_text:
return None
try:
reset_date = date.fromisoformat(reset_date_text[:10])
except ValueError:
return None
cycle = get_billing_cycle(reset_date)
remaining_percentage = quota.get("remainingPercentage", 0)
actual_fraction, pace_ratio, daily_budget = calculate_pacing(
remaining_percentage,
monthly_limit,
total_used,
cycle,
)
used_percentage = actual_fraction * PERCENT_FULL
filled = min(int(actual_fraction * BAR_WIDTH), BAR_WIDTH)
usage_bar = "█" * filled + "░" * (BAR_WIDTH - filled)
if pace_ratio <= 1.0:
color = ANSI_GREEN
pace_badge = f"{BG_GREEN}{ANSI_BLACK} CHILL {ANSI_RESET}{color}"
elif pace_ratio <= WARNING_PACE_RATIO:
color = ANSI_YELLOW
pace_badge = f"{BG_YELLOW}{ANSI_BLACK} CAUTION {ANSI_RESET}{color}"
else:
color = ANSI_RED
pace_badge = f"{BG_RED}{ANSI_WHITE} DANGER {ANSI_RESET}{color}"
return (
f"{color}⚡ {usage_bar} "
f"{total_used}/{monthly_limit} ({used_percentage:.1f}%) │ "
f"{cycle['days_remaining']}d left │ pace guide:{daily_budget:.0f} req/day │ {pace_badge}"
f"{ANSI_RESET}"
)
def main():
quota = load_quota()
if quota is None:
print(f"{ANSI_DIM}⚡ --/-- │ waiting for quota data{ANSI_RESET}")
return
statusline = render_statusline(quota)
if statusline is None:
print(f"{ANSI_DIM}⚡ --/-- │ waiting for quota data{ANSI_RESET}")
return
print(statusline)
if __name__ == "__main__":
main()
STATUSLINE_PY
extension.mjs
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { homedir } from "node:os";
import { joinSession } from "@github/copilot-sdk/extension";
const session = await joinSession({});
const outputPath = join(homedir(), ".copilot", "premium-quota.json");
session.on("assistant.usage", async (event) => {
const snapshots = event.data?.quotaSnapshots;
if (!snapshots) return;
const premium = snapshots["premium_interactions"];
if (!premium) return;
await writeFile(
outputPath,
JSON.stringify({
entitlementRequests: premium.entitlementRequests,
usedRequests: premium.usedRequests,
remainingPercentage: premium.remainingPercentage,
overage: premium.overage,
isUnlimitedEntitlement: premium.isUnlimitedEntitlement,
resetDate: premium.resetDate,
updatedAt: new Date().toISOString(),
}),
"utf-8",
);
});
2. statusline.sh に実行権限を付与する
statusline.sh は Copilot CLIを実行しているユーザの実行権限が必要です。
適切なユーザに実行権限を付与してください。例えばオーナーであれば、
chmod u+x ~/.copilot/statusline.sh
3. settings.json を編集する
~/.copilot/settings.json に以下を追加します。
{
"experimental": true,
"statusLine": {
"type": "command",
"command": "~/.copilot/statusline.sh",
"padding": 0
}
}
experimental(実験的機能)をONにしないと、extension自体が機能しないので、必ず記載してください。
statusLineの設定項目
| 項目 | 説明 |
|---|---|
type |
現状は "command" 固定 |
command |
statusline 描画用のスクリプト |
padding |
statusline の左側に表示する余白(省略可) |
4. Extension が読み込まれているか確認
Copilot CLI を起動してください。
/env を実行して、Extension が読み込まれているか確認します。
Extensions の項目に下記のように自分で設定したExtensionsが表示されていればOK!
Extensions (1)
┌─────────────┬───────┬────────┬─────────────────────────────┐
│ Extension │ Source│ Status │ Path │
├─────────────┼───────┼────────┼─────────────────────────────┤
│ premium-... │ user │ running│ ~/.copilot/extensions/... │
└─────────────┴───────┴────────┴─────────────────────────────┘
表示されない場合は、experimental が false になっていないか、ファイル配置が正しいか、あたりを確認してください。
5. custom statusline を ON にする
Copilot CLI 上で /statusline を実行して custom を ON にします。
/footer でもOKです(statuslineのエイリアスになっているようです)
できたら、/restartで Copilot CLI を再起動します。
再起動後、一度チャットするとstatuslineの下部に表示されるはずです!
仕組み
最後に簡単に仕組みを記載します。
- チャットを送信するとイベントが発火してextensions内の
extension.mjsが実行される -
extension.mjsがusage情報を取得して、.copilot配下にpremium-quota.jsonを書き出す -
statusline.shがpremium-quota.jsonを読み込み、statuslineに表示
おまけ
この仕組みを利用して、statuslineでペットを飼ってみました。
今回は試しにcopilotくん?を表示。消費が激しいと赤くなります。

まとめ
月末にプレミアムリクエスト不足になりがちだったので、どれくらいのペースで使えば良さそうかを見える化して消費ペースを管理しやすくしてみました。
新しい課金体系に移行したら、改良していきたいと思います。
結構簡単にできるので、ぜひカスタマイズしてみてください!
参考
Discussion