Fable / All Models usage Relative to Weekly Reset "Pace".
A custom status line for Claude that shows usage relative to "pace" based on your quota reset timeline. Useful for knowing if your burn rate is too high or low relative to your next reset.
Updated 2026-07-30
0 copies
Preview
Clean repo
Opus · F:15% ██▍ ▏ A:7% █▏ ▏ ⟳ 3dNew session
Opus · F:15% ██▍ ▏ A:9% █▌ ▏ ⟳ 3dDirty branch
Sonnet · F:15% ██▍ ▏ A:18% ██▉ ▏ ⟳ 3dNear-full
Opus · F:15% ██▍ ▏ A:61% █████████▊ ▏ ⟳ 20h1M context
Fable · F:80% █████🮕🮕🮕🮕🮕🮕🮕🮕 A:80% █████🮕🮕🮕🮕🮕🮕🮕🮕 ⟳ 5dPost-compact
Haiku · F:15% ██▍ ▏ A:9% █▌ ▏ ⟳ 3dWorktree
Opus · F:15% ██▍ ▏ A:20% ███▎ ▏ ⟳ 2dNon-git
Opus · F:15% ██▍ ▏ A:9% █▌ ▏ ⟳ 3dSource
#!/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();What it shows
- Active model family, such as Opus, Sonnet, Haiku, or Fable
- Scoped weekly quota usage as a percentage and 16-cell bar
- All-model weekly quota usage as a percentage and 16-cell bar
- An even-pacing marker when usage is behind pace, or checker fill for the amount spent ahead of pace
- Time remaining until the weekly reset, rounded to days, hours, or minutes
Requirements
- Node.js 18 or newer
- A Claude Code OAuth token in the macOS Keychain or ~/.claude/.credentials.json
- Network access to api.anthropic.com for the OAuth usage endpoint
- A terminal font with Symbols for Legacy Computing to display the checker-fill glyph
- Write access to ~/.claude for the 60-second usage cache and last-input snapshot
Behavior notes
- The model label keeps only the family name, dropping spaces and version numbers
- Fresh cached usage is reused for 60 seconds; a failed request falls back to stale cached data
- When usage is behind even pace, a thin marker shows the pace point; usage ahead of pace changes the overage cells to a checker texture
- If neither scoped nor all-model weekly limits are returned, the status line says no weekly limits reported
- Without usable limits, the status line says quota unavailable
- The reset countdown uses days above 24 hours, hours above one hour, then minutes
- The OAuth usage endpoint is undocumented and may change
More status lines
Dreambase Panel
59 copies
╭─ CONTEXT ────────────────────────────────────────────╮
│ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │
├─ STATS ──────────────────────────────────────────────┤
│ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │
├─ REPO ───────────────────────────────────────────────┤
│ ◆ Opus 4.8 · main · 16:21 │
╰──────────────────────────────────────────────────────╯
Single-line usage
44 copies
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0hActivity Feed
20 copies
Opus 4.8 high ✦ │ ctx 22% │ app (main) │ PR #1287 ● │ $0.41 · 10m +128 -34
Session ● ● ● ● ● ● ● ● ○ ○ ○ 74% left Resets in 2h 7m
Weekly ● ● ● ● ● ● ● ● ● ● ○ 93% left Resets in 2d 1h
◐ Read:index.ts ✓ Read×1 ✓ Bash×1 ✓ Edit×1 ✓ Grep×1
◐ code-reviewer Review the change for regressions
Pace Dot Meters
16 copies
Opus 4.8 [high] ~/app main
16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Dreambase Flat
12 copies
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │ ◆ Opus 4.8 · main · 16:22
Usage Dot Bars
11 copies
Opus 4.8 │ ✍️ 22% │ app (main) │ ● high
current ●●○○○○○○○○ 26% ⟳ 8:49pm
weekly ○○○○○○○○○○ 7% ⟳ jul 8, 7:42pm