SLOPSHOPPER

usage-meter

Band above the prompt: session tokens, cost, and what remains of the 5-hour and weekly limits.

newpanebandcommandstatus
v0.1.0no licenseupdated 2026-10-08kdahab2000/Social-AI-schedular/mods/usage-meter
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-meter
│ ┃ Usage ✕ › fix the failing auth test and add an audit log call │ ┃ Tokens in: 2.1k │ ┃ Tokens out: 1.5k ⏺ Read(src/auth.ts) │ ┃ Cache read / write: 91.0k / 4.3k ⎿ Read 6 lines │ ┃ Cost: $0.42 ⏺ Update(src/auth.ts) │ ┃ 5h: 69% left resets NaNm ⎿ 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 │ │ › /usage-meter │ ⎿ usage-meter: Tokens in 2.1k · out 1.5k · cache read 91.0k · ca │ ⎿ usage-meter: Cost $0.42 │ ⎿ usage-meter: 5h 69% left (31% used) resets NaNm │ │ tokens 98.9k (in 2.1k · out 1.5k · cache 95.3k) · $0.42 · 5h 69% left resets NaNm ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ usage-meter: tokens 98.9k (in 2.1k · out 1.5k · cache 95.3k) · $0.42 · 5h 69% left resets NaNm

Draws

Band
tokens 98.9k (in 2.1k · out 1.5k · cache 95.3k) · $0.42 · 5h 69% left resets NaNm
Pane · Usage
Tokens in: 2.1k Tokens out: 1.5k Cache read / write: 91.0k / 4.3k Cost: $0.42 5h: 69% left resets NaNm
Source 2 files
hooks/register.tsx 119 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Meter } from '../types'
5
6const empty: Meter = {
7  tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
8  usd: null,
9  limits: [],
10}
11const meter = atom({ plugin: 'usage-meter', key: 'meter' } as const, empty)
12
13const fmt = (n: number) =>
14  n >= 1e6 ? `${(n / 1e6).toFixed(2)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : `${n}`
15
16const until = (iso: string | undefined, now: number) => {
17  if (!iso) return ''
18  const mins = Math.max(0, Math.round((Date.parse(iso) - now) / 60000))
19  const d = Math.floor(mins / 1440)
20  const h = Math.floor((mins % 1440) / 60)
21  const m = mins % 60
22  return d > 0 ? ` resets ${d}d${h}h` : h > 0 ? ` resets ${h}h${m}m` : ` resets ${m}m`
23}
24
25const PANE = 'usage-meter'
26const name = (k: string) => (k === 'five_hour' ? '5h' : k === 'seven_day' ? 'week' : k)
27
28const line = (m: Meter, now: number) => {
29  const t = m.tokens
30  const total = t.input + t.output + t.cacheRead + t.cacheWrite
31  return (
32    `tokens ${fmt(total)} (in ${fmt(t.input)} · out ${fmt(t.output)} · cache ${fmt(t.cacheRead + t.cacheWrite)})` +
33    (m.usd !== null ? ` · $${m.usd.toFixed(2)}` : '') +
34    m.limits
35      .map(l => ` · ${name(l.kind)} ${Math.max(0, 100 - l.percentUsed).toFixed(0)}% left${until(l.resetsAt, now)}`)
36      .join('')
37  )
38}
39
40export const register: Register = on => {
41  on('session.start', async ($, e, next) => {
42    const u = await $.session.usage()
43    const m = await update($, meter, m => ({ ...m, usd: u.cost?.usd ?? null, limits: u.rateLimits }))
44    $.ui.status(line(m, await $.clock.now()))
45    await $.command.register({ name: 'usage-meter', description: 'Show token usage, cost and limits in a pane' })
46    return next(e)
47  })
48
49  on('turn.complete', async ($, e, next) => {
50    const u = e.usage
51    if (u) {
52      const m = await update($, meter, m => ({
53        ...m,
54        tokens: {
55          input: m.tokens.input + u.input_tokens,
56          output: m.tokens.output + u.output_tokens,
57          cacheRead: m.tokens.cacheRead + u.cache_read_input_tokens,
58          cacheWrite: m.tokens.cacheWrite + u.cache_creation_input_tokens,
59        },
60      }))
61      $.ui.status(line(m, await $.clock.now()))
62    }
63    return next(e)
64  })
65
66  on('session.measure', async ($, e, next) => {
67    const m = await update($, meter, m => ({ ...m, usd: e.cost?.usd ?? m.usd, limits: e.rateLimits }))
68    $.ui.status(line(m, await $.clock.now()))
69    return next(e)
70  })
71
72  on('command.run', { command: 'usage-meter' }, async $ => {
73    void $.ui.open({ id: PANE, title: 'Usage' })
74    const m = await read($, meter)
75    const now = await $.clock.now()
76    const t = m.tokens
77    const rows = [
78      `Tokens   in ${fmt(t.input)} · out ${fmt(t.output)} · cache read ${fmt(t.cacheRead)} · cache write ${fmt(t.cacheWrite)}`,
79      `Cost     ${m.usd !== null ? `$${m.usd.toFixed(2)}` : 'n/a'}`,
80      ...(m.limits.length === 0
81        ? ['Limits   no 5h / week data reported yet (needs a subscription plan and one finished turn)']
82        : m.limits.map(l => `${name(l.kind).padEnd(8)} ${Math.max(0, 100 - l.percentUsed).toFixed(0)}% left (${l.percentUsed}% used)${until(l.resetsAt, now)}`)),
83    ]
84    return { text: rows.join('\n') }
85  })
86
87  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
88    const m = await read($, meter)
89    const now = await $.clock.now()
90    const { Box, Text } = $.ui.resolve(e)
91    const t = m.tokens
92    return (
93      <Box flexDirection="column">
94        <Text>Tokens in: {fmt(t.input)}</Text>
95        <Text>Tokens out: {fmt(t.output)}</Text>
96        <Text>Cache read / write: {fmt(t.cacheRead)} / {fmt(t.cacheWrite)}</Text>
97        <Text>Cost: {m.usd !== null ? `$${m.usd.toFixed(2)}` : 'n/a'}</Text>
98        {m.limits.length === 0 && <Text dimColor>No 5h / week limit data (needs a subscription plan).</Text>}
99        {m.limits.map(l => (
100          <Text>
101            {name(l.kind)}: {Math.max(0, 100 - l.percentUsed).toFixed(0)}% left{until(l.resetsAt, now)}
102          </Text>
103        ))}
104      </Box>
105    )
106  })
107
108  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
109    if (e.props.hasSurvey) return next(e)
110    const m = await read($, meter)
111    const { Box, Text } = $.ui.resolve(e)
112    return (
113      <Box>
114        <Text dimColor>{line(m, await $.clock.now())}</Text>
115      </Box>
116    )
117  })
118}
119
types/index.d.ts 10 lines
1export type Tokens = { input: number; output: number; cacheRead: number; cacheWrite: number }
2export type Limit = { kind: string; percentUsed: number; resetsAt?: string }
3export type Meter = { tokens: Tokens; usd: number | null; limits: Limit[] }
4
5declare module 'claude-code' {
6  interface PluginState {
7    'usage-meter': { meter: Meter }
8  }
9}
10