SLOPSHOPPER

context-bar

Your status line (model, session/weekly usage, context bar) above the prompt

newbandguard
v0.1.0no licenseupdated 2026-10-07whalewhalewhaleson/claude-mods/plugins/context-bar
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · context-bar
› 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 ✦ claude-opus-5-5 · compactions 0 · $0.42 session 31% · weekly – · resets in NaNm context ━━━━━━━━━━━━━━━━━━━━━━━━ 49% 97k / 200k ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
✦ claude-opus-5-5 · compactions 0 · $0.42 session 31% · weekly – · resets in NaNm context ━━━━━━━━━━━━━━━━━━━━━━━━ 49% 97k / 200k
README

whaleson-mods

Claude Code mods, as a plugin marketplace.

  • context-bar: model, session/weekly usage and a context bar above the prompt.

Use in a repo (cloud sessions included) by adding to its .claude/settings.json:

{
  "extraKnownMarketplaces": {
    "whaleson-mods": { "source": { "source": "github", "repo": "whalewhalewhaleson/claude-mods" } }
  },
  "enabledPlugins": { "context-bar@whaleson-mods": true }
}
Source 2 files
hooks/register.tsx 133 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Snapshot } from '../types'
5
6const snap = atom({ plugin: 'context-bar', key: 'snap' } as const, null)
7const compactions = atom({ plugin: 'context-bar', key: 'compactions' } as const, 0)
8
9const WIDTH = 24
10
11const C = {
12  model: '#c4a7e7',
13  label: '#908caa',
14  value: '#e0def4',
15  ok: '#9ccfd8',
16  warn: '#f6c177',
17  hot: '#eb6f92',
18  empty: '#403d52',
19}
20
21export const shade = (percent: number) =>
22  percent >= 80 ? C.hot : percent >= 50 ? C.warn : C.ok
23
24export const k = (n: number) => (n >= 1000 ? `${Math.round(n / 1000)}k` : `${n}`)
25
26export const countdown = (resetsAt: string, now: number) => {
27  const mins = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60000))
28  const h = Math.floor(mins / 60)
29  return h > 0 ? `${h}h ${mins % 60}m` : `${mins}m`
30}
31
32async function refresh($: EngineInterface) {
33  const usage = await $.session.usage()
34  const limit = (kind: string) => usage.rateLimits.find(r => r.kind === kind)
35  const five = limit('five_hour')
36  const week = limit('seven_day')
37  const now: Snapshot = {
38    model: await $.session.model(),
39    compactions: await read($, compactions),
40    tokens: usage.context.tokens,
41    window: usage.context.window,
42    percent: usage.context.percent,
43    session: five?.percentUsed,
44    weekly: week?.percentUsed,
45    resetsAt: five?.resetsAt,
46    usd: usage.cost?.usd,
47  }
48  await update($, snap, () => now)
49}
50
51export const register: Register = on => {
52  on('session.start', async ($, e, next) => {
53    void refresh($).catch(() => {})
54    return next(e)
55  })
56
57  on('session.compact', async ($, e, next) => {
58    const done = await next(e)
59    await update($, compactions, n => n + 1)
60    void refresh($).catch(() => {})
61    return done
62  })
63
64  on('tool.call', async ($, e, next) => {
65    const ran = await next(e)
66    void refresh($).catch(() => {})
67    return ran
68  })
69
70  on('turn.complete', async ($, e, next) => {
71    void refresh($).catch(() => {})
72    return next(e)
73  })
74
75  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
76    if (e.props.hasSurvey) return next(e)
77    const s = await read($, snap)
78    if (s === null) return next(e)
79
80    const { Box, Text } = $.ui.resolve(e)
81    const sep = <Text color={C.empty}> · </Text>
82    const clock = await $.clock.now()
83    const stat = (label: string, n?: number) => (
84      <Box>
85        <Text color={C.label}>{label} </Text>
86        {n === undefined ? (
87          <Text color={C.empty}>–</Text>
88        ) : (
89          <Text color={shade(n)}>{Math.round(n)}%</Text>
90        )}
91      </Box>
92    )
93    const full = s.percent === undefined ? 0 : Math.round((s.percent / 100) * WIDTH)
94
95    return (
96      <Box flexDirection="column">
97        <Box>
98          <Text color={C.model} bold>✦ {s.model}</Text>
99          {sep}
100          <Text color={C.label}>compactions </Text>
101          <Text color={C.value}>{s.compactions}</Text>
102          {s.usd !== undefined && sep}
103          {s.usd !== undefined && <Text color={C.value}>${s.usd.toFixed(2)}</Text>}
104        </Box>
105        <Box>
106          {stat('session', s.session)}
107          {sep}
108          {stat('weekly', s.weekly)}
109          {s.resetsAt !== undefined && sep}
110          {s.resetsAt !== undefined && <Text color={C.label}>resets in </Text>}
111          {s.resetsAt !== undefined && <Text color={C.value}>{countdown(s.resetsAt, clock)}</Text>}
112        </Box>
113        <Box>
114          <Text color={C.label}>context </Text>
115          {s.percent === undefined ? (
116            <Box>
117              <Text color={C.empty}>{'━'.repeat(WIDTH)}</Text>
118              <Text color={C.label} italic> waiting for first reply…</Text>
119            </Box>
120          ) : (
121            <Box>
122              <Text color={shade(s.percent)}>{'━'.repeat(full)}</Text>
123              <Text color={C.empty}>{'━'.repeat(WIDTH - full)}</Text>
124              <Text color={shade(s.percent)} bold> {s.percent}%</Text>
125              <Text color={C.label}> {k(s.tokens ?? 0)} / {k(s.window)}</Text>
126            </Box>
127          )}
128        </Box>
129      </Box>
130    )
131  })
132}
133
types/index.d.ts 18 lines
1export type Snapshot = {
2  model: string
3  compactions: number
4  tokens?: number
5  window: number
6  percent?: number
7  session?: number
8  weekly?: number
9  resetsAt?: string
10  usd?: number
11}
12
13declare module 'claude-code' {
14  interface PluginState {
15    'context-bar': { snap: Snapshot | null; compactions: number }
16  }
17}
18