Shows how much of the current usage window is left, and when it resets, at the end of the hint line under the prompt

A Claude Code mod that shows how much of your usage window is left, and when it resets, without getting in the way.
▸▸ auto mode on (shift+tab to cycle) ▰▰▰▰▰▰▰▱▱▱ 74% left · resets in 3h 7m
· week 20%) only when it has 25% or less left, or is tighter than the five-hour one./left prints the full line on demand.Claude Code reports these windows on a subscription plan, after the first response of a session; until then nothing is shown.
At the Claude Code prompt in a terminal:
/plugin install usage-left --marketplace JorgeRomero123/claude-usage-left
Answer y to add the marketplace, then pick a scope (user scope loads it in every session).
claude plugin validate .
claude plugin test .
claude --plugin-dir .
The mod API is early access and may change between Claude Code releases.
MIT
hooks/register.ts 125 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { UsageLeftWindow } from '../types'
5
6type SessionRateLimit = UsageLeftWindow
7
8const windows = atom({ plugin: 'usage-left', key: 'windows' } as const, [])
9const drawnAt = atom({ plugin: 'usage-left', key: 'now' } as const, 0)
10
11const NONE = 'No usage window reported yet: it comes with the first response, on a subscription plan.'
12
13const leftOf = (window: SessionRateLimit) => Math.max(0, Math.round(100 - window.percentUsed))
14
15/** How long until `iso`, as `2h 14m`, `38m` or `3d 4h`; nothing for a time that has passed or does not parse. */
16const until = (iso: string | undefined, now: number) => {
17 const ms = iso === undefined ? NaN : Date.parse(iso) - now
18 if (!(ms > 0)) return undefined
19 const minutes = Math.max(1, Math.round(ms / 60_000))
20 const days = Math.floor(minutes / 1440)
21 const hours = Math.floor((minutes % 1440) / 60)
22 if (days > 0) return `${days}d ${hours}h`
23
24 return hours > 0 ? `${hours}h ${minutes % 60}m` : `${minutes}m`
25}
26
27const part = (name: string, window: SessionRateLimit, now: number) => {
28 const wait = until(window.resetsAt, now)
29
30 return `${name}: ${leftOf(window)}% left${wait === undefined ? '' : `, resets in ${wait}`}`
31}
32
33/**
34 * The toast's line: what is left of the five-hour window and when it resets,
35 * and the weekly window too once it is the tighter of the two or nearly spent.
36 */
37const lineOf = (limits: readonly SessionRateLimit[], now: number) => {
38 const session = limits.find(w => w.kind === 'five_hour')
39 const week = limits.find(w => w.kind === 'seven_day')
40 const parts: string[] = []
41 if (session !== undefined) parts.push(part('Session', session, now))
42 if (week !== undefined && (session === undefined || leftOf(week) <= 25 || leftOf(week) < leftOf(session)))
43 parts.push(part('Week', week, now))
44
45 return parts.length === 0 ? undefined : parts.join(' · ')
46}
47
48/**
49 * What the hint line under the prompt ends with: a small meter of the five-hour
50 * window, what is left of it and when it resets; the week once it matters.
51 */
52const tailOf = (limits: readonly SessionRateLimit[], now: number) => {
53 const session = limits.find(w => w.kind === 'five_hour')
54 const week = limits.find(w => w.kind === 'seven_day')
55 const parts: string[] = []
56 if (session !== undefined) {
57 const left = leftOf(session)
58 const filled = Math.round(left / 10)
59 const wait = until(session.resetsAt, now)
60 parts.push(`${'\u25b0'.repeat(filled)}${'\u25b1'.repeat(10 - filled)} ${left}% left${wait === undefined ? '' : ` \u00b7 resets in ${wait}`}`)
61 }
62 if (week !== undefined && (session === undefined || leftOf(week) <= 25 || leftOf(week) < leftOf(session)))
63 parts.push(`week ${leftOf(week)}%`)
64
65 return parts.length === 0 ? undefined : parts.join(' \u00b7 ')
66}
67
68/** A toast marks the session window falling to each of these, percent left. */
69const MARKS = [50, 25, 10]
70
71export const register: Register = on => {
72 // The lowest mark already toasted in this window; a reset (the figure rising) arms them again.
73 let toasted = 101
74 let before = 100
75
76 on('session.start', async ($, e, next) => {
77 await $.command.register({ name: 'left', description: 'Show how much of the usage window is left' })
78 const { rateLimits } = await $.session.usage()
79 await update($, windows, () => [...rateLimits])
80 const started = await $.clock.now()
81 await update($, drawnAt, () => started)
82 $.clock.every(60_000, async () => {
83 const now = await $.clock.now()
84 await update($, drawnAt, () => now)
85 })
86
87 return next(e)
88 })
89
90 on('session.measure', async ($, e, next) => {
91 const now = await $.clock.now()
92 await update($, windows, () => [...e.rateLimits])
93 await update($, drawnAt, () => now)
94 const session = e.rateLimits.find(w => w.kind === 'five_hour')
95 if (session !== undefined) {
96 const left = leftOf(session)
97 if (left > before) toasted = 101
98 before = left
99 const mark = MARKS.filter(m => left <= m && m < toasted).pop()
100 if (mark !== undefined) {
101 toasted = mark
102 $.ui.toast(lineOf(e.rateLimits, now) ?? '', { timeoutMs: 8000 })
103 }
104 }
105
106 return next(e)
107 })
108
109 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
110 const tail = tailOf(await read($, windows), await read($, drawnAt))
111 if (tail === undefined) return next(e)
112
113 return next({ ...e, props: { ...e.props, tail: e.props.tail === undefined ? tail : `${e.props.tail} \u00b7 ${tail}` } })
114 })
115
116 on('command.run', { command: 'left' }, async $ => {
117 const { rateLimits } = await $.session.usage()
118 const now = await $.clock.now()
119 await update($, windows, () => [...rateLimits])
120 await update($, drawnAt, () => now)
121
122 return { text: lineOf(rateLimits, now) ?? NONE }
123 })
124}
125types/index.d.ts 13 lines1export type UsageLeftWindow = { kind: string; percentUsed: number; resetsAt?: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'usage-left': {
6 /** The rate-limit windows as last measured. */
7 windows: UsageLeftWindow[]
8 /** The time the countdown is drawn for, moved once a minute. */
9 now: number
10 }
11 }
12}
13