Shows remaining 5-hour and weekly rate-limit quota as a status line under the prompt

A Claude Code mod that pins your remaining subscription quota under the prompt:
quota-band 5h left 73% (resets 3h21m) · week left 61% (resets 2d17h)
five_hour and seven_day rate-limit windows Claude Code already receives from each API response; no extra API calls, no credentials.At the prompt of a terminal session:
/plugin install quota-band --marketplace SodaSolas/claude-quota-band
Answer y to add the marketplace, then pick a scope (user scope loads it in every session, including the desktop app's Code tab).
From a shell:
claude plugin install quota-band --marketplace SodaSolas/claude-quota-band
In the desktop app, where /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with SodaSolas/claude-quota-band, then Add plugin.
claude plugin validate .
claude plugin test .
MIT
hooks/register.tsx 53 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, SessionRateLimit } from 'claude-code'
3
4import type { Quota, Window } from '../types'
5
6const quota = atom({ plugin: 'quota-band', key: 'quota' } as const, { fiveHour: null, sevenDay: null })
7
8const pick = (limits: SessionRateLimit[], kind: string): Window | null => {
9 const w = limits.find(l => l.kind === kind)
10 return w ? { percentUsed: w.percentUsed, resetsAt: w.resetsAt } : null
11}
12
13export const toQuota = (limits: SessionRateLimit[]): Quota => ({
14 fiveHour: pick(limits, 'five_hour'),
15 sevenDay: pick(limits, 'seven_day'),
16})
17
18const left = (w: Window | null) => (w ? `${Math.max(0, Math.round((100 - w.percentUsed) * 10) / 10)}%` : '—')
19
20const resetIn = (w: Window | null, now: number) => {
21 if (!w?.resetsAt) return ''
22 const mins = Math.max(0, Math.round((Date.parse(w.resetsAt) - now) / 60000))
23 if (mins >= 1440) return ` (resets ${Math.floor(mins / 1440)}d${Math.floor((mins % 1440) / 60)}h)`
24 return ` (resets ${Math.floor(mins / 60)}h${mins % 60}m)`
25}
26
27export const describe = (q: Quota, now: number) =>
28 `5h left ${left(q.fiveHour)}${resetIn(q.fiveHour, now)} · week left ${left(q.sevenDay)}${resetIn(q.sevenDay, now)}`
29
30async function show($: EngineInterface) {
31 const q = await read($, quota)
32 $.ui.status(q.fiveHour || q.sevenDay ? describe(q, await $.clock.now()) : undefined)
33}
34
35export const register: Register = on => {
36 on('session.start', async ($, e, next) => {
37 const result = await next(e)
38 const { rateLimits } = await $.session.usage()
39 await update($, quota, () => toQuota(rateLimits))
40 await show($)
41 $.clock.every(60_000, () => show($))
42 return result
43 })
44
45 on('session.measure', async ($, e, next) => {
46 if (e.changed.includes('rateLimits')) {
47 await update($, quota, () => toQuota(e.rateLimits))
48 await show($)
49 }
50 return next(e)
51 })
52}
53types/index.d.ts 9 lines1export type Window = { percentUsed: number; resetsAt?: string }
2export type Quota = { fiveHour: Window | null; sevenDay: Window | null }
3
4declare module 'claude-code' {
5 interface PluginState {
6 'quota-band': { quota: Quota }
7 }
8}
9