Weekly Quota Pacer

·

UNLICENSED license

Per-model and all-model weekly quota meters, showing whether usage is ahead of an even pace and when the allowance resets.

Before you install

  • This version was reviewed before it was listed on Oct 18, 2026
  • Reads your Claude login
  • Connects to api.anthropic.com
Not showing after you paste? Fix it

Preview

Clean repo
Opus · F:15% ██▍ ▏ A:7% █▏ ▏ ⟳ 3d
New session
Opus · F:15% ██▍ ▏ A:9% █▌ ▏ ⟳ 3d
Dirty branch
Sonnet · F:15% ██▍ ▏ A:18% ██▉ ▏ ⟳ 3d
Near-full
Opus · F:15% ██▍ ▏ A:61% █████████▊ ▏ ⟳ 20h
1M context
Fable · F:80% █████🮕🮕🮕🮕🮕🮕🮕🮕 A:80% █████🮕🮕🮕🮕🮕🮕🮕🮕 ⟳ 5d
Post-compact
Haiku · F:15% ██▍ ▏ A:9% █▌ ▏ ⟳ 3d
Worktree
Opus · F:15% ██▍ ▏ A:20% ███▎ ▏ ⟳ 2d
Non-git
Opus · F:15% ██▍ ▏ A:9% █▌ ▏ ⟳ 3d

In Claude Desktop

Weekly Quota Pacer in Claude Desktop

Recorded with statusline-anywhere.

What it does

Written in
Node.js
Network access
api.anthropic.com
Reads your Claude login
Yes
Works in
Terminal

Source

#!/usr/bin/env node
// Claude Code statusline: weekly quota meters (per-model + all models) with
// even-pacing indicators. Solid fill = usage, 🮕 = spent ahead of pace,
// ▏ = pace point when behind it, ⟳ Nd = weekly reset countdown.
//
// Install: save to ~/.claude/statusline-quota.js, then in ~/.claude/settings.json:
//   "statusLine": { "type": "command", "command": "node ~/.claude/statusline-quota.js" }
//
// Reads your Claude Code OAuth token locally (macOS Keychain or
// ~/.claude/.credentials.json) and calls only api.anthropic.com/api/oauth/usage
// (the endpoint /usage uses; undocumented, may change). Needs Node 18+ and a
// font with Symbols for Legacy Computing (else swap 🮕 for ▒).

const fs = require("fs");
const os = require("os");
const path = require("path");
const { execFileSync } = require("child_process");

const CACHE_FILE = path.join(os.homedir(), ".claude", ".statusline-usage-cache.json");
const CACHE_TTL_MS = 60_000;
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
const BAR_WIDTH = 16;

const ESC = "\x1b[";
const RESET = ESC + "0m";
const DIM = ESC + "2m";
const BOLD = ESC + "1m";
// Solid fill in the bar's identity color on a dark track
const FG_PURPLE = "38;5;141m"; // Fable
const FG_BLUE = "38;5;75m"; // All models
const BG_TRACK = ESC + "48;5;236m";
const FG_MUTED = ESC + "38;5;245m";

function readStdin() {
  try {
    return JSON.parse(fs.readFileSync(0, "utf8"));
  } catch {
    return {};
  }
}

function getToken() {
  try {
    const raw = execFileSync(
      "security",
      ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
      { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
    );
    const tok = JSON.parse(raw).claudeAiOauth?.accessToken;
    if (tok) return tok;
  } catch {}
  try {
    const raw = fs.readFileSync(path.join(os.homedir(), ".claude", ".credentials.json"), "utf8");
    return JSON.parse(raw).claudeAiOauth?.accessToken || null;
  } catch {}
  return null;
}

function readCache() {
  try {
    return JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
  } catch {
    return null;
  }
}

async function getUsage() {
  const cache = readCache();
  if (cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) return cache.data;

  const token = getToken();
  if (!token) return cache?.data ?? null;

  try {
    const ctl = new AbortController();
    const timer = setTimeout(() => ctl.abort(), 3000);
    const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
      headers: {
        Authorization: `Bearer ${token}`,
        "anthropic-beta": "oauth-2025-04-20",
        "Content-Type": "application/json",
      },
      signal: ctl.signal,
    });
    clearTimeout(timer);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const data = await res.json();
    fs.writeFileSync(CACHE_FILE, JSON.stringify({ fetchedAt: Date.now(), data }));
    return data;
  } catch {
    return cache?.data ?? null; // stale is better than nothing
  }
}

