Bar above the prompt: how much of your plan limit is left (5 hours and week) and when it resets.

Function-hook mods for Claude Code (terminal and desktop). Each one adds a small piece of UI or a command to your session.
| Mod | What it does |
|---|---|
usage-limits | A bar above the prompt: plan limit left (5 hours, week) and time to reset. |
proc-registry | /procs command and a Processes panel: background shells and subagents with ID, port and start time, plus a counter in the status line. |
context-weather | A bar above the prompt: context fill, session cost and plan limits, with a warning before compaction. |
claude plugin marketplace add Chronosauros/claude-mods
claude plugin install usage-limits@chronosaur-mods
claude plugin install proc-registry@chronosaur-mods
claude plugin install context-weather@chronosaur-mods
English by default. For Polish, set the language option per mod (/config, or in settings.json):
{
"pluginConfigs": {
"usage-limits@chronosaur-mods": { "options": { "language": "pl" } },
"proc-registry@chronosaur-mods": { "options": { "language": "pl" } },
"context-weather@chronosaur-mods": { "options": { "language": "pl" } }
}
}
MIT
hooks/register.tsx 133 lines1import type { EngineInterface, Register } from 'claude-code'
2
3import type { UsageLimits } from '../types'
4
5const limits = { plugin: 'usage-limits', key: 'limits' } as const
6const isHidden = { plugin: 'usage-limits', key: 'isHidden' } as const
7
8const BAR = 10
9const REFRESH_MS = 60_000
10
11const TEXT = {
12 en: {
13 names: { five_hour: '5h', seven_day: 'Week', spend_limit: 'Budget' } as Record<string, string>,
14 waiting: 'Limits: will show after the first reply ',
15 hide: 'Hide',
16 left: 'left',
17 reset: 'resets in',
18 },
19 pl: {
20 names: { five_hour: '5h', seven_day: 'Tydzień', spend_limit: 'Budżet' } as Record<string, string>,
21 waiting: 'Limity: pokażą się po pierwszej odpowiedzi ',
22 hide: 'Ukryj',
23 left: 'zostało',
24 reset: 'reset za',
25 },
26} as const
27
28const colorOf = (left: number) => (left <= 15 ? 'red' : left <= 35 ? 'yellow' : 'green')
29
30const bar = (left: number) => {
31 const full = Math.max(0, Math.min(BAR, Math.round((left / 100) * BAR)))
32
33 return '█'.repeat(full) + '░'.repeat(BAR - full)
34}
35
36// Relative time, so the module never depends on the sandbox's time zone.
37const countdown = (ms: number) => {
38 const minutes = Math.max(0, Math.round(ms / 60_000))
39 const days = Math.floor(minutes / 1440)
40 const hours = Math.floor((minutes % 1440) / 60)
41 const rest = minutes % 60
42
43 if (days > 0) return `${days} d ${hours} h`
44 if (hours > 0) return `${hours} h ${rest} min`
45 return `${rest} min`
46}
47
48async function measure($: EngineInterface): Promise<UsageLimits> {
49 const usage = await $.session.usage()
50 const measuredAt = await $.clock.now()
51 const windows = usage.rateLimits.map(one => ({
52 kind: one.kind,
53 percentUsed: one.percentUsed,
54 resetsAt: one.resetsAt ? Date.parse(one.resetsAt) : null,
55 }))
56
57 return { windows, measuredAt }
58}
59
60export const register: Register = (on, options) => {
61 const T = TEXT[options.language === 'pl' ? 'pl' : 'en']
62 let stop: (() => void) | null = null
63
64 on('session.start', async ($, e, next) => {
65 await $.state.set(limits, await measure($))
66 stop?.()
67 stop = $.clock.every(REFRESH_MS, () => {
68 void measure($).then(now => $.state.set(limits, now))
69 })
70
71 return next(e)
72 })
73
74 on('turn.complete', async ($, e, next) => {
75 await $.state.set(limits, await measure($))
76
77 return next(e)
78 })
79
80 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
81 const below = await next(e)
82 const { value: now = null } = await $.state.get(limits)
83 const { value: hidden = false } = await $.state.get(isHidden)
84
85 if (e.props.hasSurvey || hidden || now === null) {
86 return below
87 }
88
89 const { Box, Button, Text } = $.ui.resolve(e)
90
91 const row =
92 now.windows.length === 0 ? (
93 <Box key="usage">
94 <Text dimColor>{T.waiting}</Text>
95 <Button key="hide" label={T.hide} onPress={() => $.state.set(isHidden, true)} />
96 </Box>
97 ) : (
98 <Box key="usage">
99 {now.windows.map(one => {
100 const left = Math.max(0, Math.round(100 - one.percentUsed))
101 const reset =
102 one.resetsAt === null ? '' : ` · ${T.reset} ${countdown(one.resetsAt - now.measuredAt)}`
103
104 return (
105 <Text key={one.kind}>
106 <Text dimColor>{T.names[one.kind] ?? one.kind} </Text>
107 <Text color={colorOf(left)}>
108 {bar(left)} {T.left} {left}%
109 </Text>
110 <Text dimColor>
111 {reset}
112 {' '}
113 </Text>
114 </Text>
115 )
116 })}
117 <Button key="hide" label={T.hide} onPress={() => $.state.set(isHidden, true)} />
118 </Box>
119 )
120
121 if (!below) {
122 return row
123 }
124
125 return (
126 <Box flexDirection="column">
127 {below}
128 {row}
129 </Box>
130 )
131 })
132}
133types/index.d.ts 10 lines1export type UsageLimitsWindow = { kind: string; percentUsed: number; resetsAt: number | null }
2
3export type UsageLimits = { windows: UsageLimitsWindow[]; measuredAt: number } | null
4
5declare module 'claude-code' {
6 interface PluginState {
7 'usage-limits': { limits: UsageLimits; isHidden: boolean }
8 }
9}
10