Skip to content

Feature Request: Add /usage support to display real-time usage graph #94

Closed

Description

@TeddyH

Hi,

Claude Code 2 has recently introduced a /usage command.
This feature allows users to check their current usage and remaining quota in real time, like this:

/usage
→ shows current consumption and remaining balance like following.

` Current session
██████████████████████████████████████████████████ 100% used
Resets 1am (Asia/Seoul)

Current week (all models)
█████████████ 26% used
Resets Oct 7, 8am (Asia/Seoul)
`

It would be great if ccstatusline could integrate this information as well and visualize it in the same graphical format as the other stats.

Expected Behavior:

Parse /usage output from Claude Code 2

Display both current and remaining usage as a graph within ccstatusline

Keep the style consistent with the existing statusline indicators

This would make it much easier to monitor usage at a glance.

Thanks for considering!

Activity

  1. sirmalloc commented on Oct 1, 2025

    @sirmalloc
    Owner

    The output of /usage is not written to the jsonl session transcript, so it's not possible to use it in the statusline. The only possible way to get this output, to my knowledge, is to basically proxy all calls to claude via a local proxy, then look at the response headers:

    "anthropic-ratelimit-unified-5h-reset": "1759348800",
    "anthropic-ratelimit-unified-5h-status": "allowed",
    "anthropic-ratelimit-unified-7d-reset": "1759770000",
    "anthropic-ratelimit-unified-7d-status": "allowed",
    "anthropic-ratelimit-unified-fallback-percentage": "0.5",
    "anthropic-ratelimit-unified-overage-disabled-reason": "org_level_disabled",
    "anthropic-ratelimit-unified-representative-claim": "five_hour",
    "anthropic-ratelimit-unified-reset": "1759348800",
    "anthropic-ratelimit-unified-status": "allowed",

    This is definitely do-able, however it would add more friction to the process as I'd need a background process running indefinitely, and the user would need to be able to launch claude with the ANTHROPIC_BASE_URL pointing to the local proxy every time.

    I'm not ruling this out, it'll just require a disruptive architecture change. I feel like this would be better done as an opt-in if you want those widgets, rather than the default.

  2. self-assigned this
    on Oct 1, 2025
  3. jonaustin commented on Nov 28, 2025

    @jonaustin

    Would it be possible to fetch it from anthropic api?

    ❯ TOKEN=$(security find-generic-password -s "Claude Code-credentials" -w | jq -r '.claudeAiOauth.accessToken')
    
    # Hit the API
    curl -s "https://api.anthropic.com/api/oauth/usage" \
      -H "Authorization: Bearer $TOKEN" \
      -H "anthropic-beta: oauth-2025-04-20"
    {"five_hour":{"utilization":28.0,"resets_at":"2025-11-28T06:00:00.445118+00:00"},"seven_day":{"utilization":3.0,"resets_at":"2025-12-05T01:00:00.445134+00:00"},"seven_day_oauth_apps":{"utilization":0.0,"resets_at":null},"seven_day_opus":null,"seven_day_sonnet":null,"iguana_necktie":null,"extra_usage":{"is_enabled":false,"monthly_limit":null,"used_credits":null,"utilization":null}}
    

    granted this current implementation for fetching the oauth token is macos specific.
    edit: sounds like on linux credentials are stored in ~/.claude/.credentials.json (untested)

    https://codelynx.dev/posts/claude-code-usage-limits-statusline

  4. sirmalloc commented on Nov 28, 2025

    @sirmalloc
    Owner
  5. jonaustin commented on Nov 29, 2025

    @jonaustin

    Technically yes, that will work. However I’m not sure if it’s all good with Anthropic that we start hammering their usage endpoint every time the status line. I’ll do some investigation.

    Thanks; would definitely need caching; probably only needs to be updated every minute at most.

  6. pcvelz commented on Dec 2, 2025

    @pcvelz
    Contributor

    @jonaustin thank you for the inspiration. I exported this script with a healthy 60 seconds caching

    #!/bin/bash
    
    CACHE_FILE="$HOME/.cache/ccstatusline-usage.txt"
    LOCK_FILE="$HOME/.cache/ccstatusline-usage.lock"
    
    # Function to generate progress bar
    make_bar() {
      local pct="$1"
      local width=15
      local filled=$((pct * width / 100))
      local empty=$((width - filled))
      printf "["
      printf "█%.0s" $(seq 1 "$filled")
      printf "░%.0s" $(seq 1 "$empty")
      printf "]"
    }
    
    # Use cache if < 180 seconds old
    if [[ -f "$CACHE_FILE" ]]; then
      AGE=$(($(date +%s) - $(stat -f '%m' "$CACHE_FILE")))
      [[ $AGE -lt 180 ]] && cat "$CACHE_FILE" && exit 0
    fi
    
    # Rate limit: only try API once per 30 seconds
    if [[ -f "$LOCK_FILE" ]]; then
      LOCK_AGE=$(($(date +%s) - $(stat -f '%m' "$LOCK_FILE")))
      if [[ $LOCK_AGE -lt 30 ]]; then
        [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
        echo "[Timeout]" && exit 1
      fi
    fi
    touch "$LOCK_FILE"
    
    TOKEN=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty')
    if [[ -z "$TOKEN" ]]; then
      [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
      echo "[No credentials]"
      exit 1
    fi
    
    RESPONSE=$(curl -s --max-time 5 "https://api.anthropic.com/api/oauth/usage" -H "Authorization: Bearer $TOKEN" -H "anthropic-beta: oauth-2025-04-20" 2>/dev/null)
    
    if [[ -z "$RESPONSE" ]]; then
      [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
      echo "[API Error]"
      exit 1
    fi
    
    SESSION=$(echo "$RESPONSE" | jq -r '.five_hour.utilization // empty' 2>/dev/null)
    WEEKLY=$(echo "$RESPONSE" | jq -r '.seven_day.utilization // empty' 2>/dev/null)
    
    # If API failed, use stale cache or show error
    if [[ -z "$SESSION" || -z "$WEEKLY" ]]; then
      [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
      echo "[Parse Error]"
      exit 1
    fi
    
    SESSION_INT=${SESSION%.*}
    WEEKLY_INT=${WEEKLY%.*}
    
    SESSION_BAR=$(make_bar "$SESSION_INT")
    WEEKLY_BAR=$(make_bar "$WEEKLY_INT")
    
    echo "Session: $SESSION_BAR ${SESSION}% | Weekly: $WEEKLY_BAR ${WEEKLY}%" | tee "$CACHE_FILE"

    Added as a custom command in ccstatusline settings.json:

    {
      "version": 3,
      "lines": [
         [...]
          {
            "id": "ccusage-widget",
            "type": "custom-command",
            "commandPath": "~/.local/bin/ccstatusline-usage.sh"
          },
          {
            "id": "XXXXXX-XXXXXX-XXXX-XXXX-XXXXXX",
            "type": "block-timer"
          }
         [...]
        ]
    }

    End result:

    Image

    (edit: implemented rate limiting in example sh script)

  7. pcvelz commented on Dec 2, 2025

    @pcvelz
    Contributor

    Ok I just created the script above today and noticed a few api limits, I updated the script above with some simple rate limits

  8. tonydehnke commented on Dec 5, 2025

    @tonydehnke

    Awesome, this works great! thank you

  9. jonaustin commented on Dec 22, 2025

    @jonaustin

    Somewhat tangential, but for anyone that uses tmux; i created a little plugin to show usage stats if it's useful to anyone: https://github.com/jonaustin/claude-usage-tmux

  10. vildanbina commented on Jan 2, 2026

    @vildanbina

    @jonaustin thank you for the inspiration. I exported this script with a healthy 60 seconds caching

    for anyone struggling to fetch credentials, especially on Linux, here is the snippet for Linux:

    #!/bin/bash
    
    CACHE_FILE="$HOME/.cache/ccstatusline-usage.txt"
    LOCK_FILE="$HOME/.cache/ccstatusline-usage.lock"
    
    # Function to generate progress bar
    make_bar() {
      local pct="$1"
      local width=15
      local filled=$((pct * width / 100))
      local empty=$((width - filled))
      printf "["
      printf "█%.0s" $(seq 1 "$filled")
      printf "░%.0s" $(seq 1 "$empty")
      printf "]"
    }
    
    # Use cache if < 180 seconds old
    if [[ -f "$CACHE_FILE" ]]; then
      AGE=$(($(date +%s) - $(stat -c '%Y' "$CACHE_FILE")))
      [[ $AGE -lt 180 ]] && cat "$CACHE_FILE" && exit 0
    fi
    
    # Rate limit: only try API once per 30 seconds
    if [[ -f "$LOCK_FILE" ]]; then
      LOCK_AGE=$(($(date +%s) - $(stat -c '%Y' "$LOCK_FILE")))
      if [[ $LOCK_AGE -lt 30 ]]; then
        [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
        echo "[Timeout]" && exit 1
      fi
    fi
    touch "$LOCK_FILE"
    
    TOKEN="$(jq -r '.claudeAiOauth.accessToken // empty' ~/.claude/.credentials.json 2>/dev/null)"
    if [[ -z "$TOKEN" ]]; then
      [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
      echo "[No credentials]"
      exit 1
    fi
    
    RESPONSE=$(curl -s --max-time 5 "https://api.anthropic.com/api/oauth/usage" -H "Authorization: Bearer $TOKEN" -H "anthropic-beta: oauth-2025-04-20" 2>/dev/null)
    
    if [[ -z "$RESPONSE" ]]; then
      [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
      echo "[API Error]"
      exit 1
    fi
    
    SESSION=$(echo "$RESPONSE" | jq -r '.five_hour.utilization // empty' 2>/dev/null)
    WEEKLY=$(echo "$RESPONSE" | jq -r '.seven_day.utilization // empty' 2>/dev/null)
    
    # If API failed, use stale cache or show error
    if [[ -z "$SESSION" || -z "$WEEKLY" ]]; then
      [[ -f "$CACHE_FILE" ]] && cat "$CACHE_FILE" && exit 0
      echo "[Parse Error]"
      exit 1
    fi
    
    SESSION_INT=${SESSION%.*}
    WEEKLY_INT=${WEEKLY%.*}
    
    SESSION_BAR=$(make_bar "$SESSION_INT")
    WEEKLY_BAR=$(make_bar "$WEEKLY_INT")
    
    echo "Session: $SESSION_BAR ${SESSION}% | Weekly: $WEEKLY_BAR ${WEEKLY}%" | tee "$CACHE_FILE"
  11. pcvelz commented on Jan 27, 2026

    @pcvelz
    Contributor

    Since I was sharing the very simplistic SH script I created earlier more frequently I decided to create a fork for ccstatus + usage: https://github.com/pcvelz/ccstatusline-usage

    The widget implementation should work on Linux but perhaps @jonaustin @vildanbina it's worth checking this fork out. * check the uninstall paragraph as well when switching from the upstream to the forked ccstatus plugin

  12. sanjeevpenupala commented on Feb 11, 2026

    @sanjeevpenupala

    @sirmalloc Can this be revisited?

    Seems like someone else was able to integrate usage limits in status line.

  13. sirmalloc commented on Feb 11, 2026

    @sirmalloc
    Owner

    @sirmalloc Can this be revisited?

    Seems like someone else was able to integrate usage limits in status line.

    Yeah, I'll get on it.

  14. added a commit that references this issue on Feb 11, 2026
    e643ee6
  15. pcvelz commented on Feb 11, 2026

    @pcvelz
    Contributor

    @spenpal claude-pulse uses the same API approach — GET /api/oauth/usage with the OAuth token from credentials. Same endpoint, same auth headers.

    I've already implemented this as widgets in a fork: pcvelz/ccstatusline-usage (widget source). Session usage, weekly usage, reset timer, and context bar — all with caching and rate limiting.

    PR submitted: #168

  16. zac15987 commented on Feb 26, 2026

    @zac15987

    for anyone struggling to fetch credentials, especially on Linux, here is the snippet for Linux:

    For anyone using Windows, here’s a working snippet based on @vildanbina and @jonaustin’s solutions:

    import fs from 'fs';
    import os from 'os';
    import path from 'path';
    
    const HOME = os.homedir();
    const CACHE_DIR = path.join(HOME, '.cache');
    const CACHE_FILE = path.join(CACHE_DIR, 'ccstatusline-usage.txt');
    const LOCK_FILE = path.join(CACHE_DIR, 'ccstatusline-usage.lock');
    
    function makeBar(pct) {
      const width = 15;
      const filled = Math.floor(pct * width / 100);
      const empty = width - filled;
      return '[' + '\u2588'.repeat(filled) + '\u2591'.repeat(empty) + ']';
    }
    
    function fileAge(filePath) {
      try {
        const stat = fs.statSync(filePath);
        return (Date.now() - stat.mtimeMs) / 1000;
      } catch {
        return Infinity;
      }
    }
    
    function readCache() {
      try {
        return fs.readFileSync(CACHE_FILE, 'utf8').trim();
      } catch {
        return null;
      }
    }
    
    function outputAndExit(text) {
      process.stdout.write(text);
      process.exit(0);
    }
    
    function fallbackOrError(message) {
      const cached = readCache();
      if (cached) outputAndExit(cached);
      outputAndExit(message);
    }
    
    // Ensure cache directory exists
    fs.mkdirSync(CACHE_DIR, { recursive: true });
    
    // Use cache if < 180 seconds old
    if (fileAge(CACHE_FILE) < 180) {
      const cached = readCache();
      if (cached) outputAndExit(cached);
    }
    
    // Rate limit: only try API once per 30 seconds
    if (fileAge(LOCK_FILE) < 30) {
      fallbackOrError('[Timeout]');
    }
    
    // Touch lock file
    fs.writeFileSync(LOCK_FILE, '');
    
    // Read credentials
    const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR
      ? path.resolve(process.env.CLAUDE_CONFIG_DIR)
      : path.join(HOME, '.claude');
    const credentialsPath = path.join(claudeConfigDir, '.credentials.json');
    
    let token;
    try {
      const creds = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'));
      token = creds?.claudeAiOauth?.accessToken;
    } catch {
      // ignore
    }
    
    if (!token) {
      fallbackOrError('[No credentials]');
    }
    
    // Fetch usage data
    let data;
    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 5000);
    
      const response = await fetch('https://api.anthropic.com/api/oauth/usage', {
        headers: {
          'Authorization': `Bearer ${token}`,
          'anthropic-beta': 'oauth-2025-04-20'
        },
        signal: controller.signal
      });
    
      clearTimeout(timeout);
    
      if (!response.ok) {
        fallbackOrError('[API Error]');
      }
    
      data = await response.json();
    } catch {
      fallbackOrError('[API Error]');
    }
    
    // Parse utilization
    const session = data?.five_hour?.utilization;
    const weekly = data?.seven_day?.utilization;
    
    if (session == null || weekly == null) {
      fallbackOrError('[Parse Error]');
    }
    
    const sessionInt = Math.floor(session);
    const weeklyInt = Math.floor(weekly);
    
    const sessionBar = makeBar(sessionInt);
    const weeklyBar = makeBar(weeklyInt);
    
    const output = `Session: ${sessionBar} ${session}% | Weekly: ${weeklyBar} ${weekly}%`;
    
    // Write cache and output
    fs.writeFileSync(CACHE_FILE, output);
    process.stdout.write(output);

    ccstatusline custom command:

    bun %USERPROFILE%/.local/bin/ccstatusline-usage-win.mjs
    

    result:

    Image
  17. added 2 commits that reference this issue on Feb 26, 2026
    b7dae7c
    07dd5d8
  18. nikhilsitaram commented on Mar 2, 2026

    @nikhilsitaram

    would love to see this supported natively @sirmalloc

  19. pcvelz commented on Mar 2, 2026

    @pcvelz
    Contributor

    @nikhilsitaram I believe the PR is still being reviewed on whether or not it's compatible with Windows -- from what it seems a hard requirement. The fork hasn't been merged yet enabling displaying the 5hr session and weekly limits:

    Session: [████░░░░░░░░░░░] 27.0% | Weekly: [████████████░░░] 86.0% | 1:56 hr | Model: Opus 4.6 ▌▌▌ | Session ID: 0109b99d...
      Context: [███████░░░░░░░░] 103k/200k (51%)
    

    But the changes from this PR but it's already installable from here: https://github.com/pcvelz/ccstatusline-usage

  20. added a commit that references this issue on Mar 4, 2026
    a03b7c2
  21. added a commit that references this issue on Mar 28, 2026
    515ac38
  22. added a commit that references this issue on Oct 20, 2026
    9b900a6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions