Bar above the prompt: context fill, session cost and plan limits, with a warning before compaction.

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 130 lines1import type { EngineInterface, Register } from 'claude-code'
2
3import type { Weather } from '../types'
4
5const weather = { plugin: 'context-weather', key: 'weather' } as const
6const isHidden = { plugin: 'context-weather', key: 'isHidden' } as const
7
8const TEXT = {
9 en: {
10 context: 'Context',
11 waiting: 'Context: waiting for the first reply ',
12 hide: 'Hide',
13 warn1: (percent: number | null) => `Context ${percent}% - consider wrapping up before compaction`,
14 warn2: (percent: number | null) => `Context ${percent}% - compaction is near, save your work state`,
15 },
16 pl: {
17 context: 'Kontekst',
18 waiting: 'Kontekst: czeka na pierwszą odpowiedź ',
19 hide: 'Ukryj',
20 warn1: (percent: number | null) => `Kontekst ${percent}% - warto domknąć wątek przed kompakcją`,
21 warn2: (percent: number | null) => `Kontekst ${percent}% - kompakcja blisko, zapisz stan pracy`,
22 },
23} as const
24
25type Text = (typeof TEXT)[keyof typeof TEXT]
26
27const STEPS = [70, 85, 95]
28const BAR = 10
29
30let warned = 0
31
32const kilo = (n: number) => (n >= 1000 ? `${Math.round(n / 1000)}k` : `${n}`)
33
34const colorOf = (percent: number) =>
35 percent >= 85 ? 'red' : percent >= 70 ? 'yellow' : 'green'
36
37const bar = (percent: number) => {
38 const full = Math.min(BAR, Math.round((percent / 100) * BAR))
39
40 return '█'.repeat(full) + '░'.repeat(BAR - full)
41}
42
43async function measure($: EngineInterface): Promise<Weather> {
44 const usage = await $.session.usage()
45 const limit = (kind: string) =>
46 usage.rateLimits.find(one => one.kind === kind)?.percentUsed ?? null
47
48 return {
49 percent: usage.context.percent ?? null,
50 tokens: usage.context.tokens ?? null,
51 window: usage.context.window,
52 usd: usage.cost?.usd ?? null,
53 fiveHour: limit('five_hour'),
54 sevenDay: limit('seven_day'),
55 }
56}
57
58const stepOf = (percent: number | null) => STEPS.filter(one => (percent ?? 0) >= one).length
59
60const warning = (T: Text, percent: number | null) =>
61 stepOf(percent) === 1 ? T.warn1(percent) : T.warn2(percent)
62
63const setWeather = async ($: EngineInterface) => {
64 await $.state.set(weather, await measure($))
65}
66
67export const register: Register = (on, options) => {
68 const T = TEXT[options.language === 'pl' ? 'pl' : 'en']
69
70 on('session.start', async ($, e, next) => {
71 await setWeather($)
72
73 return next(e)
74 })
75
76 on('turn.complete', async ($, e, next) => {
77 const now = await measure($)
78 await $.state.set(weather, now)
79 if (stepOf(now.percent) > warned) $.ui.toast(warning(T, now.percent))
80 warned = stepOf(now.percent)
81
82 return next(e)
83 })
84
85 on('tool.call', async ($, e, next) => {
86 const ran = await next(e)
87 await setWeather($)
88
89 return ran
90 })
91
92 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
93 const now = ((await $.state.get(weather)).value as Weather | null | undefined) ?? null
94 const hidden = ((await $.state.get(isHidden)).value as boolean | undefined) ?? false
95 if (e.props.hasSurvey || now === null || hidden) {
96 return next(e)
97 }
98
99 const { Box, Button, Text } = $.ui.resolve(e)
100
101 if (now.percent === null) {
102 return (
103 <Box>
104 <Text dimColor>{T.waiting}</Text>
105 <Button key="hide" label={T.hide} onPress={() => $.state.set(isHidden, true)} />
106 </Box>
107 )
108 }
109
110 const tokens = now.tokens === null ? '' : ` (${kilo(now.tokens)}/${kilo(now.window)})`
111 const money = now.usd === null ? '' : ` · $${now.usd.toFixed(2)}`
112 const five = now.fiveHour === null ? '' : ` · 5h ${Math.round(now.fiveHour)}%`
113 const seven = now.sevenDay === null ? '' : ` · 7d ${Math.round(now.sevenDay)}%`
114
115 return (
116 <Box>
117 <Text color={colorOf(now.percent)}>
118 {T.context} {bar(now.percent)} {now.percent}%{tokens}
119 </Text>
120 <Text dimColor>
121 {money}
122 {five}
123 {seven}{' '}
124 </Text>
125 <Button key="hide" label={T.hide} onPress={() => $.state.set(isHidden, true)} />
126 </Box>
127 )
128 })
129}
130types/index.d.ts 15 lines1export type Weather = {
2 percent: number | null
3 tokens: number | null
4 window: number
5 usd: number | null
6 fiveHour: number | null
7 sevenDay: number | null
8}
9
10declare module 'claude-code' {
11 interface PluginState {
12 'context-weather': { weather: Weather | null; isHidden: boolean }
13 }
14}
15