SLOPSHOPPER

usage-meters

A compact band above the prompt with context, 5-hour and weekly usage meters.

newband
v1.1.0no licenseupdated 2026-10-13Tvrtk0/claude-code-mods/usage-meters
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-meters
› 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% · 5h ▰▰▰▱▱▱▱▱▱▱ 31% ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
ctx ▰▰▰▰▰▱▱▱▱▱ 49% · 5h ▰▰▰▱▱▱▱▱▱▱ 31% ⟨Claude Code's own drawing⟩
README

local-mods

Personal Claude Code mods for a calmer, more readable terminal.

ModWhat it does
readable-widthWraps replies and prompts at 80 columns; tables and code blocks that name a language keep the full width; an unlabelled or text block wraps like prose. Your prompts are bold and accent-colored, with a blank line above each message.
usage-metersOne band above the prompt: ctx, 5h and 7d meters, green under 50%, yellow under 80%, red above; 5h also shows the time left until it resets.
tool-logFolds tool calls and their results in the chat into a dim › N tools line and keeps them in a side log grouped by prompt. Toggle it with [ panel ], /panel or a key bound to app:toggleDiffPreSession (ctrl+t in ~/.claude/keybindings.json, see below; this replaces the built-in todo list toggle, which the Todo tab covers); a click on a chat line opens it on that prompt's steps, until the next prompt or ✕ returns it to live. The chat line also sums file changes and failures (› 6 tools · 2 files changed +14 −3 · 1 failed). A turn longer than 30 s ends with a desktop notification. Successful Bash rows expand to show their command, and Edit/Write rows to show their diff. Inside a git repo a [ diff ] button opens the built-in /diff panel. The panel has four tabs: Log, Files (every file changed this session with its diff), Agents (subagents with status, time, tool count and their latest step) and Todo (Claude's plan); [ panel ] reopens it on the tab last in view. The active tab is orange, a tab with something new since you last looked is bright, the rest are dim. Replaces the spinner with a minimal one that names the current step and the plan's progress, e.g. ⠋ 12s · 3/7 · Reading register.tsx or ⠙ 3s · Thinking.
turn-detailsReplaces the Brewed for 34s · done 1:09 PM line with 📋 details ▸, which folds the turn's duration, finish time, tool calls and model open in place.

Install

From GitHub:

claude plugin marketplace add Tvrtk0/claude-code-mods
claude plugin install readable-width@local-mods
claude plugin install usage-meters@local-mods
claude plugin install tool-log@local-mods
claude plugin install turn-details@local-mods

From a local clone (edits apply after /reload-plugins, no reinstall):

claude plugin marketplace add ~/.claude/mods
claude plugin install readable-width@local-mods
claude plugin install usage-meters@local-mods
claude plugin install tool-log@local-mods
claude plugin install turn-details@local-mods

Installed from a local clone, plugins are read straight from the folder.

Develop

claude plugin validate ~/.claude/mods/<mod>
claude plugin test ~/.claude/mods/<mod>

Keyboard shortcut for the panel

A mod cannot add a key of its own, so the [ panel ] button borrows an engine action that only the /diff panel handles. Bind any key to it:

{
  "$schema": "https://www.schemastore.org/claude-code-keybindings.json",
  "bindings": [
    { "context": "Global", "bindings": { "ctrl+t": "app:toggleDiffPreSession" } }
  ]
}

ctrl+t hands the panel the keyboard (opening it if needed) and closes it when it already has it. Inside: h/l or 1–4 switch tabs, j/k move between rows (starting from the newest), enter opens one, d/u scroll half a window, g/b jump to the first or last row, esc returns to the prompt and leaves the panel open. The panel scrolls itself, so its tabs and keys stay at the top; Bottom is b, not G: a button hotkey cannot tell shift+g from g.

Source 2 files
hooks/register.tsx 90 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4const snapshot = atom({ plugin: 'usage-meters', key: 'snapshot' } as const, null)
5
6const BAR = 10
7
8const heat = (percent: number) =>
9  percent >= 80 ? 'error' : percent >= 50 ? 'warning' : 'success'
10
11async function refresh($: EngineInterface) {
12  const usage = await $.session.usage()
13  const limit = (kind: string) =>
14    usage.rateLimits.find(r => r.kind === kind)?.percentUsed
15  const resets = usage.rateLimits.find(r => r.kind === 'five_hour')?.resetsAt
16
17  await update($, snapshot, () => ({
18    context: usage.context.percent,
19    fiveHour: limit('five_hour'),
20    fiveHourResetsAt: resets === undefined ? undefined : Date.parse(resets),
21    sevenDay: limit('seven_day'),
22  }))
23}
24
25export const register: Register = on => {
26  on('session.start', async ($, e, next) => {
27    const started = await next(e)
28    await refresh($)
29
30    return started
31  })
32
33  on('turn.complete', async ($, e, next) => {
34    const done = await next(e)
35    await refresh($)
36
37    return done
38  })
39
40  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
41    const s = await read($, snapshot)
42
43    if (e.props.hasSurvey || s === null) {
44      return next(e)
45    }
46
47    const { Box, Text } = $.ui.resolve(e)
48    const now = await $.clock.now()
49    const left = s.fiveHourResetsAt === undefined ? 0 : s.fiveHourResetsAt - now
50    // Time until the 5-hour window resets, as of this drawing.
51    const resetIn =
52      left > 0 ? `${Math.floor(left / 3_600_000)}h ${Math.floor((left % 3_600_000) / 60_000)}m` : ''
53
54    // One labelled meter: a bar of BAR cells and its percentage, both colored
55    // by how full it is.
56    const meter = (label: string, percent: number | undefined, isFirst = false, note = '') => {
57      if (percent === undefined) return null
58
59      const filled = Math.min(BAR, Math.round((percent / 100) * BAR))
60
61      return (
62        <Text>
63          {!isFirst && <Text dimColor>  ·  </Text>}
64          <Text dimColor>{label} </Text>
65          <Text color={heat(percent)}>{'▰'.repeat(filled)}</Text>
66          <Text dimColor>{'▱'.repeat(BAR - filled)}</Text>
67          <Text bold color={heat(percent)}> {Math.round(percent)}%</Text>
68          {note !== '' && <Text dimColor> {note}</Text>}
69        </Text>
70      )
71    }
72
73    // Other mods' band content (the log toggle) is drawn beside this line.
74    const below = await next(e)
75
76    // A blank line above keeps the band off whatever the transcript ends with.
77    return (
78      <Box marginTop={1}>
79        <Box>
80          {meter('ctx', s.context ?? 0, true)}
81          {meter('5h', s.fiveHour, false, resetIn)}
82          {meter('7d', s.sevenDay)}
83        </Box>
84        <Text>   </Text>
85        {below}
86      </Box>
87    )
88  })
89}
90
types/index.d.ts 13 lines
1export type Snapshot = {
2  context?: number
3  fiveHour?: number
4  fiveHourResetsAt?: number
5  sevenDay?: number
6}
7
8declare module 'claude-code' {
9  interface PluginState {
10    'usage-meters': { snapshot: Snapshot | null }
11  }
12}
13