function pacePercent(resetsAt) {
  const reset = Date.parse(resetsAt);
  if (Number.isNaN(reset)) return null;
  const start = reset - WEEK_MS;
  return Math.min(100, Math.max(0, ((Date.now() - start) / WEEK_MS) * 100));
}


// Bar with the percent embedded: fill = actual usage (background color),
// white '│' = where even weekly pacing would put you, drawn over fill or track.
function segment(label, used, resetsAt, identityFg) {
  const pace = pacePercent(resetsAt);
  const fillFg = ESC + identityFg;
  // Pace is shown one of two ways, so any two-tone fill is intentional:
  //  - behind pace: normal '▒' fill, thin '▏' hairline out in the track at
  //    the pace position
  //  - ahead of pace: no hairline; fill cells past the pace point render as
  //    denser '▓' to mark the overage
  const EIGHTHS = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"];
  const used8 = Math.round((used / 100) * BAR_WIDTH * 8);
  const ahead = pace !== null && used > pace;
  let paceCell =
    pace === null ? -1 : Math.min(BAR_WIDTH - 1, Math.round((pace / 100) * BAR_WIDTH));

  // Behind pace: eighth-precision edge glyph. Ahead of pace: whole cells only,
  // with at least the final fill cell textured '▚' so overage is always visible.
  let fullCells, rem, edgeCell;
  if (ahead) {
    fullCells = Math.max(1, Math.round(used8 / 8));
    rem = 0;
    edgeCell = -1;
    if (paceCell >= fullCells) paceCell = fullCells - 1;
  } else {
    fullCells = Math.floor(used8 / 8);
    rem = used8 % 8;
    edgeCell = rem > 0 ? fullCells : -1;
  }

  let markerCell = -1;
  if (pace !== null && !ahead) {
    // '▏' marks the LEFT boundary of its cell; keep it at/past the fill edge
    markerCell = paceCell;
    while (markerCell < BAR_WIDTH - 1 && markerCell * 8 < used8) markerCell++;
  }

  let out = "";
  for (let i = 0; i < BAR_WIDTH; i++) {
    if (i === markerCell && i !== edgeCell) {
      out += BG_TRACK + ESC + "38;5;240m" + "▏" + RESET;
    } else if (i < fullCells) {
      // Overage cells use '🮕' (U+1FB95 CHECKER BOARD FILL) — a fine
      // full-bleed checker texture from Symbols for Legacy Computing
      if (ahead && i >= paceCell) out += BG_TRACK + fillFg + "🮕" + RESET;
      else out += BG_TRACK + fillFg + "█" + RESET;
    } else if (i === edgeCell) {
      out += BG_TRACK + fillFg + EIGHTHS[rem] + RESET;
    } else {
      out += BG_TRACK + " " + RESET;
    }
  }
  return `${DIM}${label}:${RESET}${FG_MUTED}${Math.round(used)}%${RESET} ${out}`;
}

async function main() {
  const input = readStdin();
  try {
    fs.writeFileSync(
      path.join(os.homedir(), ".claude", ".statusline-last-input.json"),
      JSON.stringify(input, null, 2)
    );
  } catch {}
  const model = input.model?.display_name || "";
  const usage = await getUsage();

  const parts = [];
  // Family name only ("Fable 5" -> "Fable") to save characters
  const family = model.split(/[\s\d]/)[0];
  if (family) parts.push(`${DIM}${family} ·${RESET}`);

  if (!usage?.limits) {
    parts.push(`${DIM}quota unavailable${RESET}`);
  } else {
    const weeklyAll = usage.limits.find((l) => l.kind === "weekly_all");
    const scoped = usage.limits.filter((l) => l.kind === "weekly_scoped");
    for (const lim of scoped) {
      const label = (lim.scope?.model?.display_name || "S")[0].toUpperCase();
      parts.push(segment(label, lim.percent, lim.resets_at, FG_PURPLE));
    }
    if (weeklyAll)
      parts.push(segment("A", weeklyAll.percent, weeklyAll.resets_at, FG_BLUE));
    if (!scoped.length && !weeklyAll) parts.push(`${DIM}no weekly limits reported${RESET}`);

    const resetsAt = weeklyAll?.resets_at || scoped[0]?.resets_at;
    const resetMs = resetsAt ? Date.parse(resetsAt) - Date.now() : NaN;
    if (!Number.isNaN(resetMs) && resetMs > 0) {
      let left;
      if (resetMs >= 24 * 60 * 60 * 1000) left = `${Math.ceil(resetMs / 86_400_000)}d`;
      else if (resetMs >= 60 * 60 * 1000) left = `${Math.ceil(resetMs / 3_600_000)}h`;
      else left = `${Math.ceil(resetMs / 60_000)}m`;
      parts.push(`${DIM}⟳ ${left}${RESET}`);
    }
  }

  process.stdout.write(parts.join(" "));
}

