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の下部に表示されるはずです!

仕組み

最後に簡単に仕組みを記載します。

  1. チャットを送信するとイベントが発火してextensions内のextension.mjsが実行される
  2. extension.mjsがusage情報を取得して、.copilot配下にpremium-quota.jsonを書き出す
  3. statusline.shpremium-quota.jsonを読み込み、statuslineに表示

おまけ

この仕組みを利用して、statuslineでペットを飼ってみました。
今回は試しにcopilotくん?を表示。消費が激しいと赤くなります。

まとめ

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

結構簡単にできるので、ぜひカスタマイズしてみてください!

参考

https://qiita.com/inoue_d/items/1633bac01aaa1c9e1cdf#1-extension
https://github.com/github/copilot-cli/issues/1311
https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference

Discussion