Context window fill as a footer label; toast when it passes 70%

hooks/register.tsx 80 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4const WARN_AT = 70
5const YELLOW_FROM = 30
6const RED_FROM = 60
7
8const percent = atom({ plugin: 'context-gauge', key: 'percent' } as const, null)
9
10// Stores the fill for the footer label and toasts on crossing WARN_AT;
11// returns whether the context is past it, so the caller toasts once per crossing.
12async function refresh($: EngineInterface, hasWarned: boolean) {
13 const usage = await $.session.usage()
14 const now = usage.context.percent ?? 0
15
16 await update($, percent, () => now)
17
18 if (now >= WARN_AT && !hasWarned) {
19 $.ui.toast(`Context at ${now}% — consider /handover or /compact`)
20 }
21
22 return now >= WARN_AT
23}
24
25export const register: Register = on => {
26 let hasWarned = false
27
28 on('session.start', async ($, e, next) => {
29 const result = await next(e)
30 hasWarned = await refresh($, hasWarned)
31
32 return result
33 })
34
35 on('tool.call', async ($, e, next) => {
36 const result = await next(e)
37 hasWarned = await refresh($, hasWarned)
38
39 return result
40 })
41
42 on('turn.complete', async ($, e, next) => {
43 const result = await next(e)
44 hasWarned = await refresh($, hasWarned)
45
46 return result
47 })
48
49 on('session.compact', async ($, e, next) => {
50 const result = await next(e)
51 hasWarned = await refresh($, hasWarned)
52
53 return result
54 })
55
56 // Tails the hint line: the footer's right side holds the effort indicator,
57 // and a mode label there wraps to a row of its own. The engine's `tail` is
58 // always dim, so from YELLOW_FROM the hook draws the hint text itself
59 // with a colored gauge; the engine's pills around it stay its own.
60 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
61 const now = await read($, percent)
62
63 if (now === null) {
64 return next(e)
65 }
66 if (now < YELLOW_FROM) {
67 return next({ ...e, props: { ...e.props, tail: `◔ ${now}%` } })
68 }
69
70 const { Text } = $.ui.resolve(e)
71
72 return (
73 <Text dimColor>
74 {`${e.props.hint} · `}
75 <Text color={now >= RED_FROM ? 'red' : 'yellow'}>{`◔ ${now}%`}</Text>
76 </Text>
77 )
78 })
79}
80types/index.d.ts 8 lines1export type Percent = number | null
2
3declare module 'claude-code' {
4 interface PluginState {
5 'context-gauge': { percent: Percent }
6 }
7}
8