SLOPSHOPPER

usage-meter

Band above the prompt: context-window progress bar plus 5h and weekly usage-limit percentages

newbandtimer
v0.1.0no licenseupdated 2026-10-15Ztubben/claude-code-mods/plugins/usage-meter
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-meter
› 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 ctx ██████████░░░░░░░░░░ 49% 97k/200k 5h 31% (NaNm) week – ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
ctx ██████████░░░░░░░░░░ 49% 97k/200k 5h 31% (NaNm) week –
README

claude-code-mods

Mods and other artifacts for Claude Code. Each mod lives in plugins/<name>/; the repository is a plugin marketplace listing them in .claude-plugin/marketplace.json.

Mods

ModWhat it does
usage-meterA band above the prompt: a context-window progress bar, plus the 5h and weekly usage limits as percentages with the time until each resets.
subagent-monitorA pane (/subagents, or opens by itself when a subagent starts) listing each subagent: its status, what it is doing now, how long it has run, and how much context it holds.

Install

At the prompt of a Claude Code terminal session:

/plugin install usage-meter --marketplace Ztubben/claude-code-mods
/plugin install subagent-monitor --marketplace Ztubben/claude-code-mods

Answer y to add the marketplace, then pick a scope (user scope loads it in every session).

From a shell:

claude plugin install usage-meter --marketplace Ztubben/claude-code-mods
claude plugin install subagent-monitor --marketplace Ztubben/claude-code-mods

In the desktop app, where /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with Ztubben/claude-code-mods, then Add plugin.

Source 2 files
hooks/register.tsx 97 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, SessionContextUsage, SessionRateLimit } from 'claude-code'
3
4import type { Limit, Meter } from '../types'
5
6const meter = atom({ plugin: 'usage-meter', key: 'meter' } as const, null)
7// Wall clock as of the last tick: the reset countdowns redraw when it moves.
8const now = atom({ plugin: 'usage-meter', key: 'now' } as const, 0)
9
10const BAR_CELLS = 20
11const TICK_MS = 60_000
12
13const toLimit = (r: SessionRateLimit | undefined): Limit | null =>
14  r === undefined ? null : { percent: r.percentUsed, resetsAt: r.resetsAt ? Date.parse(r.resetsAt) : null }
15
16const toMeter = (context: SessionContextUsage, rateLimits: SessionRateLimit[]): Meter => ({
17  contextPercent: context.percent ?? null,
18  contextTokens: context.tokens ?? null,
19  contextWindow: context.window,
20  fiveHour: toLimit(rateLimits.find(r => r.kind === 'five_hour')),
21  weekly: toLimit(rateLimits.find(r => r.kind === 'seven_day')),
22})
23
24const bar = (percent: number) => {
25  const filled = Math.round((Math.min(Math.max(percent, 0), 100) / 100) * BAR_CELLS)
26  return { filled: '█'.repeat(filled), empty: '░'.repeat(BAR_CELLS - filled) }
27}
28
29const levelColor = (percent: number | null) =>
30  percent === null ? undefined : percent >= 90 ? 'red' : percent >= 70 ? 'yellow' : 'green'
31
32const kTokens = (n: number) => `${Math.round(n / 1000)}k`
33
34// Time left as its two largest units: 3d4h, 2h14m, 9m.
35const until = (resetsAt: number, at: number) => {
36  const minutes = Math.max(0, Math.ceil((resetsAt - at) / 60_000))
37  const d = Math.floor(minutes / 1440)
38  const h = Math.floor((minutes % 1440) / 60)
39  const m = minutes % 60
40  return d > 0 ? `${d}d${h}h` : h > 0 ? `${h}h${m}m` : `${m}m`
41}
42
43const limitText = (limit: Limit | null, at: number) =>
44  limit === null
45    ? '–'
46    : `${Math.round(limit.percent)}%${limit.resetsAt === null ? '' : ` (${until(limit.resetsAt, at)})`}`
47
48const tick = async ($: EngineInterface) => {
49  const t = await $.clock.now()
50  await update($, now, () => t)
51}
52
53export const register: Register = on => {
54  on('session.start', async ($, e, next) => {
55    const result = await next(e)
56    const usage = await $.session.usage()
57    await update($, meter, () => toMeter(usage.context, usage.rateLimits))
58    await tick($)
59    $.clock.every(TICK_MS, () => tick($))
60    return result
61  })
62
63  on('session.measure', async ($, e, next) => {
64    await update($, meter, () => toMeter(e.context, e.rateLimits))
65    await tick($)
66    return next(e)
67  })
68
69  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
70    const m = await read($, meter)
71    if (e.props.hasSurvey || m === null) {
72      return next(e)
73    }
74
75    const at = await read($, now)
76    const { Box, Text } = $.ui.resolve(e)
77    const pct = m.contextPercent
78    const { filled, empty } = bar(pct ?? 0)
79    const tokens = m.contextTokens === null ? '' : ` ${kTokens(m.contextTokens)}/${kTokens(m.contextWindow)}`
80
81    return (
82      <Box>
83        <Text>
84          <Text dimColor>ctx </Text>
85          <Text color={levelColor(pct)}>{filled}</Text>
86          <Text dimColor>{empty}</Text>
87          <Text> {pct === null ? '–' : `${pct}%`}{tokens}</Text>
88          <Text dimColor>  5h </Text>
89          <Text color={levelColor(m.fiveHour?.percent ?? null)}>{limitText(m.fiveHour, at)}</Text>
90          <Text dimColor>  week </Text>
91          <Text color={levelColor(m.weekly?.percent ?? null)}>{limitText(m.weekly, at)}</Text>
92        </Text>
93      </Box>
94    )
95  })
96}
97
types/index.d.ts 16 lines
1export type Limit = { percent: number; resetsAt: number | null }
2
3export type Meter = {
4  contextPercent: number | null
5  contextTokens: number | null
6  contextWindow: number
7  fiveHour: Limit | null
8  weekly: Limit | null
9}
10
11declare module 'claude-code' {
12  interface PluginState {
13    'usage-meter': { meter: Meter | null; now: number }
14  }
15}
16