smithery/tankygranny05

claude-usage-meter

Programmatically fetch Claude Code usage limits and remaining quota via API. Use when user asks about usage, rate limits, quota, billing blocks, or remaining capacity. Also handles OAuth token refresh for API access.

Installation

$ npx skills add smithery/tankygranny05 --skill claude-usage-meter

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/tankygranny05 · top by installs.

npx skills add smithery/tankygranny05

Browse all from smithery/tankygranny05

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 13,591 B
  • docs SUMMARY.md 242 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

<!-- [Edited by Claude: 74b7884b-c678-4264-bf72-363a827187bc 2026-01-24] -->

Claude Usage Meter

Fetch Claude Code usage limits programmatically without the interactive /usage command.

Quick Start

# 1. Refresh token (generates oauth_token_dump.json)
python scripts/refresh_oauth.py

# 2. Convert to .credentials.json
python scripts/convert_oauth_to_credentials.py

# 3. Fetch usage
python scripts/fetch_usage.py

Critical Knowledge (Lessons Learned)

⚠️ IMPORTANT: File Naming Matters

Claude Code reads from .credentials.json (WITH leading dot), NOT oauthtokendump.json.

File Purpose Read by Claude Code?
.credentials.json Active credentials ✅ YES
oauthtokendump.json Raw dump from 2.0.28 ❌ NO (intermediate only)

Common mistake: Copying oauthtokendump.json to remote and expecting it to work. It won't.

⚠️ IMPORTANT: Token Dump Has Extra Fields

The oauthtokendump.json contains extra envelope fields that must be stripped:

// oauth_token_dump.json (RAW - has extra fields)
{
  "event": "oauth_token_dump",
  "ts": "2026-01-24T...",
  "claudeAiOauth": { ... }  // <-- Only this part is needed
}
// .credentials.json (CORRECT format)
{
  "claudeAiOauth": {
    "accessToken": "...",
    "refreshToken": "...",
    "expiresAt": 1737812345678,
    "scopes": ["user:inference", "..."],
    "subscriptionType": "pro",
    "rateLimitTier": "..."
  }
}

⚠️ IMPORTANT: Use -p Flag to Avoid Blocking

When running Claude CLI for token refresh, you MUST use -p flag with a prompt:

# WRONG - blocks forever waiting for input
~/swe/claude-code-2.0.28/cli.js

# CORRECT - exits after responding
~/swe/claude-code-2.0.28/cli.js -p "OAuth refresh ping"

⚠️ IMPORTANT: Always Read Scripts Before Running

The refresh script already has -p built in. Read it first before blindly passing flags.

⚠️ IMPORTANT: Never Log/Print OAuth Tokens

When agents handle OAuth tokens:

  • NEVER read token files and print contents
  • NEVER cat/echo token files
  • Use scripts that process tokens without exposing them
  • Tokens in conversation logs = security risk

⚠️ IMPORTANT: Backup Before Refresh

Always backup existing token before refreshing:

mkdir -p ~/temp/backups
mv ~/.claude/oauth_token_dump.json ~/temp/backups/oauth_token_dump_$(date +%Y%m%d_%H%M%S).json

File Schemas

.credentials.json Schema

{
  "claudeAiOauth": {
    "accessToken": "string (JWT)",
    "refreshToken": "string (JWT)",
    "expiresAt": "number (Unix ms timestamp)",
    "scopes": ["user:inference", "..."],
    "subscriptionType": "pro | free | ...",
    "rateLimitTier": "string | null"
  }
}

oauthtokendump.json Schema (Raw Output)

{
  "event": "oauth_token_dump",
  "ts": "ISO 8601 timestamp",
  "claudeAiOauth": {
    "accessToken": "...",
    "refreshToken": "...",
    "expiresAt": 1737812345678,
    "scopes": [...],
    "subscriptionType": "...",
    "rateLimitTier": "..."
  }
}

Usage API Response Schema

{
  "five_hour": {
    "utilization": 8.0,
    "resets_at": "2026-01-03T09:00:00Z"
  },
  "seven_day": {
    "utilization": 7.0,
    "resets_at": "2026-01-08T08:00:00Z"
  },
  "seven_day_opus": null,
  "seven_day_sonnet": {
    "utilization": 1.0,
    "resets_at": "2026-01-08T19:00:00Z"
  },
  "extra_usage": {
    "is_enabled": false,
    "monthly_limit": null
  }
}

API Details

Endpoint

GET https://api.anthropic.com/api/oauth/usage

Headers

Authorization: Bearer <accessToken>
Content-Type: application/json
User-Agent: claude-code/2.0.76
anthropic-beta: oauth-2025-04-20

Remote Deployment Workflow

To deploy OAuth credentials to a remote machine:

# 1. Backup existing remote token (if any)
ssh remote 'mv ~/.claude/.credentials.json ~/temp/backup_creds_$(date +%Y%m%d).json 2>/dev/null || true'

