Shows your 5-hour and weekly Claude usage in the terminal's prompt footer

A Claude Code mod that shows, in the prompt footer, how much of your Claude subscription's 5-hour and weekly windows you have used, and how long until each one resets.
5h ━━━━━━━━ 49% ↻ 12m 7d ━━━━━━━━ 19% ↻ 4d 9h
The bar is green up to 70%, yellow up to 90%, and red beyond that. The time until reset refreshes every minute.
At the prompt of a Claude Code session in the terminal:
/plugin install usage-meter --marketplace ivanpasq/claude-usage-meter
From a shell:
claude plugin install usage-meter --marketplace ivanpasq/claude-usage-meter
In the desktop app, where /plugin is not available: click + next to the prompt box, then Plugins, Add marketplace with ivanpasq/claude-usage-meter, then Add plugin.
claude plugin update usage-meter@claude-usage-meter
claude plugin uninstall usage-meter
MIT
hooks/register.tsx 86 lines1import { atom, read, update } from 'claude-code'
2import type { Color, Register } from 'claude-code'
3
4import type { Limits } from '../types'
5
6const limits = atom({ plugin: 'usage-meter', key: 'limits' } as const, [] as Limits)
7
8const WINDOWS = [
9 { kind: 'five_hour', label: '5h' },
10 { kind: 'seven_day', label: '7d' },
11] as const
12const BAR_WIDTH = 8
13const MINUTE = 60_000
14
15export const tone = (percent: number): Color =>
16 percent >= 90 ? 'error' : percent >= 70 ? 'warning' : 'success'
17
18export const split = (percent: number) => {
19 const filled = Math.round((Math.min(Math.max(percent, 0), 100) / 100) * BAR_WIDTH)
20 return { filled: '━'.repeat(filled), empty: '━'.repeat(BAR_WIDTH - filled) }
21}
22
23export const untilReset = (resetsAt: string | undefined, now: number) => {
24 if (resetsAt === undefined) return undefined
25 const minutes = Math.max(0, Math.round((Date.parse(resetsAt) - now) / MINUTE))
26 const days = Math.floor(minutes / 1440)
27 const hours = Math.floor((minutes % 1440) / 60)
28 if (days > 0) return `${days}d ${hours}h`
29 if (hours > 0) return `${hours}h ${minutes % 60}m`
30 return `${minutes}m`
31}
32
33export const register: Register = on => {
34 on('session.start', async ($, e, next) => {
35 const started = await next(e)
36 $.ui.status(undefined)
37 const usage = await $.session.usage()
38 await update($, limits, () => usage.rateLimits)
39 $.clock.every(MINUTE, () => $.ui.invalidate('ui.render'))
40 return started
41 })
42
43 on('session.measure', async ($, e, next) => {
44 if (e.changed.includes('rateLimits')) await update($, limits, () => e.rateLimits)
45 return next(e)
46 })
47
48 on('ui.render', { component: 'SessionMode' }, async ($, e, next) => {
49 // The desktop app already shows the usage beside the model picker.
50 if (e.surface !== 'terminal') return next(e)
51
52 const current = await read($, limits)
53 const shown = WINDOWS.flatMap(w => {
54 const limit = current.find(l => l.kind === w.kind)
55 return limit === undefined ? [] : [{ ...w, limit }]
56 })
57 if (shown.length === 0) return next(e)
58
59 const now = await $.clock.now()
60 const { Box, Text } = $.ui.resolve(e)
61
62 return (
63 <Box key="usage-meter" flexDirection="row" gap={2}>
64 {shown.map(({ kind, label, limit }) => {
65 const color = tone(limit.percentUsed)
66 const bar = split(limit.percentUsed)
67 const reset = untilReset(limit.resetsAt, now)
68 return (
69 <Text key={kind} wrap="truncate-end">
70 <Text dimColor>{label} </Text>
71 <Text color={color}>{bar.filled}</Text>
72 <Text color="subtle">{bar.empty}</Text>
73 <Text color={color} weight="bold" tabular>
74 {' '}
75 {limit.percentUsed}%
76 </Text>
77 {reset === undefined ? null : <Text dimColor> ↻ {reset}</Text>}
78 </Text>
79 )
80 })}
81 {e.props.modes.length > 0 ? <Text dimColor>{e.props.modes.join(' & ')}</Text> : null}
82 </Box>
83 )
84 })
85}
86types/index.d.ts 9 lines1export type Limit = { kind: string; percentUsed: number; resetsAt?: string }
2export type Limits = readonly Limit[]
3
4declare module 'claude-code' {
5 interface PluginState {
6 'usage-meter': { limits: Limits }
7 }
8}
9