Each turn's tokens and the session's running total, in color at the end of the hint line under the prompt.

A Claude Code mod: after each turn, the hint line under the prompt ends with that turn's tokens and the session's running total, in color.
⏵⏵ auto mode on · ↑ 1.2k ↓ 340 cache 4.1k · session ↑ 1.2k ↓ 340
↑ input (blue), ↓ output (green), cache the tokens read from or written to the prompt cache (Claude orange); the session total is dim. It starts over when the session starts.
/plugin marketplace add midudev/better-claude
/plugin install token-meter@better-claude
One hooks module (hooks/register.tsx):
turn.complete keeps the turn's usage and adds it to the session total, in $.state.ui.render hook on PromptHint wraps what the engine draws there (await next(e), so its pills stay live and other plugins still apply) and adds the meter after it.The status line ($.ui.status) only takes plain text, so the meter draws in the hint line to have colors.
claude plugin validate ./token-meter # from the repository root
claude plugin test ./token-meter # runs hooks/register.test.tsx against the engine
MIT © Miguel Ángel Durán (midudev)
hooks/register.tsx 60 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Tokens } from '../types'
5
6const ZERO: Tokens = { input: 0, output: 0, cached: 0 }
7const turn = atom({ plugin: 'token-meter', key: 'turn' } as const, null as Tokens | null)
8const session = atom({ plugin: 'token-meter', key: 'session' } as const, ZERO)
9
10const short = (n: number) => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`)
11
12export const register: Register = on => {
13 on('session.start', async ($, e, next) => {
14 await update($, turn, () => null)
15 await update($, session, () => ZERO)
16 return next(e)
17 })
18
19 on('turn.complete', async ($, e, next) => {
20 const usage = e.usage
21 if (usage) {
22 const last: Tokens = {
23 input: usage.input_tokens,
24 output: usage.output_tokens,
25 cached: usage.cache_read_input_tokens + usage.cache_creation_input_tokens
26 }
27 await update($, turn, () => last)
28 await update($, session, total => ({
29 input: total.input + last.input,
30 output: total.output + last.output,
31 cached: total.cached + last.cached
32 }))
33 }
34 return next(e)
35 })
36
37 // The engine's hint line stays as it is (its pills live); the meter follows it.
38 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
39 const last = await read($, turn)
40 if (last === null) return next(e)
41
42 const total = await read($, session)
43 const { Box, Text } = $.ui.resolve(e)
44
45 return (
46 <Box flexDirection="row">
47 {await next(e)}
48 <Text dimColor> · </Text>
49 <Text color="suggestion">↑</Text>
50 <Text> {short(last.input)} </Text>
51 <Text color="success">↓</Text>
52 <Text> {short(last.output)} </Text>
53 <Text color="claude">cache</Text>
54 <Text> {short(last.cached)}</Text>
55 <Text dimColor> · session ↑ {short(total.input)} ↓ {short(total.output)}</Text>
56 </Box>
57 )
58 })
59}
60types/index.d.ts 9 lines1/** Token counts of one turn, or summed over the session. */
2export type Tokens = { input: number; output: number; cached: number }
3
4declare module 'claude-code' {
5 interface PluginState {
6 'token-meter': { turn: Tokens | null; session: Tokens }
7 }
8}
9