SLOPSHOPPER

context-gauge

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

newspinnerguardtoast
v0.1.0no licenseupdated 2026-10-08arowinski/dotfiles/claude/mods/context-gauge
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · context-gauge
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts · ◔ 49%

Draws

Prompt hint
? for shortcuts · ◔ 49%
README

Dotfiles

Source 2 files
hooks/register.tsx 80 lines
1import { 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}
80
types/index.d.ts 8 lines
1export type Percent = number | null
2
3declare module 'claude-code' {
4  interface PluginState {
5    'context-gauge': { percent: Percent }
6  }
7}
8