A thin line above the prompt for the 5-hour usage limit, colored by pace (where it would end at the current rate), with its time to reset, a tick at the…

Two small Claude Code mods for keeping an eye on how much room you have left: one for the context window, one for the 5-hour and 7-day usage limits. They work in the terminal and in the desktop app's Code tab. What changed in each version: CHANGELOG.md.

Above the prompt, limit-band (right) beside Cache Maxxer's band (left); in the footer, context-label beside the model name.
Cache Maxxer is not part of this repository. The prompt-cache countdown on the left of the screenshot is Cache Maxxer by Vayaan Labs, a separate mod. Install it from there if you want it too; limit-band shares the row with it when it is installed and takes the row by itself when it is not. These mods work without it.
Copy this into your coding agent (Claude Code, for example); the copy button is in the block's corner:
Install the Claude Code mods from https://github.com/pdrive/prompt-gauges. Read the "Install with an AI agent" section of its README and follow those steps, asking me before you install anything.
The steps below are written for that agent.
For the agent installing these mods. Ask the user before each step that installs anything.
- Ask which mods they want: context-label (context fill in the prompt footer), limit-band (the 5-hour and 7-day usage limits above the prompt; it shows only on a subscription with usage limits), or both.
- Ask whether they also want Cache Maxxer (the prompt-cache countdown, a separate mod by Vayaan Labs, not maintained here). Say plainly that it is someone else's mod; skip it if they say no.
- Install from a shell, in this order (limit-band before Cache Maxxer, so the two share one row): ``
claude plugin install context-label --marketplace pdrive/prompt-gauges claude plugin install limit-band --marketplace pdrive/prompt-gauges claude plugin install cache-maxxer --marketplace vayaan-labs/plugins`` Leave out the lines for what they declined.- If Cache Maxxer was already installed before limit-band, the two land on separate lines: in
~/.claude/settings.json, move"limit-band@prompt-gauges"above"cache-maxxer@vayaan-labs"inenabledPlugins, with the user's OK.- Tell the user to run
/reload-plugins(or start a new session), and that the settings are in/config(context-label.*,limit-band.*; see the sections below).- In the desktop app, where a shell is not at hand, the same is: + next to the prompt box → Plugins → Add marketplace
pdrive/prompt-gauges→ Add plugin.
The context window's fill, always visible in the prompt footer beside the model name, instead of behind a click:
■☐☐☐ ctx 23% · 228k/1M Opus 5.5 Medium
| Color | From (defaults) | Why |
|---|---|---|
| green | 0 | |
| yellow | half the window (500k of 1M), or 100k on Haiku | each turn resends the whole context; Haiku 5.5's price per token goes up 5x for prompts over 100k; the other Claude 4.6-and-later models price the full 1M window the same (pricing) |
| orange | 72% of the way to auto-compact (about 700k) | getting close |
| red | 93% of the way to auto-compact (about 900k) | auto-compact is near, which can break a chat |
Orange and red follow your session's auto-compact point (read from Claude Code, as /context shows it); with auto-compact off they follow the whole window.
Settings, with /config. The thresholds take any whole number from 0 to 100, and 0 turns a level off:
/config context-label.yellow_at=50 # % of the window
/config context-label.orange_at=72 # % of the way to auto-compact
/config context-label.red_at=93 # % of the way to auto-compact
/config context-label.haiku_price_step=true # yellow at 100k on Haiku 5.5
/config context-label.show=bar and text|bar|text
The levels are checked from red down and the first that is reached wins, so they never overlap: a lower level set above a higher one simply never shows.
A thin line above the prompt for the 5-hour usage window, with its percent and time to reset, and the 7-day window beside it:
5h ━━━━━━━━┃━━────────── 49% (2h 56m) ↑119% · 7d 31% (4d 18h)
The bar is colored by pace, not by how full it is. 49% used sounds fine, but if only 41% of the window has gone by, you are on course to hit the limit before it resets. So:
↑ by its window's pace; the numbers after it, the times and the 7-day percent stay dim;↑119% shows the projection once it reaches the orange pace setting (100% by default), for the 7-day window too; its arrow is orange, or red past the red pace setting;The percents come from the last API response; the countdowns tick on their own. It shows only on a subscription with usage limits.
Settings, with /config (the defaults are the values above). Any whole number, 0 is off; the levels are checked from red down, so they never overlap:
/config limit-band.orange_used=60 # % used, 0 to 100
/config limit-band.red_used=90 # % used, 0 to 100
/config limit-band.orange_pace=100 # % projected, 0 to 500
/config limit-band.red_pace=0 # % projected, 0 to 500 (off by default)
Pace coloring is an idea from PCritchfield/claude-gauges (MIT), whose status line and session pane color plan limits the same way; this is its own implementation for the band above the prompt.
It shares its row with Cache Maxxer's band when that mod is installed (and moves under it while Cache Maxxer's detail is open); without it, it takes the row by itself. It also gives that band fixed-width digits, so its ticking seconds don't nudge the row. The row works when limit-band is drawn around Cache Maxxer's band, which follows the order of enabledPlugins in ~/.claude/settings.json: if the two land on separate lines, list limit-band@prompt-gauges before cache-maxxer@vayaan-labs there and run /reload-plugins.
At the prompt of a Claude Code terminal session:
/plugin install context-label --marketplace pdrive/prompt-gauges
/plugin install limit-band --marketplace pdrive/prompt-gauges
Answer y to add the marketplace, then pick a scope. From a shell:
claude plugin install context-label --marketplace pdrive/prompt-gauges
claude plugin install limit-band --marketplace pdrive/prompt-gauges
In the desktop app: click + next to the prompt box, then Plugins, Add marketplace with pdrive/prompt-gauges, then Add plugin.
Each mod is a Claude Code hooks module under plugins/<mod>/: .claude-plugin/plugin.json (the manifest and its /config settings), hooks/register.tsx (the module), types/index.d.ts (its state contract and shared types) and tests/*.test.ts. .claude-plugin/marketplace.json lists them.
To run your changes, add a local clone as a marketplace and install from it; Claude Code then reads the mods from the clone, and an edit plus /reload-plugins reloads them:
claude plugin marketplace add <path to your clone>
claude plugin install context-label@prompt-gauges
claude plugin install limit-band@prompt-gauges
Before a commit:
claude plugin validate . and claude plugin validate plugins/<mod>.claude plugin test plugins/<mod> for each mod changed, with the tests updated for the new behaviour.version in its plugin.json for a change people should get, and add it to its heading here and to CHANGELOG.md.Limits of the desktop app worth knowing:
Svg is dropped, a Button loses the colors, sibling Texts are spaced apart (nest them in one Text), and the slot is cut at a fixed width. Keep it short.enabledPlugins (a reinstall appends it at the end and splits the row).isInteractive on the bar's Svg. The interactive frame a <title> tooltip needs reloads on every redraw, so the bar flashes with every countdown tick.tabular on its Text, so it doesn't change width.Keep docs, comments and commit messages about the mods themselves, and credit ideas taken from other projects here.
MIT
hooks/register.tsx 321 lines1import { atom, read, update } from 'claude-code'
2import type { ElementTable, PluginOptions, Register, RenderNode, SessionRateLimit } from 'claude-code'
3
4import type { Limits, Pace, PaceConfig, Window } from '../types'
5
6// A thin line above the prompt for the 5-hour usage window, with its percent and time to reset, and
7// the 7-day window beside it. The bar is colored by pace, not by how full it is: where the window
8// would end at the rate used so far (used % over the share of the window gone). Green on pace, orange
9// when it would run out before the reset or once 60% is used, red well past that or once 90% is used
10// (each a setting); `↑117%` says where it is heading, and a tick on the bar stands where the window's clock is.
11// The percents come from the last API response; the countdowns tick here. It sits on the same row
12// as whatever band is drawn beneath it (Cache Maxxer's): that band takes the room left, this sits at the right end.
13
14const limitsAtom = atom({ plugin: 'limit-band', key: 'limits' } as const, { fiveHour: null, sevenDay: null })
15const tickAtom = atom({ plugin: 'limit-band', key: 'tick' } as const, 0)
16// Cache Maxxer's own values, read to know whether its band is open with its detail
17const cmHidden = atom({ plugin: 'cache-maxxer', key: 'hidden' } as const, false)
18const cmExpanded = atom({ plugin: 'cache-maxxer', key: 'expanded' } as const, false)
19
20const TICK_MS = 30_000
21const BAR_CELLS = 20
22const BAR_PX = 80
23const TRACK = '#8888884d'
24
25const HOUR = 3_600_000
26const FIVE_HOURS = 5 * HOUR
27const SEVEN_DAYS = 7 * 24 * HOUR
28const TICK = '#bdbdbd'
29// Orange and red far enough apart to tell without the other beside them
30export const PACE_COLORS: Record<Pace, string> = { ok: '#3cc43c', ahead: '#ff9800', over: '#e53935' }
31
32export const DEFAULT_PACE: PaceConfig = { orangeUsed: 60, redUsed: 90, orangePace: 100, redPace: null }
33
34// A whole-percent setting: 60 -> 60, 0 or "off" -> null (off); a value outside 0 to max, or no number
35// at all, falls back to the default.
36export function toPct(v: unknown, fallback: number | null, max: number): number | null {
37 const n = typeof v === 'number' ? v : typeof v === 'string' ? (v.trim() === 'off' ? 0 : parseInt(v, 10)) : NaN
38 const pct = Number.isFinite(n) && n >= 0 && n <= max ? Math.round(n) : (fallback ?? 0)
39 return pct === 0 ? null : pct
40}
41
42// Checked from red down, so a lower level set above a higher one never shows: they cannot overlap.
43export function toPaceConfig(options: PluginOptions): PaceConfig {
44 return {
45 orangeUsed: toPct(options.orange_used, DEFAULT_PACE.orangeUsed, 100),
46 redUsed: toPct(options.red_used, DEFAULT_PACE.redUsed, 100),
47 orangePace: toPct(options.orange_pace, DEFAULT_PACE.orangePace, 500),
48 redPace: toPct(options.red_pace, DEFAULT_PACE.redPace, 500),
49 }
50}
51
52type El = { type: string; props?: Record<string, unknown>; children?: unknown }
53
54// Whether any Text in the tree already says how its digits are drawn (`tabular`, either way).
55function setsTabular(node: unknown): boolean {
56 if (node === null || typeof node !== 'object') return false
57 const el = node as El
58 if (el.type === 'Text' && el.props !== undefined && 'tabular' in el.props) return true
59 return Array.isArray(el.children) && el.children.some(setsTabular)
60}
61
62// The band drawn beneath this one (Cache Maxxer's) with fixed-width digits on every Text, so its
63// ticking seconds stop nudging the row; a copy, nothing else in it changed. Once that band sets
64// `tabular` itself anywhere, it is handed on as drawn: its own choice wins and this steps aside.
65export function withTabularDigits(node: RenderNode | null | undefined): RenderNode | null | undefined {
66 if (setsTabular(node)) return node
67 return addTabular(node)
68}
69
70function addTabular(node: RenderNode | null | undefined): RenderNode | null | undefined {
71 if (node === null || node === undefined || typeof node === 'string') return node
72 const el = node as El
73 const children = Array.isArray(el.children) ? el.children.map(c => addTabular(c as RenderNode)) : el.children
74 const props = el.type === 'Text' ? { ...(el.props ?? {}), tabular: true } : el.props
75 return { ...el, ...(props ? { props } : {}), ...(children !== undefined ? { children } : {}) } as RenderNode
76}
77
78const toWindow = (l: SessionRateLimit | undefined): Window | null => {
79 if (!l) return null
80 const at = l.resetsAt ? Date.parse(l.resetsAt) : NaN
81 return { percent: l.percentUsed, resetsAt: Number.isNaN(at) ? null : at }
82}
83
84export const toLimits = (list: SessionRateLimit[]): Limits => ({
85 fiveHour: toWindow(list.find(l => l.kind === 'five_hour')),
86 sevenDay: toWindow(list.find(l => l.kind === 'seven_day')),
87})
88
89// How far through the window the clock is, 0 to 1; null without a reset time or once past it.
90export function elapsedOf(w: Window, lengthMs: number, now: number): number | null {
91 if (w.resetsAt === null || w.resetsAt <= now) return null
92 return Math.min(Math.max(1 - (w.resetsAt - now) / lengthMs, 0), 1)
93}
94
95// Where the window ends at this rate, in percent; null before a tenth of it has gone (too early to say).
96export function projectedOf(w: Window, lengthMs: number, now: number): number | null {
97 const elapsed = elapsedOf(w, lengthMs, now)
98 return elapsed === null || elapsed < 0.1 ? null : (w.percent / elapsed)
99}
100
101// Orange from a projected 100% or once 60% is used, red once 90% is used (the defaults; red by pace is
102// off unless set): the used floors hold whatever the pace, since near the top little is left either way.
103export function paceOf(w: Window, lengthMs: number, now: number, cfg: PaceConfig = DEFAULT_PACE): Pace {
104 if (w.resetsAt !== null && w.resetsAt <= now) return 'ok'
105 const projected = projectedOf(w, lengthMs, now)
106 const reaches = (value: number | null, at: number | null) => value !== null && at !== null && value >= at
107 if (reaches(w.percent, cfg.redUsed) || reaches(projected, cfg.redPace)) return 'over'
108 if (reaches(w.percent, cfg.orangeUsed) || reaches(projected, cfg.orangePace)) return 'ahead'
109 return 'ok'
110}
111
112// The pace alone, without the used floors: orange from `orangePace` projected, red from `redPace`;
113// null while the projection reaches neither (or both are off). The `↑` hint and its color follow it.
114export function hintPaceOf(w: Window, lengthMs: number, now: number, cfg: PaceConfig = DEFAULT_PACE): Pace | null {
115 const projected = projectedOf(w, lengthMs, now)
116 if (projected === null) return null
117 if (cfg.redPace !== null && projected >= cfg.redPace) return 'over'
118 if (cfg.orangePace !== null && projected >= cfg.orangePace) return 'ahead'
119 return null
120}
121
122// " ↑117%" once the projection reaches the orange (or red) pace setting, else ''.
123export function paceHint(w: Window, lengthMs: number, now: number, cfg: PaceConfig = DEFAULT_PACE): string {
124 const projected = projectedOf(w, lengthMs, now)
125 return projected !== null && hintPaceOf(w, lengthMs, now, cfg) !== null ? ` ↑${Math.round(projected)}%` : ''
126}
127
128// "2h 14m", "3d 4h", "12m"; null when the window has no reset time.
129export function untilReset(resetsAt: number | null, now: number): string | null {
130 if (resetsAt === null) return null
131 const mins = Math.max(0, Math.ceil((resetsAt - now) / 60_000))
132 if (mins === 0) return 'reset'
133 const d = Math.floor(mins / 1440)
134 const h = Math.floor((mins % 1440) / 60)
135 const m = mins % 60
136 if (d > 0) return `${d}d ${h}h`
137 if (h > 0) return `${h}h ${m}m`
138 return `${m}m`
139}
140
141const fmtPct = (p: number) => `${Math.round(p)}%`
142
143// "42% (2h 14m)"; a window past its reset reads 0%, since the next response will say so.
144export function label(w: Window, now: number): string {
145 const left = untilReset(w.resetsAt, now)
146 const isPast = left === 'reset'
147 return `${isPast ? '0%' : fmtPct(w.percent)}${left && !isPast ? ` (${left})` : ''}`
148}
149
150const shownPercent = (w: Window, now: number) => (w.resetsAt !== null && w.resetsAt <= now ? 0 : w.percent)
151
152// How full the window is, by the used floors alone: green, orange from 60%, red from 90% (the defaults).
153export function fillOf(percent: number, cfg: PaceConfig = DEFAULT_PACE): Pace {
154 if (cfg.redUsed !== null && percent >= cfg.redUsed) return 'over'
155 if (cfg.orangeUsed !== null && percent >= cfg.orangeUsed) return 'ahead'
156 return 'ok'
157}
158
159// The band's text as runs, each with its color or none (dim): the 5-hour percent by how full it is,
160// each hint's `↑` alone by its window's pace (its number dim), the times and the 7-day percent dim.
161// "67% (2h 21m) ↑126% · 7d 33% (4d 17h) ↑102%"
162export function textRuns(five: Window, seven: Window | null, now: number, cfg: PaceConfig = DEFAULT_PACE): { text: string; color?: string }[] {
163 const left = (w: Window) => {
164 const t = untilReset(w.resetsAt, now)
165 return t && t !== 'reset' ? ` (${t})` : ''
166 }
167 const fivePct = shownPercent(five, now)
168 const runs: { text: string; color?: string }[] = [
169 { text: fmtPct(fivePct), color: PACE_COLORS[fillOf(fivePct, cfg)] },
170 { text: left(five) },
171 ]
172 // " ↑126%" as " ", "↑" in the pace color, "126%" dim
173 const hint = (text: string, pace: Pace) => runs.push({ text: ' ' }, { text: '↑', color: PACE_COLORS[pace] }, { text: text.slice(2) })
174 const fiveHint = paceHint(five, FIVE_HOURS, now, cfg)
175 if (fiveHint) hint(fiveHint, hintPaceOf(five, FIVE_HOURS, now, cfg) ?? 'ahead')
176 if (seven) {
177 runs.push({ text: ` · 7d ${fmtPct(shownPercent(seven, now))}${left(seven)}` })
178 const sevenHint = paceHint(seven, SEVEN_DAYS, now, cfg)
179 if (sevenHint) hint(sevenHint, hintPaceOf(seven, SEVEN_DAYS, now, cfg) ?? 'ahead')
180 }
181 return runs
182}
183
184const PACE_WORDS: Record<Pace, string> = { ok: 'on pace', ahead: 'ahead of pace', over: 'well ahead of pace' }
185
186// What the bar says, as its alt text:
187// "5-hour limit: 67% used, 53% of the window gone (resets in 2h 21m). At this rate: 126% by the reset, ahead of pace."
188export function barTitle(w: Window, now: number, cfg: PaceConfig = DEFAULT_PACE): string {
189 const pct = shownPercent(w, now)
190 const elapsed = elapsedOf(w, FIVE_HOURS, now)
191 const left = untilReset(w.resetsAt, now)
192 const projected = projectedOf(w, FIVE_HOURS, now)
193 const fill = fillOf(pct, cfg)
194 return (
195 `5-hour limit: ${fmtPct(pct)} used` +
196 (elapsed === null ? '' : `, ${fmtPct(elapsed * 100)} of the window gone`) +
197 (left && left !== 'reset' ? ` (resets in ${left})` : '') +
198 '.' +
199 (projected === null ? ' Too early in the window to project.' : ` At this rate: ${fmtPct(projected)} by the reset, ${PACE_WORDS[paceOf(w, FIVE_HOURS, now, cfg)]}.`) +
200 (fill === 'ok' ? '' : ` Past the ${fill === 'over' ? 'red' : 'orange'} line of ${fmtPct((fill === 'over' ? cfg.redUsed : cfg.orangeUsed) ?? 0)} used.`)
201 )
202}
203
204// The bar is 4px high in an 8px box, so the clock's tick stands out above and below it. Drawn as an
205// image: a hover tooltip needs an interactive frame, and the frame reloads (flashes) on every redraw.
206export function barSvg(percent: number, pace: Pace, elapsed: number | null, width = BAR_PX): string {
207 const fill = Math.round((width * Math.min(Math.max(percent, 0), 100)) / 100)
208 const tickX = elapsed === null ? null : Math.min(width - 2, Math.max(0, Math.round(elapsed * width) - 1))
209 return (
210 `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="8" viewBox="0 0 ${width} 8">` +
211 `<rect x="0" y="2" width="${width}" height="4" rx="2" fill="${TRACK}"/>` +
212 (fill > 0 ? `<rect x="0" y="2" width="${fill}" height="4" rx="2" fill="${PACE_COLORS[pace]}"/>` : '') +
213 (tickX === null ? '' : `<rect x="${tickX}" y="0" width="2" height="8" rx="1" fill="${TICK}"/>`) +
214 `</svg>`
215 )
216}
217
218// The terminal's bar in cells, the clock's cell drawn as a tick: runs of one kind each.
219export function barCells(percent: number, elapsed: number | null): { kind: 'fill' | 'track' | 'tick'; text: string }[] {
220 const filled = Math.round((BAR_CELLS * Math.min(Math.max(percent, 0), 100)) / 100)
221 const tickAt = elapsed === null ? -1 : Math.min(BAR_CELLS - 1, Math.floor(elapsed * BAR_CELLS))
222 const runs: { kind: 'fill' | 'track' | 'tick'; text: string }[] = []
223 for (let i = 0; i < BAR_CELLS; i++) {
224 const kind = i === tickAt ? 'tick' : i < filled ? 'fill' : 'track'
225 const ch = kind === 'tick' ? '┃' : kind === 'fill' ? '━' : '─'
226 const last = runs[runs.length - 1]
227 if (last?.kind === kind) last.text += ch
228 else runs.push({ kind, text: ch })
229 }
230 return runs
231}
232
233export const register: Register = (on, options) => {
234 const paceCfg = toPaceConfig(options)
235 let timer: { cancel: () => void } | null = null
236
237 on('session.start', async ($, e, next) => {
238 const started = await next(e)
239 try {
240 const usage = await $.session.usage()
241 await update($, limitsAtom, () => toLimits(usage.rateLimits))
242 } catch {
243 // No reading yet: the band waits for the first response
244 }
245 timer?.cancel()
246 timer = $.clock.every(TICK_MS, async () => {
247 const now = await $.clock.now()
248 await update($, tickAtom, () => now)
249 })
250 return started
251 })
252
253 on('session.measure', async ($, e, next) => {
254 if (e.changed.includes('rateLimits')) await update($, limitsAtom, () => toLimits(e.rateLimits))
255 return next(e)
256 })
257
258 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
259 await read($, tickAtom)
260 const limits = await read($, limitsAtom)
261 const five = limits.fiveHour
262 if (e.props.hasSurvey || five === null) return next(e)
263
264 const rest = withTabularDigits(await next(e))
265 const now = await $.clock.now()
266 const pct = shownPercent(five, now)
267 const pace = paceOf(five, FIVE_HOURS, now, paceCfg)
268 const elapsed = elapsedOf(five, FIVE_HOURS, now)
269 const seven = limits.sevenDay
270 const runs = textRuns(five, seven, now, paceCfg)
271 const title = barTitle(five, now, paceCfg)
272 // Beside Cache Maxxer's band on one row, except when its detail is open: then under it, full width.
273 const isUnder = !(await read($, cmHidden)) && (await read($, cmExpanded))
274 const row = { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-end', columnGap: 2 } as const
275 const column = { flexDirection: 'column', rowGap: 1 } as const
276 const outer = isUnder ? column : row
277
278 if (e.surface === 'desktop') {
279 const { Box, Svg, Text } = $.ui.resolve(e) as ElementTable<'desktop'>
280 return (
281 <Box {...outer}>
282 {rest}
283 <Box key="limit-band" flexDirection="row" alignItems="center" columnGap={1} paddingX={1} flexShrink={0}>
284 <Text key="name" dimColor>5h</Text>
285 <Svg
286 key="bar"
287 source={barSvg(pct, pace, elapsed, isUnder ? BAR_PX * 2 : BAR_PX)}
288 alt={title}
289 width={isUnder ? BAR_PX * 2 : BAR_PX}
290 height={8}
291 />
292 {/* One Text with the runs nested: as sibling Texts the desktop spaces them apart */}
293 <Text key="text" dimColor tabular>
294 {runs.map((r, i) => (r.color ? <Text key={`t${i}`} color={r.color}>{r.text}</Text> : r.text))}
295 </Text>
296 </Box>
297 </Box>
298 )
299 }
300
301 const { Box, Text } = $.ui.resolve(e)
302 return (
303 <Box {...outer}>
304 {rest}
305 <Box key="limit-band" flexDirection="row" flexShrink={0}>
306 <Text key="name" dimColor>5h </Text>
307 {barCells(pct, elapsed).map((r, i) =>
308 r.kind === 'fill' ? <Text key={`b${i}`} color={PACE_COLORS[pace]}>{r.text}</Text>
309 : r.kind === 'tick' ? <Text key={`b${i}`} color={TICK}>{r.text}</Text>
310 : <Text key={`b${i}`} dimColor>{r.text}</Text>,
311 )}
312 <Text key="text" dimColor tabular>
313 {' '}
314 {runs.map((r, i) => (r.color ? <Text key={`t${i}`} color={r.color}>{r.text}</Text> : r.text))}
315 </Text>
316 </Box>
317 </Box>
318 )
319 })
320}
321types/index.d.ts 15 lines1export type Window = { percent: number; resetsAt: number | null }
2export type Limits = { fiveHour: Window | null; sevenDay: Window | null }
3// ok: on pace; ahead: would run out before the reset, or 60% used; over: well past that, or 90% used (the defaults)
4export type Pace = 'ok' | 'ahead' | 'over'
5// The userConfig thresholds in percent; null is off
6export type PaceConfig = { orangeUsed: number | null; redUsed: number | null; orangePace: number | null; redPace: number | null }
7
8declare module 'claude-code' {
9 interface PluginState {
10 'limit-band': { limits: Limits; tick: number }
11 // Read only, never written: Cache Maxxer's view, to lay this band beside or under it
12 'cache-maxxer': { hidden: boolean; expanded: boolean }
13 }
14}
15