SLOPSHOPPER

usage-meter

Shows the 5-hour and 7-day usage limits in the status line and warns at 80% and 95%

newtoaststatustimer
v0.1.0no licenseupdated 2026-10-09RenaudLavoisier/claude-code-mods/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 ⚠ usage-meter: limits 5h 31%
README

claude-code-mods

Five mods for Claude Code that show what is happening in a session without getting in your way:

ModWhere it showsWhat it does
Tokachu (context-pet)Band above the promptA pet that grows and turns from green to red as the context fills up
usage-meterStatus line under the promptYour 5-hour and 7-day usage limits, with warnings at 80% and 95%
turn-notifyDesktop notificationTells you when a turn of one minute or more is done
changed-filesSide paneThe files that differ from HEAD, as git diff shows them, with added and removed lines
pr-checksSide paneThe GitHub checks of the current branch's pull request: failed, running, passed

The mods work together: each one uses a different part of the screen, and the two panes show as tabs of the side pane. You can install all of them or only the ones you want.

Installation

You need a Claude Code version that supports mods (plugins with function hooks). The mods are tested with Claude Code 2.1.295.

Add the marketplace, then install the mods you want:

claude plugin marketplace add RenaudLavoisier/claude-code-mods

claude plugin install context-pet@claude-code-mods --scope user
claude plugin install usage-meter@claude-code-mods --scope user
claude plugin install turn-notify@claude-code-mods --scope user
claude plugin install changed-files@claude-code-mods --scope user
claude plugin install pr-checks@claude-code-mods --scope user

With --scope user, the mods load in every Claude Code session. In a session that is already open, run /reload-plugins to load them.

To get a new version:

claude plugin marketplace update claude-code-mods
claude plugin update context-pet@claude-code-mods   # same for the other mods

To remove a mod:

claude plugin uninstall context-pet@claude-code-mods

Tokachu (context pet)

Tokachu lives above the prompt and eats your context. The more of the context window the conversation uses, the bigger and redder Tokachu gets.

 /\___/\           Tokachu · 10% of context
(  ^_^  )          20k / 200k tokens
 \_____/           feeling great

 /\_____________/\   Tokachu · 70% of context
