Battery-style usage bars above the prompt: context, rate limits, cost

A Claude Code mod that shows your usage as battery-style bars above the prompt: context window fill, the 5-hour and 7-day rate-limit windows (with reset countdowns, and the 7-day reset date and time), and the session's estimated API cost.
ctx ▰▰▰▱▱▱▱▱▱▱ 34% 5h ▰▰▱▱▱▱▱▱▱▱ 18% ↻2h14m 7d ▰▱▱▱▱▱▱▱▱▱ 9% ↻4d3h (Thu 30 Oct 14:00) ~$1.47 API
Bars turn yellow at 70% and red at 90%. They refresh after each turn. Rate limits are account-wide, so usage from other sessions shows up after this session's next reply. Reset times are hidden below 100 columns.
/usage-panel shows or hides the bars.
At the prompt of a Claude Code terminal session:
/plugin install usage-panel --marketplace psychemisss/claude-usage-panel
From a shell:
claude plugin install usage-panel --marketplace psychemisss/claude-usage-panel
In the desktop app: click + next to the prompt box, then Plugins, Add marketplace with psychemisss/claude-usage-panel, then Add plugin.
hooks/register.tsx 107 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Usage } from '../types'
5
6const usage = atom({ plugin: 'usage-panel', key: 'usage' } as const, null, {
7 shape: 'usage-1',
8})
9const isHidden = atom({ plugin: 'usage-panel', key: 'isHidden' } as const, false)
10
11const LABELS: Record<string, string> = {
12 five_hour: '5h',
13 seven_day: '7d',
14 spend_limit: 'spend',
15}
16
17// one cell per 10%, battery style
18const cells = (pct: number) => Math.round(Math.min(100, Math.max(0, pct)) / 10)
19
20const tone = (pct: number) =>
21 pct >= 90 ? 'error' : pct >= 70 ? 'warning' : 'success'
22
23const span = (ms: number) => {
24 const m = Math.max(0, Math.round(ms / 60000))
25 const d = Math.floor(m / 1440)
26 const h = Math.floor((m % 1440) / 60)
27 return d ? `${d}d${h}h` : h ? `${h}h${m % 60}m` : `${m}m`
28}
29
30// local time, e.g. "Thu 30 Oct 14:00"
31const when = (iso: string) => {
32 const t = new Date(iso)
33 const day = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][t.getDay()]
34 const mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][t.getMonth()]
35 const pad = (n: number) => String(n).padStart(2, '0')
36 return `${day} ${t.getDate()} ${mon} ${pad(t.getHours())}:${pad(t.getMinutes())}`
37}
38
39export const register: Register = on => {
40 on('session.start', async ($, e, next) => {
41 await $.command.register({
42 name: 'usage-panel',
43 description: 'Show or hide the Claude usage bars above the prompt',
44 })
45 const { startedAt, context, rateLimits, cost } = await $.session.usage()
46 await update($, usage, () => ({ startedAt, context, rateLimits, cost }))
47
48 return next(e)
49 })
50
51 on('command.run', { command: 'usage-panel' }, async $ => {
52 const hidden = await update($, isHidden, v => !v)
53
54 return { text: hidden ? 'Usage bars hidden.' : 'Usage bars shown.' }
55 })
56
57 // ponytail: pushed after each turn, so bars refresh per turn, not per second
58 on('session.measure', async ($, e, next) => {
59 await update($, usage, prev => ({
60 startedAt: prev?.startedAt ?? 0,
61 context: e.context,
62 rateLimits: e.rateLimits,
63 cost: e.cost,
64 }))
65
66 return next(e)
67 })
68
69 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
70 const u = await read($, usage)
71 if (e.props.hasSurvey || !u || (await read($, isHidden))) return next(e)
72
73 const { Box, Text } = $.ui.resolve(e)
74 const cols = e.viewport?.columns ?? 80
75 const showReset = cols >= 100
76 const now = await $.clock.now()
77 const ctx = u.context.percent ?? 0
78
79 const meter = (label: string, pct: number, extra?: string) => (
80 <Box marginRight={3}>
81 <Text dimColor>{label} </Text>
82 <Text color={tone(pct)}>{'▰'.repeat(cells(pct))}</Text>
83 <Text dimColor>{'▱'.repeat(10 - cells(pct))}</Text>
84 <Text> {pct}%</Text>
85 {extra && <Text dimColor> ↻{extra}</Text>}
86 </Box>
87 )
88
89 return (
90 <Box flexDirection="row" paddingX={1} marginTop={1}>
91 {meter('ctx', ctx)}
92 {u.rateLimits.map(r =>
93 meter(
94 LABELS[r.kind] ?? r.kind,
95 r.percentUsed,
96 showReset && r.resetsAt
97 ? span(Date.parse(r.resetsAt) - now) +
98 (r.kind === 'seven_day' ? ` (${when(r.resetsAt)})` : '')
99 : undefined,
100 ),
101 )}
102 {u.cost && <Text dimColor>~${u.cost.usd.toFixed(2)} API</Text>}
103 </Box>
104 )
105 })
106}
107types/index.d.ts 15 lines1export type RateLimit = { kind: string; percentUsed: number; resetsAt?: string }
2
3export type Usage = {
4 startedAt: number
5 context: { tokens?: number; window: number; percent?: number }
6 rateLimits: RateLimit[]
7 cost?: { usd: number }
8}
9
10declare module 'claude-code' {
11 interface PluginState {
12 'usage-panel': { usage: Shaped<Usage | null>; isHidden: boolean }
13 }
14}
15