SLOPSHOPPER

tool-meter

A live bar above the prompt showing which tools Claude used this session

newbandguardcommandtoaststatus
v0.1.0no licenseupdated 2026-10-05VerbodhDev/tool-meter
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · tool-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 › /tool-meter ⎿ tool-meter: Tool meter is showing above the prompt. Tool meter (this session) [ Reset ] [ Hide ] Bash ████████████ 4 Write ██████ 2 Read ███ 1 Grep ███ 1 Edit ███ 1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ tool-meter: 🛠 Bash · 9 tool calls

Draws

Band
Tool meter (this session) [ Reset ] [ Hide ] Bash ████████████ 4 Write ██████ 2 Read ███ 1 Grep ███ 1 Edit ███ 1
README

tool-meter

A small, visual Claude Code mod: a live bar chart above the prompt showing which tools Claude has used this session.

Tool meter (this session) [ Reset ] [ Hide ]
Bash     ████████████ 6
Read     ██████       3
Edit     ██           1

It also puts the last tool in the status line, pops up a notice every 10 tool calls, and adds a /tool-meter command that brings the chart back after you hide it.

The repo has a second job: it carries a skill that teaches any agent CLI how to build a mod of your own, and a page of use cases for what mods can do.

What a mod is

A mod is a plugin of TypeScript function hooks that runs inside a Claude Code session. It can see every tool call and prompt, block or change them, and draw on the screen: a pane, a band above the prompt, a status line entry, a toast. It reloads when you save it.

Try it

You need Claude Code (written against version 2.1.286; the mod API is early access).

git clone https://github.com/VerbodhDev/tool-meter.git
claude --plugin-dir ./tool-meter

Then ask Claude something that uses tools, for example "list the files here and read one". The chart appears above the prompt after the first tool call.

To check the files without running a session:

claude plugin validate ./tool-meter

Build your own mod

Open Claude Code in this folder and ask for a mod, for example "make a mod that blocks git push --force". The skill in .claude/skills/build-a-mod/ loads and guides it.

With another agent CLI, paste SKILL.md into it and add one line saying what the mod should do. Only Claude Code can run the result.

Reading order

  1. hooks/register.tsx: the mod itself, about 60 lines.
  2. docs/USE-CASES.md: about 25 ideas, grouped by what a mod can do: see, guard, steer, automate, extend.
  3. .claude/skills/build-a-mod/SKILL.md: the step-by-step guide for building one.

Folder map

tool-meter/
├── .claude-plugin/plugin.json        the mod's name, version and type contract
├── hooks/hooks.json                  points Claude Code at the module
├── hooks/register.tsx                the mod
├── types/index.d.ts                  the values the mod keeps in $.state
├── docs/USE-CASES.md                 what mods can do for a team
└── .claude/skills/build-a-mod/       the skill / prompt for building a mod
    └── SKILL.md

Claude Code writes .claude-plugin/types/ and tsconfig.json on each machine when the mod loads. They are gitignored.

Source 2 files
hooks/register.tsx 68 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Counts } from '../types'
5
6const counts = atom({ plugin: 'tool-meter', key: 'counts' } as const, {} as Counts)
7const isHidden = atom({ plugin: 'tool-meter', key: 'isHidden' } as const, false)
8
9const BAR_MAX = 12
10
11export const register: Register = on => {
12  on('session.start', async ($, e, next) => {
13    await $.command.register({ name: 'tool-meter', description: 'Show the tool meter again' })
14
15    return next(e)
16  })
17
18  on('command.run', { command: 'tool-meter' }, async $ => {
19    await update($, isHidden, () => false)
20
21    return { text: 'Tool meter is showing above the prompt.' }
22  })
23
24  on('tool.call', async ($, e, next) => {
25    const tool = e.tool.startsWith('mcp__') ? 'MCP' : e.tool
26    const nextCounts = await update($, counts, c => ({ ...c, [tool]: (c[tool] ?? 0) + 1 }))
27    const total = Object.values(nextCounts).reduce((a, b) => a + b, 0)
28
29    $.ui.status(`🛠 ${tool} · ${total} tool calls`)
30    if (total % 10 === 0) {
31      $.ui.toast(`Claude has used ${total} tools this session`)
32    }
33
34    return next(e)
35  })
36
37  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
38    const c = await read($, counts)
39    const rows = Object.entries(c).sort((a, b) => b[1] - a[1]).slice(0, 5)
40
41    if (e.props.hasSurvey || rows.length === 0 || (await read($, isHidden))) {
42      return next(e)
43    }
44
45    const { Box, Button, Text } = $.ui.resolve(e)
46    const top = rows[0][1]
47
48    return (
49      <Box flexDirection="column">
50        <Box>
51          <Text bold color="cyan">Tool meter </Text>
52          <Text dimColor>(this session) </Text>
53          <Button key="reset" label="Reset" onPress={() => update($, counts, () => ({}))} />
54          <Text> </Text>
55          <Button key="hide" label="Hide" onPress={() => update($, isHidden, () => true)} />
56        </Box>
57        {rows.map(([tool, n]) => (
58          <Box key={tool}>
59            <Text>{tool.padEnd(8)} </Text>
60            <Text color="green">{'█'.repeat(Math.max(1, Math.round((n / top) * BAR_MAX)))}</Text>
61            <Text dimColor> {n}</Text>
62          </Box>
63        ))}
64      </Box>
65    )
66  })
67}
68
types/index.d.ts 8 lines
1export type Counts = Record<string, number>
2
3declare module 'claude-code' {
4  interface PluginState {
5    'tool-meter': { counts: Counts; isHidden: boolean }
6  }
7}
8