SLOPSHOPPER

usage-band

Context window, 5h and weekly usage bars above the prompt

newband
v0.1.0no licenseupdated 2026-10-08joaopfusco/dotfiles/claude/.claude/mods/usage-band
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-band
› 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 ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ context ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 49% 97k / 200k │ │ 5h ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 31% resets in NaNm │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ context ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 49% 97k / 200k │ │ 5h ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 31% resets in NaNm │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
README

dotfiles

Personal dotfiles managed with GNU Stow. Each top-level directory is a package whose contents mirror $HOME.

Prerequisites

  • Homebrew and stow (brew install stow)
  • Git configured, with an SSH key registered on GitHub
ssh-keygen -t ed25519 -C "joaopedrofusco@gmail.com"
cat ~/.ssh/id_ed25519.pub # copy this to GitHub
git clone git@github.com:joaopfusco/dotfiles.git ~/dotfiles

Initial setup

Install the apps, then link every package before opening them, so they don't create default configs that conflict with the links:

cd ~/dotfiles
stow */

Day to day

Configs under $HOME are symlinks into this repo, so just edit the files here.

After adding a file to an existing package, or a new package (a directory mirroring $HOME):

stow -R */
Source 2 files
hooks/register.tsx 113 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { ContextSnapshot, LimitWindow } from '../types'
5
6const context = atom({ plugin: 'usage-band', key: 'context' } as const, null)
7const limits = atom({ plugin: 'usage-band', key: 'limits' } as const, [])
8
9const TOKYO_NIGHT = {
10  blue: '#7aa2f7',
11  magenta: '#bb9af7',
12  red: '#f7768e',
13  fg: '#a9b1d6',
14  track: '#414868',
15}
16
17const LIMIT_STYLE: Record<string, { label: string; color: string }> = {
18  five_hour: { label: '5h', color: TOKYO_NIGHT.blue },
19  seven_day: { label: 'week', color: TOKYO_NIGHT.magenta },
20}
21
22const LABEL_WIDTH = 8
23const META_WIDTH = 24
24
25export const formatTokens = (tokens: number): string => {
26  if (tokens >= 1_000_000) return `${+(tokens / 1_000_000).toFixed(1)}M`
27  if (tokens >= 10_000) return `${Math.round(tokens / 1000)}k`
28  if (tokens >= 1000) return `${+(tokens / 1000).toFixed(1)}k`
29  return `${tokens}`
30}
31
32export const formatResetIn = (resetsAt: string | undefined, now: number): string => {
33  if (!resetsAt) return ''
34  const minutes = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60_000))
35  const days = Math.floor(minutes / 1440)
36  const hours = Math.floor((minutes % 1440) / 60)
37  if (days > 0) return `resets in ${days}d ${hours}h`
38  if (hours > 0) return `resets in ${hours}h ${minutes % 60}m`
39  return `resets in ${minutes}m`
40}
41
42export const filledCells = (percent: number, width: number): number =>
43  Math.min(width, Math.max(0, Math.round((percent / 100) * width)))
44
45const refresh = async ($: EngineInterface): Promise<void> => {
46  const usage = await $.session.usage()
47  const windows: LimitWindow[] = usage.rateLimits.map(({ kind, percentUsed, resetsAt }) => ({ kind, percentUsed, resetsAt }))
48  await update($, limits, () => windows)
49
50  const { tokens, window, percent } = usage.context
51  if (tokens === undefined || percent === undefined) return
52  const snapshot: ContextSnapshot = { tokens, window, percent }
53  await update($, context, () => snapshot)
54}
55
56export const register: Register = on => {
57  on('session.start', async ($, e, next) => {
58    const result = await next(e)
59    await refresh($)
60    return result
61  })
62
63  on('turn.complete', async ($, e, next) => {
64    const result = await next(e)
65    await refresh($)
66    return result
67  })
68
69  on('session.compact', async ($, e, next) => {
70    const result = await next(e)
71    await refresh($)
72    return result
73  })
74
75  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
76    if (e.props.hasSurvey || e.props.view.agentId) return next(e)
77
78    const snapshot = await read($, context)
79    const windows = await read($, limits)
80    if (snapshot === null && windows.length === 0) return next(e)
81
82    const { Box, Text } = $.ui.resolve(e)
83    const now = await $.clock.now()
84    const barWidth = Math.max(10, e.props.bodyColumns - LABEL_WIDTH - META_WIDTH - 6)
85
86    const row = (key: string, label: string, color: string, percent: number, meta: string) => {
87      const filled = filledCells(percent, barWidth)
88      const isHigh = percent >= 80
89      return (
90        <Text key={key}>
91          <Text color={TOKYO_NIGHT.fg}>{label.padEnd(LABEL_WIDTH)}</Text>
92          <Text color={isHigh ? TOKYO_NIGHT.red : color}>{'▆'.repeat(filled)}</Text>
93          <Text color={TOKYO_NIGHT.track}>{'▆'.repeat(barWidth - filled)}</Text>
94          <Text bold color={isHigh ? TOKYO_NIGHT.red : color}>{` ${Math.round(percent)}%`.padStart(5)}</Text>
95          <Text dimColor>{`  ${meta}`}</Text>
96        </Text>
97      )
98    }
99
100    return (
101      <Box flexDirection="column" borderStyle="round" borderColor={TOKYO_NIGHT.track} paddingX={1}>
102        {snapshot
103          ? row('context', 'context', 'claude', snapshot.percent, `${formatTokens(snapshot.tokens)} / ${formatTokens(snapshot.window)}`)
104          : null}
105        {windows.map(w => {
106          const style = LIMIT_STYLE[w.kind] ?? { label: w.kind, color: TOKYO_NIGHT.blue }
107          return row(w.kind, style.label, style.color, w.percentUsed, formatResetIn(w.resetsAt, now))
108        })}
109      </Box>
110    )
111  })
112}
113
types/index.d.ts 13 lines
1export type ContextSnapshot = { tokens: number; window: number; percent: number }
2
3export type LimitWindow = { kind: string; percentUsed: number; resetsAt?: string }
4
5declare module 'claude-code' {
6  interface PluginState {
7    'usage-band': {
8      context: ContextSnapshot | null
9      limits: LimitWindow[]
10    }
11  }
12}
13