main();

Badge for your README

Own this listing? Add it to your README. The badge shows the install count as it grows.

Reviewed on statuslin.es
[![Reviewed on statuslin.es](https://staging.statuslin.es/badge/c/weekly-quota-pacer-57a36566.svg)](https://staging.statuslin.es/c/weekly-quota-pacer-57a36566?utm_source=readme-badge)

Or show the preview card:

Preview on statuslin.es
[![Preview on statuslin.es](https://staging.statuslin.es/og/c/weekly-quota-pacer-57a36566.png)](https://staging.statuslin.es/c/weekly-quota-pacer-57a36566?utm_source=readme-badge)

More status lines

✧ ███████████▋ 73% 🗝 Ultima Weapon ✦ app ∙ main ♥ ████████████████████████ 100% 「SAVE POINT」 ✶ Limit Form ◉ 41
🤖 Opus 4.8 [200K] │ 🎯 high │ 📁 app │ 🌿 main 📅 07/07/26 Tue │ ⏰ 22:59:15 │ ₿$63k │ ☀️ +90°F Ctx: ● ▓▓░░░░ 33% [66K/200K] │ ⏱ 10m │ 🔧 2.1.155
[Opus 5.5] 📁 app | 🌿 main | $0.41 | ⏱️ 10m 12s ❤️❤️❤️❤️❤️❤️❤️🖤🖤🖤 26% 2h6m 🍗🍗🍗🍗🍗🍗🍗🍗🍗🦴 7% 2d0h 🟩🟩🟩🟩🟩⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛ 22%
┌─────────────────────────────────────────────────────────────────────────────────┬────────────────────────────────┐ │ session summary session b8e1c0d2 │ │ ├──────────────────────────┬──────────────────────────────────────────────────────┤ │ │ 📁 user/app +0 -0 lines │ Opus 5.5 effort high · 0min │ │ │ app main │ cost $0.00 (all time) · $0.00 (this session) │ │ ├──────────────────────────┴──────────────────────────────────────────────────────┤ │ │ tokens 0 (all time) · 0 (this session) context ██░░░░░░░░ 22% compact 0 times │ │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ 5h-quota ███░░░░░░░ 26% resets 2h6m 7d-quota █░░░░░░░░░ 7% resets 2d0h │ │ └─────────────────────────────────────────────────────────────────────────────────┴────────────────────────────────┘
Opus 4.8 | app main | ctx 22% | ▶ Fix lease reclaim

Related mods

⎿ agent-profiler: Agent Profiler pane opened (shown in the terminal and the desktop app). ╭────────────────────────────────────────────────────────────────────────────────────────────────✕─╮ │ 255ms agent time · 1 turn(s) │ │ model ███████░░░░░░░░░░░░░ 33% │ │ tools ░░░░░░░░░░░░░░░░░░░░ 0% │ │ subagents ░░░░░░░░░░░░░░░░░░░░ 0% │ │ engine/other █████████████░░░░░░░ 67% │ │ │ │ Running │ │ idle │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ⚠ agent-profiler: ⏱ model 33% · tools 0%

Execution profiler for Claude Code: model wait vs tools vs subagents, live, with OTLP export

Deck in Claude Desktop

A cockpit for Claude Code: plan bars and subagent strips, context, usage limits with reset countdowns and burn rates, spend, the model route and a pet. Six bar styles, three collapsed looks. CLI and Desktop.

Mod Gallery in Claude Desktop

Browse the reviewed mods on statuslin.es in a pane, with their install counts and what each can reach, and put one's install prompt in your prompt box. /gallery opens it. It runs nothing: you read the prompt and send it.

[-] ▄▄▄▀▀▀▀▀▄▄▄ ▀ ▀ ▀▀ ▀ ▀▄ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▄▀▀▀ ▀▀▀ ▀▀▄▄ Lv 5 ▀▀ ▀▀▀ ▀▀▀▀▀▀ ●●●●○ ▀▀ ▀▀ ♥♥♥♥♡

A pixel Pokémon that lives at the right edge of the band above the prompt