# 2. Refresh local token
python ~/.claude/skills/claude-usage-meter/scripts/refresh_oauth.py

# 3. Convert to credentials format
python ~/.claude/skills/claude-usage-meter/scripts/convert_oauth_to_credentials.py

# 4. Copy to remote (tokens never seen by agent)
scp ~/.claude/.credentials.json remote:~/.claude/.credentials.json

# 5. Test on remote
ssh remote '~/symlinks/claude -p "Hello, respond: OAuth working"'

For SSH with custom port:

scp -P 22222 ~/.claude/.credentials.json user@host:~/.claude/.credentials.json

Smoke Test Strategy

Test 1: Token Refresh (Local)

# Backup existing
mv ~/.claude/oauth_token_dump.json ~/temp/backup_$(date +%s).json 2>/dev/null || true

# Run refresh
python ~/.claude/skills/claude-usage-meter/scripts/refresh_oauth.py

# Verify output file exists
ls -la ~/.claude/oauth_token_dump.json

# Expected: File exists, created within last minute
# Expected output: "Token expires: <future datetime>"

Test 2: Token Conversion

# Run conversion
python ~/.claude/skills/claude-usage-meter/scripts/convert_oauth_to_credentials.py

# Verify .credentials.json exists with correct permissions
ls -la ~/.claude/.credentials.json
# Expected: -rw------- (600 permissions)

# Verify structure without exposing token
python -c "
import json
with open('$HOME/.claude/.credentials.json') as f:
    d = json.load(f)
assert 'claudeAiOauth' in d, 'Missing claudeAiOauth key'
assert 'accessToken' in d['claudeAiOauth'], 'Missing accessToken'
assert 'refreshToken' in d['claudeAiOauth'], 'Missing refreshToken'
assert 'expiresAt' in d['claudeAiOauth'], 'Missing expiresAt'
print('✅ Structure valid')
"

Test 3: Usage Fetch

python ~/.claude/skills/claude-usage-meter/scripts/fetch_usage.py

# Expected output includes:
# - "Using token from: ..."
# - "5-Hour Block:" with utilization percentage
# - "Raw API Response:" with JSON

Test 4: Claude CLI Works

~/symlinks/claude -p "Respond with exactly: OAuth functional"

# Expected: "OAuth functional"
# If you see "Invalid API key": .credentials.json is missing or malformed

Test 5: Remote Deployment

# Copy credentials
scp ~/.claude/.credentials.json remote:~/.claude/.credentials.json

# Test remote
ssh remote '~/symlinks/claude -p "Respond: Remote OAuth OK"'

# Expected: "Remote OAuth OK"

Test 6: Timeout Behavior

# The refresh script should auto-kill after 20s
# Test by checking process doesn't hang
timeout 30 python ~/.claude/skills/claude-usage-meter/scripts/refresh_oauth.py
echo "Exit code: $?"

# Expected: Completes within 30s, exit code 0

Test 7: Error Handling - Missing Token

# Temporarily move token
mv ~/.claude/oauth_token_dump.json /tmp/

# Should fail gracefully
python ~/.claude/skills/claude-usage-meter/scripts/convert_oauth_to_credentials.py
# Expected: "ERROR: ... not found"

# Restore
mv /tmp/oauth_token_dump.json ~/.claude/

Inline Code Reference

refresh_oauth.py

#!/usr/bin/env python
"""
Refresh OAuth token by running Claude Code 2.0.28 with dump flag.
Auto-kills after 20 seconds maximum.
"""

import os
import subprocess
import sys
import json
from datetime import datetime
from pathlib import Path

CLAUDE_CLI = os.environ.get(
    "CLAUDE_CODE_2028_PATH",
    os.path.expanduser("~/swe/claude-code-2.0.28/cli.js")
)
CONFIG_DIR = os.environ.get(
    "CLAUDE_CONFIG_DIR",
    os.path.expanduser("~/.claude")
)
TIMEOUT_SECONDS = 20

def main():
    cli_path = Path(CLAUDE_CLI)
    config_path = Path(CONFIG_DIR)
    token_file = config_path / "oauth_token_dump.json"

    if not cli_path.exists():
        print(f"ERROR: Claude Code 2.0.28 not found at {cli_path}")
        sys.exit(1)

    print(f"Refreshing OAuth token...")
    print(f"Config dir: {config_path}")
    print(f"Timeout: {TIMEOUT_SECONDS}s")

    env = os.environ.copy()
    env["CLAUDE_CODE_ENABLE_OAUTH_TOKEN_DUMP"] = "1"
    env["CLAUDE_CONFIG_DIR"] = str(config_path)

    proc = subprocess.Popen(
        [str(cli_path), "-p", "OAuth refresh ping"],
        env=env,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True
    )

    try:
        stdout, _ = proc.communicate(timeout=TIMEOUT_SECONDS)
        for line in stdout.strip().split("\n")[:5]:
            print(line)
    except subprocess.TimeoutExpired:
        print(f"Timed out after {TIMEOUT_SECONDS}s, killing...")
        proc.kill()
        proc.wait()

    if token_file.exists():
        with open(token_file) as f:
            data = json.load(f)
        expires_ms = data.get("claudeAiOauth", {}).get("expiresAt")
        if expires_ms:
            print(f"Token expires: {datetime.fromtimestamp(expires_ms / 1000)}")
    else:
        print("WARNING: Token file not created")
        sys.exit(1)

