SLOPSHOPPER

context-weather

A weather-style gauge of the context window, drawn above the prompt.

newbandprompt
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · context-weather
› 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 ██████████░░░░░░░░░░ 49% context, clear ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
██████████░░░░░░░░░░ 49% context, clear
Source 2 files
hooks/register.tsx 71 lines
1import { atom, read, update } from 'claude-code'
2import type { Engine, Register } from 'claude-code'
3
4import type { Fill } from '../types'
5
6const percent = atom({ plugin: 'context-weather', key: 'percent' } as const, null)
7
8const CELLS = 20
9
10// Reads the live window's fill into the atom; the write redraws the band.
11// Never throws: a failed read must not hold up the prompt it gates.
12const refresh = async ($: Engine) => {
13  try {
14    const { context } = await $.session.usage()
15    const fill: Fill = context.percent ?? null
16    await update($, percent, () => fill)
17  } catch {
18    // keep the last reading
19  }
20}
21
22const weather = (pct: number) => {
23  if (pct < 50) return { word: 'clear', color: 'success' as const }
24  if (pct < 75) return { word: 'cloudy', color: 'success' as const }
25  if (pct < 90) return { word: 'rain', color: 'warning' as const }
26  return { word: 'storm', color: 'error' as const }
27}
28
29export const register: Register = on => {
30  on('session.start', async ($, e, next) => {
31    await refresh($)
32
33    return next(e)
34  })
35
36  on('prompt.submit', async ($, e, next) => {
37    await refresh($)
38
39    return next(e)
40  })
41
42  on('turn.complete', async ($, e, next) => {
43    await refresh($)
44
45    return next(e)
46  })
47
48  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
49    const pct = await read($, percent)
50
51    if (e.props.hasSurvey || pct === null) {
52      return next(e)
53    }
54
55    const { Box, Text } = $.ui.resolve(e)
56    const { word, color } = weather(pct)
57    const filled = Math.min(CELLS, Math.round((pct / 100) * CELLS))
58    const bar = '█'.repeat(filled) + '░'.repeat(CELLS - filled)
59
60    return (
61      <Box>
62        <Text color={color}>{bar}</Text>
63        <Text dimColor>
64          {' '}
65          {pct}% context, {word}
66        </Text>
67      </Box>
68    )
69  })
70}
71
types/index.d.ts 10 lines
1// Context window fill, as a whole percentage (0 to 100), null until the first
2// API response of the live window.
3export type Fill = number | null
4
5declare module 'claude-code' {
6  interface PluginState {
7    'context-weather': { percent: number | null }
8  }
9}
10