Skip to content

Instantly share code, notes, and snippets.

@Deepti0512
Created June 7, 2026 14:29
Show Gist options
  • Select an option

  • Save Deepti0512/372b8e4e1a0779d3a0cac664f4301614 to your computer and use it in GitHub Desktop.

Select an option

Save Deepti0512/372b8e4e1a0779d3a0cac664f4301614 to your computer and use it in GitHub Desktop.
Claude Code Token Usage Dashboard — one-command setup for your team. Run: curl -fsSL <raw-url> | bash
#!/bin/bash
set -euo pipefail
# ============================================================
# Claude Code Token Usage Dashboard — One-Command Setup
# ============================================================
# Run with: curl -fsSL <your-gist-raw-url> | bash
#
# What this does:
# 1. Checks Claude Code is installed
# 2. Installs the token-optimizer plugin (if not already)
# 3. Generates a token usage dashboard (7-day + 30-day metrics)
# 4. Opens it in your browser
#
# Safe to re-run — idempotent.
# ============================================================
BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${CYAN}[info]${NC} $*"; }
success() { echo -e "${GREEN}[done]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
fail() { echo -e "${RED}[error]${NC} $*"; exit 1; }
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ Claude Code — Token Usage Dashboard Setup ║${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════════════╝${NC}"
echo ""
# -----------------------------------------------------------
# Step 1: Check prerequisites
# -----------------------------------------------------------
info "Checking prerequisites..."
if ! command -v claude &>/dev/null; then
fail "Claude Code CLI not found. Install it first: https://docs.anthropic.com/en/docs/claude-code/overview"
fi
success "Claude Code CLI found"
if ! command -v python3 &>/dev/null; then
fail "Python 3 is required but not found."
fi
success "Python 3 found"
# -----------------------------------------------------------
# Step 2: Install token-optimizer plugin (if needed)
# -----------------------------------------------------------
MEASURE_PY=""
for f in "$HOME/.claude/skills/token-optimizer/scripts/measure.py" \
"$HOME/.claude/plugins/cache"/*/token-optimizer/*/skills/token-optimizer/scripts/measure.py; do
[ -f "$f" ] && MEASURE_PY="$f" && break
done
if [ -z "$MEASURE_PY" ]; then
info "Token Optimizer plugin not found. Installing..."
# Add marketplace if not already added
SETTINGS="$HOME/.claude/settings.json"
MARKETPLACE_URL="https://raw.githubusercontent.com/AuracleTech/token-optimizer/refs/heads/main/marketplace.json"
if [ -f "$SETTINGS" ]; then
if ! grep -q "token-optimizer" "$SETTINGS" 2>/dev/null; then
# Backup settings
cp "$SETTINGS" "$SETTINGS.bak.$(date +%s)"
if command -v jq &>/dev/null; then
jq --arg url "$MARKETPLACE_URL" \
'.extraKnownMarketplaces = ((.extraKnownMarketplaces // []) + [$url] | unique)' \
"$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
else
warn "jq not found — please install token-optimizer manually:"
echo " In Claude Code, run: /install-plugin alexgreensh-token-optimizer"
echo " Then re-run this script."
exit 1
fi
fi
fi
# Try to install via Claude CLI
info "Attempting plugin installation via Claude Code..."
echo "Please run the following in Claude Code to complete plugin setup:"
echo ""
echo -e " ${BOLD}/install-plugin alexgreensh-token-optimizer${NC}"
echo ""
echo "Then re-run this script."
echo ""
# Check again after potential manual install
for f in "$HOME/.claude/plugins/cache"/*/token-optimizer/*/skills/token-optimizer/scripts/measure.py; do
[ -f "$f" ] && MEASURE_PY="$f" && break
done
if [ -z "$MEASURE_PY" ]; then
warn "Plugin not yet installed. Proceeding with basic dashboard (no trends data)..."
NO_PLUGIN=true
fi
else
success "Token Optimizer plugin found"
NO_PLUGIN=false
fi
# -----------------------------------------------------------
# Step 3: Set up SessionEnd hook (for future tracking)
# -----------------------------------------------------------
if [ "$NO_PLUGIN" = false ] && [ -n "$MEASURE_PY" ]; then
HOOK_STATUS=$(python3 "$MEASURE_PY" check-hook 2>/dev/null && echo "installed" || echo "missing")
if [ "$HOOK_STATUS" = "missing" ]; then
info "Setting up session tracking hook..."
python3 "$MEASURE_PY" setup-hook 2>/dev/null && success "Session tracking hook installed" || warn "Could not install hook automatically"
else
success "Session tracking hook already active"
fi
fi
# -----------------------------------------------------------
# Step 4: Generate the dashboard
# -----------------------------------------------------------
info "Generating token usage dashboard..."
OUTPUT="$HOME/.claude/_backups/token-optimizer/token-usage.html"
mkdir -p "$(dirname "$OUTPUT")"
GENERATED=$(date '+%Y-%m-%d %H:%M:%S')
USER_EMAIL=$(git config --global user.email 2>/dev/null || echo "unknown")
if [ "$NO_PLUGIN" = false ] && [ -n "$MEASURE_PY" ]; then
# Full dashboard with trends data
TMPDIR_DATA=$(mktemp -d /tmp/token-usage-XXXXXXXXXX)
python3 "$MEASURE_PY" trends --json --days 7 > "$TMPDIR_DATA/d7.json" 2>/dev/null || echo '{}' > "$TMPDIR_DATA/d7.json"
python3 "$MEASURE_PY" trends --json --days 30 > "$TMPDIR_DATA/d30.json" 2>/dev/null || echo '{}' > "$TMPDIR_DATA/d30.json"
D7_FILE="$TMPDIR_DATA/d7.json"
D30_FILE="$TMPDIR_DATA/d30.json"
else
# Empty data — dashboard will show "no data" state
TMPDIR_DATA=$(mktemp -d /tmp/token-usage-XXXXXXXXXX)
echo '{"period_days":7,"session_count":0,"total_input_tokens":0,"total_output_tokens":0,"total_fresh_input":0,"total_cache_read":0,"total_cache_create":0,"total_tokens":0,"total_tokens_raw":0,"total_messages":0,"avg_duration_minutes":0,"avg_input_tokens":0,"avg_output_tokens":0,"model_mix":{},"tool_calls":{},"daily":[]}' > "$TMPDIR_DATA/d7.json"
echo '{"period_days":30,"session_count":0,"total_input_tokens":0,"total_output_tokens":0,"total_fresh_input":0,"total_cache_read":0,"total_cache_create":0,"total_tokens":0,"total_tokens_raw":0,"total_messages":0,"avg_duration_minutes":0,"avg_input_tokens":0,"avg_output_tokens":0,"model_mix":{},"tool_calls":{},"daily":[]}' > "$TMPDIR_DATA/d30.json"
D7_FILE="$TMPDIR_DATA/d7.json"
D30_FILE="$TMPDIR_DATA/d30.json"
fi
python3 - "$D7_FILE" "$D30_FILE" "$OUTPUT" "$GENERATED" "$USER_EMAIL" <<'PYEOF'
import sys, json
with open(sys.argv[1]) as f:
d7 = json.load(f)
with open(sys.argv[2]) as f:
d30 = json.load(f)
output_path = sys.argv[3]
generated = sys.argv[4]
user_email = sys.argv[5]
no_data = d7.get("session_count", 0) == 0 and d30.get("session_count", 0) == 0
no_data_banner = ""
if no_data:
no_data_banner = """
<div style="background:#2d1b00;border:1px solid #d29922;border-radius:8px;padding:16px;margin-bottom:24px;max-width:1100px;margin-left:auto;margin-right:auto;text-align:center;">
<div style="font-size:16px;font-weight:600;color:#d29922;">No session data yet</div>
<div style="font-size:13px;color:#8b949e;margin-top:8px;">
Use Claude Code normally for a few sessions. Token usage data is collected automatically<br>
via the SessionEnd hook. Re-run this script after a few sessions to see your metrics.
</div>
</div>"""
html = r'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude Code — Token Usage Report</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f1117; color: #e1e4e8; padding: 24px; min-height: 100vh; }
.header { text-align: center; margin-bottom: 32px; }
.header h1 { font-size: 28px; font-weight: 700; color: #fff; margin-bottom: 4px; }
.header .subtitle { color: #8b949e; font-size: 14px; }
.header .generated { color: #6e7681; font-size: 12px; margin-top: 8px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; max-width: 1100px; margin: 0 auto; }
.card { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 24px; }
.card h2 { font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.badge { font-size: 11px; background: #238636; color: #fff; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
.badge.blue { background: #1f6feb; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #21262d; }
th { color: #8b949e; font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; }
td { font-size: 14px; }
td.num { text-align: right; font-variant-numeric: tabular-nums; font-family: 'SF Mono', 'Fira Code', monospace; }
.highlight { color: #58a6ff; font-weight: 600; }
.cost { color: #3fb950; font-weight: 600; }
.full-width { grid-column: 1 / -1; }
.model-bar { display: flex; height: 32px; border-radius: 6px; overflow: hidden; margin-top: 8px; margin-bottom: 12px; }
.seg { display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; color: #fff; }
.seg-opus { background: #8957e5; }
.seg-sonnet { background: #1f6feb; }
.seg-haiku { background: #238636; }
.legend { display: flex; gap: 16px; justify-content: center; margin-top: 8px; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #8b949e; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
.daily-row:nth-child(even) td { background: #1c2128; }
.summary-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 16px; }
.summary-stat { text-align: center; padding: 16px; background: #1c2128; border-radius: 8px; }
.summary-stat .value { font-size: 24px; font-weight: 700; color: #fff; font-variant-numeric: tabular-nums; }
.summary-stat .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 4px; }
.cache-bar { background: #21262d; height: 8px; border-radius: 4px; overflow: hidden; margin-top: 6px; }
.cache-fill { height: 100%; background: linear-gradient(90deg, #238636, #3fb950); border-radius: 4px; }
.refresh-note { text-align: center; color: #6e7681; font-size: 11px; margin-top: 24px; padding-bottom: 24px; }
.refresh-note code { background: #21262d; padding: 2px 6px; border-radius: 4px; font-size: 11px; }
.why-box { max-width: 1100px; margin: 0 auto 24px; background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 20px; }
.why-box h3 { color: #fff; font-size: 15px; margin-bottom: 8px; }
.why-box p { color: #8b949e; font-size: 13px; line-height: 1.6; }
.why-box code { background: #21262d; padding: 1px 5px; border-radius: 3px; font-size: 12px; color: #e1e4e8; }
</style>
</head>
<body>
<div class="header">
<h1>Claude Code Token Usage</h1>
<div class="subtitle">__USER__</div>
<div class="generated">Generated: __GENTIME__</div>
</div>
__NODATA__
<div class="why-box">
<h3>Why this dashboard?</h3>
<p>
The built-in <code>/stats</code> command only shows the <strong>current session's</strong> token count — not historical totals.
This dashboard tracks <strong>all sessions</strong> over 7 and 30 days, including cache efficiency, model mix,
and estimated cost. Run the setup script again anytime to refresh with the latest data.
</p>
</div>
<div class="grid" id="root"></div>
<div class="refresh-note">
Refresh: <code>bash ~/.claude/token-usage-setup.sh</code>
</div>
<script>
var D7 = __D7__;
var D30 = __D30__;
function fmt(n) {
if (n >= 1e9) return (n/1e9).toFixed(1) + 'B';
if (n >= 1e6) return (n/1e6).toFixed(1) + 'M';
if (n >= 1e3) return (n/1e3).toFixed(1) + 'K';
return n.toLocaleString();
}
function fmtFull(n) { return n.toLocaleString(); }
function estCost(d) {
return (d.total_fresh_input||0)*3/1e6 + (d.total_cache_read||0)*0.3/1e6
+ (d.total_cache_create||0)*3.75/1e6 + (d.total_output_tokens||0)*15/1e6;
}
function summaryCard(d, label, badgeCls) {
var cost = estCost(d);
var cacheRate = d.total_input_tokens ? ((d.total_cache_read||0)/d.total_input_tokens*100) : 0;
return '<div class="card">'
+ '<h2>' + label + ' <span class="badge ' + badgeCls + '">' + d.period_days + 'D</span></h2>'
+ '<div class="summary-grid">'
+ '<div class="summary-stat"><div class="value">' + d.session_count + '</div><div class="label">Sessions</div></div>'
+ '<div class="summary-stat"><div class="value">' + fmtFull(d.total_messages||0) + '</div><div class="label">Messages</div></div>'
+ '<div class="summary-stat"><div class="value cost">$' + cost.toFixed(2) + '</div><div class="label">Est. Cost</div></div>'
+ '</div>'
+ '<table><tr><th>Metric</th><th style="text-align:right">Value</th></tr>'
+ '<tr><td>Total Input (raw)</td><td class="num">' + fmt(d.total_input_tokens||0) + '</td></tr>'
+ '<tr><td>Fresh Input</td><td class="num">' + fmt(d.total_fresh_input||0) + '</td></tr>'
+ '<tr><td>Cache Read</td><td class="num">' + fmt(d.total_cache_read||0) + '</td></tr>'
+ '<tr><td>Cache Create</td><td class="num">' + fmt(d.total_cache_create||0) + '</td></tr>'
+ '<tr><td>Total Output</td><td class="num">' + fmt(d.total_output_tokens||0) + '</td></tr>'
+ '<tr><td>Effective (billed)</td><td class="num highlight">' + fmt(d.total_tokens||0) + '</td></tr>'
+ '<tr><td>Avg Duration</td><td class="num">' + Math.round(d.avg_duration_minutes||0) + ' min</td></tr>'
+ '<tr><td>Avg Input/Session</td><td class="num">' + fmt(d.avg_input_tokens||0) + '</td></tr>'
+ '</table>'
+ '<div style="margin-top:16px;font-size:13px;color:#8b949e;">Cache Hit Rate</div>'
+ '<div class="cache-bar"><div class="cache-fill" style="width:' + cacheRate + '%"></div></div>'
+ '<div style="font-size:12px;color:#3fb950;margin-top:4px;">' + cacheRate.toFixed(1) + '% cache hit rate</div>'
+ '</div>';
}
function modelCard(d, label) {
var mix = d.model_mix || {};
var total = Object.values(mix).reduce(function(a,b){return a+b;},0) || 1;
var models = [{k:'opus',c:'seg-opus',l:'Opus'},{k:'sonnet',c:'seg-sonnet',l:'Sonnet'},{k:'haiku',c:'seg-haiku',l:'Haiku'}];
var bars = models.filter(function(m){return mix[m.k];}).map(function(m){
var p = (mix[m.k]/total)*100;
return '<div class="seg ' + m.c + '" style="width:' + p + '%">' + (p>=5 ? Math.round(p)+'%' : '') + '</div>';
}).join('');
var rows = models.map(function(m){
return '<tr><td>' + m.l + '</td><td class="num">' + fmt(mix[m.k]||0) + '</td><td class="num">' + ((mix[m.k]||0)/total*100).toFixed(1) + '%</td></tr>';
}).join('');
return '<div class="card">'
+ '<h2>Model Mix (' + label + ')</h2>'
+ '<div class="model-bar">' + bars + '</div>'
+ '<div class="legend">'
+ '<div class="legend-item"><div class="legend-dot" style="background:#8957e5"></div>Opus</div>'
+ '<div class="legend-item"><div class="legend-dot" style="background:#1f6feb"></div>Sonnet</div>'
+ '<div class="legend-item"><div class="legend-dot" style="background:#238636"></div>Haiku</div>'
+ '</div>'
+ '<table style="margin-top:12px"><tr><th>Model</th><th style="text-align:right">Tokens</th><th style="text-align:right">%</th></tr>' + rows + '</table>'
+ '</div>';
}
function dailyCard(d) {
var days = (d.daily||[]).slice(0,7);
if (days.length === 0) return '';
var rows = days.map(function(day){
var c = (day.total_input||0)*1.5/1e6 + (day.total_output||0)*15/1e6;
return '<tr class="daily-row"><td>' + day.date + '</td><td class="num">' + day.sessions + '</td><td class="num">' + fmt(day.total_input||0) + '</td><td class="num">' + fmt(day.total_output||0) + '</td><td class="num cost">$' + c.toFixed(2) + '</td></tr>';
}).join('');
return '<div class="card full-width">'
+ '<h2>Daily Breakdown (Last 7 Days)</h2>'
+ '<table><tr><th>Date</th><th style="text-align:right">Sessions</th><th style="text-align:right">Input</th><th style="text-align:right">Output</th><th style="text-align:right">Est. Cost</th></tr>' + rows + '</table>'
+ '</div>';
}
function toolsCard(d) {
var tools = Object.entries(d.tool_calls||{}).sort(function(a,b){return b[1]-a[1];}).slice(0,10);
if (tools.length === 0) return '';
var rows = tools.map(function(t){ return '<tr><td>' + t[0] + '</td><td class="num">' + t[1] + '</td></tr>'; }).join('');
return '<div class="card full-width">'
+ '<h2>Top Tool Usage (7 Days)</h2>'
+ '<table><tr><th>Tool</th><th style="text-align:right">Calls</th></tr>' + rows + '</table>'
+ '</div>';
}
document.getElementById('root').innerHTML =
summaryCard(D7, 'Last 7 Days', '') +
summaryCard(D30, 'Last 30 Days', 'blue') +
modelCard(D7, '7 Days') +
modelCard(D30, '30 Days') +
dailyCard(D7) +
toolsCard(D7);
</script>
</body>
</html>'''
html = html.replace('__GENTIME__', generated)
html = html.replace('__USER__', user_email)
html = html.replace('__NODATA__', no_data_banner)
html = html.replace('__D7__', json.dumps(d7))
html = html.replace('__D30__', json.dumps(d30))
with open(output_path, 'w') as f:
f.write(html)
print("Dashboard written to: " + output_path)
PYEOF
rm -rf "$TMPDIR_DATA"
# -----------------------------------------------------------
# Step 5: Install the refresh script
# -----------------------------------------------------------
REFRESH_SCRIPT="$HOME/.claude/token-usage-dashboard.sh"
cat > "$REFRESH_SCRIPT" <<'REFRESH'
#!/bin/bash
# Quick refresh — regenerates token usage dashboard with latest data
MEASURE_PY=""
for f in "$HOME/.claude/skills/token-optimizer/scripts/measure.py" \
"$HOME/.claude/plugins/cache"/*/token-optimizer/*/skills/token-optimizer/scripts/measure.py; do
[ -f "$f" ] && MEASURE_PY="$f" && break
done
[ -z "$MEASURE_PY" ] && { echo "[Error] token-optimizer not found. Run setup again."; exit 1; }
TMPDIR_DATA=$(mktemp -d /tmp/token-usage-XXXXXXXXXX)
python3 "$MEASURE_PY" trends --json --days 7 > "$TMPDIR_DATA/d7.json" 2>/dev/null || echo '{}' > "$TMPDIR_DATA/d7.json"
python3 "$MEASURE_PY" trends --json --days 30 > "$TMPDIR_DATA/d30.json" 2>/dev/null || echo '{}' > "$TMPDIR_DATA/d30.json"
# Re-run the full setup to regenerate
bash "$HOME/.claude/token-usage-setup.sh"
rm -rf "$TMPDIR_DATA"
REFRESH
chmod +x "$REFRESH_SCRIPT" 2>/dev/null || true
# -----------------------------------------------------------
# Step 6: Add shell alias
# -----------------------------------------------------------
SHELL_RC="$HOME/.zshrc"
[ -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.zshrc" ] && SHELL_RC="$HOME/.bashrc"
if ! grep -q "alias tokens=" "$SHELL_RC" 2>/dev/null; then
echo "" >> "$SHELL_RC"
echo "# Claude Code token usage dashboard" >> "$SHELL_RC"
echo "alias tokens='bash ~/.claude/token-usage-setup.sh'" >> "$SHELL_RC"
success "Added 'tokens' alias to $SHELL_RC"
else
success "'tokens' alias already exists in $SHELL_RC"
fi
# -----------------------------------------------------------
# Step 7: Open the dashboard
# -----------------------------------------------------------
if [ -f "$OUTPUT" ]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
open "$OUTPUT"
elif command -v xdg-open &>/dev/null; then
xdg-open "$OUTPUT"
fi
success "Dashboard opened in browser"
else
fail "Dashboard generation failed"
fi
# -----------------------------------------------------------
# Summary
# -----------------------------------------------------------
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ Setup Complete! ║${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " ${GREEN}Dashboard${NC}: $OUTPUT"
echo -e " ${GREEN}Refresh${NC}: Type ${BOLD}tokens${NC} in a new terminal"
echo -e " (or run: bash ~/.claude/token-usage-setup.sh)"
echo ""
echo -e " ${YELLOW}Note${NC}: If this is your first time, you'll see empty data."
echo -e " Use Claude Code for a few sessions, then run ${BOLD}tokens${NC} again."
echo ""
echo -e " ${CYAN}Why not /stats?${NC}"
echo -e " /stats only shows the current session's tokens."
echo -e " This dashboard tracks ALL sessions over 7 and 30 days,"
echo -e " including cache efficiency, model mix, and estimated cost."
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment