Shows subscription rate-limit usage as bars under the prompt

hooks/register.tsx 61 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Limit } from '../types'
5
6const limits = atom({ plugin: 'usage-limits', key: 'limits' } as const, [] as Limit[])
7
8const BAR_WIDTH = 28
9
10const LABELS: Record<string, string> = { five_hour: '5h', seven_day: '7d', spend_limit: 'spend' }
11
12function untilReset(resetsAt: string, now: number): string {
13 const minutes = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60000))
14 if (minutes < 60) return `${minutes}m`
15 if (minutes < 48 * 60) return `${Math.floor(minutes / 60)}h${minutes % 60}m`
16 return `${Math.floor(minutes / 1440)}d${Math.floor((minutes % 1440) / 60)}h`
17}
18
19export const register: Register = on => {
20 on('session.start', async ($, e, next) => {
21 const { rateLimits } = await $.session.usage()
22 await update($, limits, () => rateLimits)
23 return next(e)
24 })
25
26 on('session.measure', async ($, e, next) => {
27 await update($, limits, () => e.rateLimits)
28 return next(e)
29 })
30
31 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
32 const current = await read($, limits)
33 if (current.length === 0) return next(e)
34
35 const now = await $.clock.now()
36 const { Box, Text } = $.ui.resolve(e)
37
38 return (
39 <Box flexDirection="column">
40 {await next(e)}
41 <Box flexWrap="wrap" columnGap={1}>
42 {current.map(limit => {
43 const reset = limit.resetsAt ? ` · ${untilReset(limit.resetsAt, now)}` : ''
44 const label = `${LABELS[limit.kind] ?? limit.kind} ${limit.percentUsed}%${reset}`
45 const pad = Math.max(0, BAR_WIDTH - label.length)
46 const text = (' '.repeat(Math.floor(pad / 2)) + label).padEnd(BAR_WIDTH)
47 const filled = Math.round((Math.min(limit.percentUsed, 100) / 100) * BAR_WIDTH)
48 const color = limit.percentUsed >= 90 ? 'error' : limit.percentUsed >= 70 ? 'warning' : 'success'
49 return (
50 <Text>
51 <Text color="black" backgroundColor={color}>{text.slice(0, filled)}</Text>
52 <Text color="black" backgroundColor="inactive">{text.slice(filled)}</Text>
53 </Text>
54 )
55 })}
56 </Box>
57 </Box>
58 )
59 })
60}
61types/index.d.ts 8 lines1export type Limit = { kind: string; percentUsed: number; resetsAt?: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'usage-limits': { limits: Limit[] }
6 }
7}
8