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

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.
| Mod | What it does |
|---|---|
usage-meter | A 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-monitor | A 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. |
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.
hooks/register.tsx 97 lines1import { 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}
97types/index.d.ts 16 lines1export 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