(       O_O       )  140k / 200k tokens
(                 )  bloated
(                 )
(                 )
 \_______________/
  • Size: the body grows from 5 to 21 cells wide and gets up to 5 rows of belly. It never takes more rows than the space above the prompt allows.
  • Color: a smooth gradient from green (#20df20) at 0%, through yellow (#dfdf20) at 50%, to red (#df2020) at 100%.
  • Face and mood:
Context usedFaceMood
Less than 40%^_^feeling great
40% to 59%o_owell fed
60% to 79%O_Obloated
80% to 89%>_<about to burst
90% or morex_xabout to explode, try /compact

While Claude works, the mood line shows nom nom….

  • Updates: after each API response, not only at the end of the turn. Subagents have their own context, so their requests do not feed Tokachu.
  • Alert: when the context goes past 80%, a toast says Tokachu ate too much: N% of context.
  • /clear: Tokachu goes back to its small, green size.
  • /pet: hides or shows Tokachu (Tokachu is taking a nap. / Tokachu is back.).

usage-meter

Shows your plan's usage limits in the status line under the prompt:

limits 5h 42% (resets in 2h10) · 7d 82% (resets in 3d4h) ⚠
  • Windows: 5h (five-hour limit), 7d (seven-day limit) and spend (spend limit), as Claude Code reports them.
  • Countdown: the time until each window resets. It refreshes every minute, also when Claude is idle.
  • 80%: the window gets a ⚠ and a toast says, for example, 5h usage limit at 81%.
  • 95%: a toast and a desktop notification.
  • Each alert fires once per level. When the usage goes down again (for example, after a reset), the alerts are ready to fire again.
  • The line appears only on plans with usage limits. It can stay empty until the first API response of the session.

turn-notify

Sends a desktop notification when a turn of one minute or more ends, so you can do something else while Claude works.

Claude Code
Done in 2m14s: The migration is ready and all tests pass.
  • Message: Done in <duration>: <first line of the answer>. The first line is cleaned of Markdown marks and cut at 120 characters. If the turn fails, the message is Turn failed after <duration> (or Turn refused after <duration>).
  • Skipped: turns that you cancel, turns shorter than one minute, and subagent turns.
  • Channel: the notification uses the channel set in /config (Notifications). Your own Notification hooks also receive it.
  • When Claude waits for a permission, Claude Code already sends its own notification, so this mod does not send a second one.

changed-files

A side pane that lists the tracked files that differ from HEAD in the session's git repository, as git diff and git status show them:

+12 -3 app/src/core/tasks.py
+40 -0 app/tests/test_tasks.py added
+0 -25 app/legacy.py deleted
bin logo.png

4 files · +52 -28 · press a file to mention it
  • What it lists: the staged and unstaged changes against HEAD (git diff HEAD). Untracked files are not listed: git add a new file to see it. The list shows every change in the repository, whoever made it: Claude, you in your editor, or a shell command.
  • Counts: +N lines added and -N lines removed, then added or deleted when the file is not only modified. bin marks a binary file. Paths are relative to the session's directory.
  • Updates: right after Claude runs Edit, Write, NotebookEdit or Bash, when you type /changes, and every 5 seconds while the pane is open. Git runs with --no-optional-locks, so these reads never block your own git commands.
  • Opening: the pane opens once per session, the first time Claude changes a file while the repository has changes, if the terminal is 144 columns wide or more. In a narrower terminal, a message tells you to type /changes, which opens the pane at any width.
  • Mention a file: press a file to insert @path in the prompt.
  • Outside a git repository: the pane says Not in a git repository.

pr-checks

A side pane that shows the GitHub checks of the pull request of the current git branch:

PR #3021
feat(core): Log caller and force flags of task API launches
✓ 14 ✗ 1 ● 2 ○ 2

✗ Run linters
● Run unit tests
● Run functional tests
✓ Run Sonarqube analysis
…

↻ refresh
  • Requirements: the GitHub CLI (gh), logged in with gh auth login. The mod runs git branch --show-current, gh pr view and gh pr checks.
  • What it lists: every check, failed (✗) and cancelled (⊘) first, then running (●), passed (✓) and skipped (○). PR #N and each check name are links to GitHub.
  • Updates: when the session starts, at the end of each turn, and when you press ↻ refresh. In between, every 30 seconds while a check runs or after you change branch, else every 2 minutes.
  • Toast: when the running checks are done, a toast says PR #N: all checks passed or PR #N: N checks failed.
  • Opening: the pane opens once per session, the first time the branch has a pull request, if the terminal is 144 columns wide or more. In a narrower terminal, a message tells you to type /pr-checks, which opens the pane at any width.
  • /pr-checks: opens the pane and lists every check with its link in the conversation, so you can ask Claude to fix a failing one.
  • No pull request: the pane says No pull request for branch <branch>. If gh fails, the pane shows the error.

Development

Each mod is a plugin folder:

context-pet/
├── .claude-plugin/plugin.json   # name, version, description
├── hooks/hooks.json             # points to register.tsx
├── hooks/register.tsx           # the hooks
├── hooks/*.ts                   # pure helpers
├── types/index.d.ts             # the mod's state (when it has one)
└── tests/*.test.ts

To work on the mods, clone the repository and add the clone as a local marketplace. Claude Code reads a local marketplace in place, so /reload-plugins loads your changes without a reinstall:

git clone git@github.com:RenaudLavoisier/claude-code-mods.git
claude plugin marketplace add ./claude-code-mods
claude plugin install context-pet@claude-code-mods --scope user

Check and test a mod:

claude plugin validate ./context-pet
claude plugin test ./context-pet

Before you push a change, increase version in the mod's plugin.json, so that claude plugin update finds the new version.

Source 3 files
hooks/register.tsx 59 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, SessionRateLimit } from 'claude-code'
3
4import type { UsageWindow } from '../types'
5import { labelOf, levelOf, statusLine } from './meter'
6
7const windows = atom({ plugin: 'usage-meter', key: 'windows' } as const, [])
8const alerted = atom({ plugin: 'usage-meter', key: 'alerted' } as const, {})
9
10async function show($: EngineInterface) {
11  $.ui.status(statusLine(await read($, windows), await $.clock.now()))
12}
13
14async function remember($: EngineInterface, limits: readonly SessionRateLimit[]) {
15  const list: UsageWindow[] = limits.map(({ kind, percentUsed, resetsAt }) => ({
16    kind,
17    percentUsed,
18    resetsAt,
19  }))
20  await update($, windows, () => list)
21
22  const before = await read($, alerted)
23  const after: Record<string, number> = {}
24  for (const window of list) {
25    const level = levelOf(window.percentUsed)
26    after[window.kind] = level
27
28    if (level > (before[window.kind] ?? 0)) {
29      const text = `${labelOf(window.kind)} usage limit at ${Math.round(window.percentUsed)}%`
30      $.ui.toast(text, { timeoutMs: 8000 })
31      if (level >= 95) {
32        await $.ui.notify(text, { title: 'Claude Code usage' }).catch(() => undefined)
33      }
34    }
35  }
36  await update($, alerted, () => after)
37
38  await show($)
39}
40
41export const register: Register = on => {
42  on('session.start', async ($, e, next) => {
43    const { rateLimits } = await $.session.usage()
44    await remember($, rateLimits)
45    // Keeps the "resets in" countdown fresh between measures.
46    $.clock.every(60_000, () => void show($))
47
48    return next(e)
49  })
50
51  on('session.measure', async ($, e, next) => {
52    if (e.changed.includes('rateLimits')) {
53      await remember($, e.rateLimits)
54    }
55
56    return next(e)
57  })
58}
59
hooks/meter.ts 37 lines
1import type { UsageWindow } from '../types'
2
3export const LEVELS = [80, 95] as const
4
5const LABELS: Record<string, string> = { five_hour: '5h', seven_day: '7d', spend_limit: 'spend' }
6
7export const labelOf = (kind: string) => LABELS[kind] ?? kind
8
9export function formatIn(ms: number): string {
10  const minutes = Math.max(0, Math.round(ms / 60_000))
11  if (minutes < 60) return `${minutes}m`
12
13  const hours = Math.floor(minutes / 60)
14  if (hours < 24) return `${hours}h${String(minutes % 60).padStart(2, '0')}`
15
16  return `${Math.floor(hours / 24)}d${hours % 24}h`
17}
18
19// Highest alert level the percentage has reached, 0 below the first one.
20export function levelOf(percent: number): number {
21  return LEVELS.filter(level => percent >= level).at(-1) ?? 0
22}
23
24export function statusLine(windows: readonly UsageWindow[], now: number): string | undefined {
25  if (windows.length === 0) return undefined
26
27  const parts = windows.map(window => {
28    const resetsAt = window.resetsAt === undefined ? NaN : Date.parse(window.resetsAt)
29    const resets = Number.isNaN(resetsAt) ? '' : ` (resets in ${formatIn(resetsAt - now)})`
30    const warning = window.percentUsed >= LEVELS[0] ? ' ⚠' : ''
31
32    return `${labelOf(window.kind)} ${Math.round(window.percentUsed)}%${resets}${warning}`
33  })
34
35  return `limits ${parts.join(' · ')}`
36}
37
types/index.d.ts 8 lines
1export type UsageWindow = { kind: string; percentUsed: number; resetsAt?: string }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'usage-meter': { windows: UsageWindow[]; alerted: Record<string, number> }
6  }
7}
8