if __name__ == "__main__":
    main()

convertoauthto_credentials.py

#!/usr/bin/env python
"""
Convert oauth_token_dump.json to .credentials.json format.
Does not print or log any sensitive token data.
"""

import json
import os
import sys
from pathlib import Path

DEFAULT_CONFIG_DIR = os.path.expanduser("~/.claude")

def convert_oauth_to_credentials(config_dir=None, output_path=None):
    config_dir = Path(config_dir or DEFAULT_CONFIG_DIR)
    input_file = config_dir / "oauth_token_dump.json"
    output_file = Path(output_path) if output_path else config_dir / ".credentials.json"

    if not input_file.exists():
        print(f"ERROR: {input_file} not found")
        sys.exit(1)

    with open(input_file) as f:
        data = json.load(f)

    if "claudeAiOauth" not in data:
        print(f"ERROR: No 'claudeAiOauth' key in {input_file}")
        sys.exit(1)

    # Extract only claudeAiOauth (strips event, ts fields)
    credentials = {"claudeAiOauth": data["claudeAiOauth"]}

    output_file.parent.mkdir(parents=True, exist_ok=True)
    with open(output_file, "w") as f:
        json.dump(credentials, f, indent=2)

    os.chmod(output_file, 0o600)
    print(f"Created: {output_file}")
    print(f"Permissions: 600")

    expires_ms = data["claudeAiOauth"].get("expiresAt")
    if expires_ms:
        from datetime import datetime
        print(f"Token expires: {datetime.fromtimestamp(expires_ms / 1000)}")

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--config-dir", "-c",
        default=os.environ.get("CLAUDE_CONFIG_DIR", DEFAULT_CONFIG_DIR))
    parser.add_argument("--output", "-o")
    args = parser.parse_args()
    convert_oauth_to_credentials(args.config_dir, args.output)

fetch_usage.py (Core Logic)

#!/usr/bin/env python
"""Fetch Claude Code usage limits via direct API call."""

import json
import sys
from pathlib import Path
import requests  # pip install requests

OAUTH_TOKEN_PATHS = [
    Path.home() / ".claude" / ".credentials.json",
    Path.home() / ".claude" / "oauth_token_dump.json",
]
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"

def load_auth_token():
    for path in OAUTH_TOKEN_PATHS:
        if path.exists():
            with open(path) as f:
                data = json.load(f)
            oauth = data.get("claudeAiOauth", {})
            token = oauth.get("accessToken")
            if token:
                return token, oauth.get("subscriptionType", "unknown")
    print("ERROR: No OAuth token found")
    sys.exit(1)

def fetch_usage(token):
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
        "anthropic-beta": "oauth-2025-04-20"
    }
    resp = requests.get(USAGE_ENDPOINT, headers=headers, timeout=10)
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    token, sub_type = load_auth_token()
    usage = fetch_usage(token)
    print(json.dumps(usage, indent=2))

Troubleshooting

"Invalid API key - Please run /login"

Cause: .credentials.json is missing or malformed.

Fix:

python scripts/refresh_oauth.py
python scripts/convert_oauth_to_credentials.py

Token refresh hangs forever

Cause: Missing -p flag or interactive mode.

Fix: The script handles this with 20s timeout. If still hanging, kill and check CLI path.

"env: node: No such file or directory" (Remote)

Cause: SSH non-interactive shell doesn't load PATH.

Fix:

ssh remote 'source ~/.zshrc; ~/symlinks/claude -p "test"'

401 Authentication Error on Usage API

Cause: Token expired or wrong format.

Fix: Refresh and convert:

python scripts/refresh_oauth.py
python scripts/convert_oauth_to_credentials.py

Token file not created after refresh

Cause: Claude Code version doesn't support CLAUDECODEENABLEOAUTHTOKEN_DUMP.

Fix: Must use Claude Code 2.0.28 specifically. Set CLAUDECODE2028_PATH env var.


Environment Variables

Variable Default Purpose
CLAUDECONFIGDIR ~/.claude Where to read/write credentials
CLAUDECODE2028_PATH ~/swe/claude-code-2.0.28/cli.js Path to Claude 2.0.28 for token dump

Dependencies

  • Python 3.8+
  • requests library (pip install requests)
  • Claude Code 2.0.28 (for token refresh only)