SLOPSHOPPER

usage-meter

Session (5h) and weekly (7d) usage bars under the Claude Code prompt: white, yellow from 75%, red from 90%.

newspinnerstatus
v0.2.0no licenseupdated 2026-10-20FranderCT/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 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts Session ━━────── 31% NaNm

Draws

Prompt hint
? for shortcuts Session ━━────── 31% NaNm
README

usage-meter

Claude Code mod that keeps your session (5h) and weekly (7d) usage always visible, as thin bars on the hint line under the prompt:

▶▶ auto mode on · (shift+tab to cycle)   Session ━━━━━━━─ 18% 3h 19m  ·  Week ━─────── 2% 21h 19m
  • White below 75%, yellow from 75%, red from 90%.
  • After each percentage: time left until that window resets.
  • Updates by itself whenever a window moves one point.

Install

At the prompt of a Claude Code terminal session:

/plugin install usage-meter --marketplace FranderCT/usage-meter

Answer y to add the marketplace, then pick a scope (user to have it in every session). The mod is active right away, no restart needed.

From a shell:

claude plugin install usage-meter --marketplace FranderCT/usage-meter

In the desktop app /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with FranderCT/usage-meter, then Add plugin.

Requirements

  • A Claude subscription (Pro/Max). With an API key there are no usage windows, so the bars stay hidden.
  • Numbers appear after the first response of the session, since they come from the API's last reply.

Customize

Edit hooks/register.tsx:

WhatWhere
Bar lengthBAR_WIDTH
Colors and thresholdscolorFor (white, yellow at 75, red at 90)
LabelsWINDOWS (Session, Week)

Uninstall

/plugin uninstall usage-meter

Notes

The mod replaces the engine's hint line under the prompt (? for shortcuts, esc to interrupt): it repeats the same text first, but the clickable pills on that line stop being clickable while the mod is active.

Source 2 files
hooks/register.tsx 95 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Limit } from '../types'
5
6const limits = atom({ plugin: 'usage-meter', key: 'limits' } as const, [] as Limit[])
7
8const WINDOWS = [
9  { kind: 'five_hour', label: 'Session' },
10  { kind: 'seven_day', label: 'Week' },
11]
12const BAR_WIDTH = 8
13
14const colorFor = (percent: number) => {
15  if (percent >= 90) {
16    return 'red'
17  }
18
19  return percent >= 75 ? 'yellow' : 'white'
20}
21
22const filledCells = (percent: number) =>
23  Math.min(BAR_WIDTH, Math.max(0, Math.round((percent / 100) * BAR_WIDTH)))
24
25const untilReset = (resetsAt: string | undefined, now: number) => {
26  if (resetsAt === undefined) {
27    return ''
28  }
29
30  const minutes = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60000))
31  const days = Math.floor(minutes / 1440)
32  const hours = Math.floor((minutes % 1440) / 60)
33
34  if (days > 0) {
35    return ` ${days}d ${hours}h`
36  }
37
38  return hours > 0 ? ` ${hours}h ${minutes % 60}m` : ` ${minutes}m`
39}
40
41export const register: Register = on => {
42  on('session.start', async ($, e, next) => {
43    $.ui.status(undefined)
44    const usage = await $.session.usage()
45    await update($, limits, () => usage.rateLimits)
46
47    return next(e)
48  })
49
50  on('session.measure', async ($, e, next) => {
51    if (e.changed.includes('rateLimits')) {
52      await update($, limits, () => e.rateLimits)
53    }
54
55    return next(e)
56  })
57
58  on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
59    const current = await read($, limits)
60    const rows = WINDOWS.flatMap(w => {
61      const found = current.find(limit => limit.kind === w.kind)
62
63      return found === undefined ? [] : [{ ...w, ...found }]
64    })
65
66    if (rows.length === 0) {
67      return next(e)
68    }
69
70    const now = await $.clock.now()
71    const { Text } = $.ui.resolve(e)
72
73    return (
74      <Text>
75        <Text dimColor>{e.props.hint === '' ? '' : `${e.props.hint}   `}</Text>
76        {rows.map((row, index) => {
77          const color = colorFor(row.percentUsed)
78          const filled = filledCells(row.percentUsed)
79
80          return (
81            <Text key={row.kind}>
82              {index > 0 ? '  ·  ' : ''}
83              <Text color="white">{row.label} </Text>
84              <Text color={color}>{'━'.repeat(filled)}</Text>
85              <Text dimColor>{'─'.repeat(BAR_WIDTH - filled)}</Text>
86              <Text color={color}> {Math.round(row.percentUsed)}%</Text>
87              <Text dimColor>{untilReset(row.resetsAt, now)}</Text>
88            </Text>
89          )
90        })}
91      </Text>
92    )
93  })
94}
95
types/index.d.ts 8 lines
1export type Limit = { kind: string; percentUsed: number; resetsAt?: string }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'usage-meter': { limits: Limit[] }
6  }
7}
8