Usage meters, model, effort, context window and git branch in one line under the prompt

My personal Claude Code mods. Each one is tuned for my own setup and taste: the macOS + Google Chrome stack, a gruvbox palette and the layout of my terminal. They are shared as they are. Use them, fork them, but expect to change them to fit your setup.
One directory per mod.
| Mod | Description |
|---|---|
| yt-music | YT Music in Chrome under the prompt: track, time, play/pause, previous and next |
| session-info | Usage meters, model, effort, context window and git branch in one line under the prompt |
osascript, so Chrome needs "Allow JavaScript from Apple Events" turned on (View → Developer). It reads the first tab open on music.youtube.com.git in the session's working directory. Colors are hard-coded gruvbox./plugin marketplace add cybertron288/claude-mods
/plugin install yt-music@ravi-mods
/plugin install session-info@ravi-modshooks/register.tsx 138 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Info, Meter } from '../types'
5
6const infoAtom = atom({ plugin: 'session-info', key: 'info' } as const, null)
7
8const windowLabel = (tokens: number): string =>
9 tokens >= 1_000_000 ? `${tokens / 1_000_000}M` : `${Math.round(tokens / 1000)}k`
10
11// `five_hour` reads as `5h`, `seven_day` as `7d`; any other kind as its own name.
12const limitLabel = (kind: string): string => {
13 const name = kind.toLowerCase()
14
15 return name.includes('five') ? '5h' : name.includes('seven') || name.includes('week') ? '7d' : name.replace(/_/g, ' ')
16}
17
18const bar = (percent: number, width = 8): string => {
19 const filled = Math.round((percent / 100) * width)
20
21 return '━'.repeat(filled) + '─'.repeat(width - filled)
22}
23
24// Gruvbox, to match the skin.
25const COLORS = { accent: '#fabd2f', warn: '#fe8019', err: '#fb4934', model: '#8ec07c', effort: '#d3869b', window: '#83a598', branch: '#b8bb26', muted: '#928374' }
26
27const meterColor = (percent: number): string => (percent >= 95 ? COLORS.err : percent >= 80 ? COLORS.warn : COLORS.accent)
28
29// The last effort a model request carried; none before the first.
30let effort: string | null = null
31
32// The effort `/effort` set, as the /config menu holds it: for the first turn, before any
33// model request has said which effort it ran at.
34async function configuredEffort($: EngineInterface): Promise<string | null> {
35 const rows = await $.config.list().catch(() => [])
36 const row = rows.find(r => /effort/i.test(r.key) || /effort/i.test(r.label))
37
38 return row === undefined || row.value === null || row.value === undefined || row.value === '' ? null : String(row.value)
39}
40
41async function refresh($: EngineInterface): Promise<void> {
42 const [model, usage, git] = await Promise.all([
43 $.session.model(),
44 $.session.usage(),
45 $.process.run(['git', 'branch', '--show-current']).catch(() => null),
46 ])
47 const branch = git !== null && git.exitCode === 0 ? git.stdout.trim() || null : null
48 const status = branch === null ? null : await $.process.run(['git', 'status', '--porcelain']).catch(() => null)
49 const dirty = status !== null && status.exitCode === 0 && status.stdout.trim() !== ''
50 const meters: Meter[] = [
51 ...(usage.context.percent == null ? [] : [{ label: 'ctx', percent: usage.context.percent }]),
52 ...usage.rateLimits.map(limit => ({ label: limitLabel(limit.kind), percent: limit.percentUsed })),
53 ].map(meter => ({ ...meter, percent: Math.max(0, Math.min(100, Math.round(meter.percent))) }))
54 const next: Info = { model, effort: effort ?? (await configuredEffort($)), window: usage.context.window || null, branch, dirty, meters }
55 const prev = await read($, infoAtom)
56
57 if (JSON.stringify(prev) !== JSON.stringify(next)) {
58 await update($, infoAtom, () => next)
59 }
60}
61
62export const register: Register = on => {
63 on('session.start', async ($, e, next) => {
64 const result = await next(e)
65 await refresh($)
66
67 return result
68 })
69
70 on('turn.step', async function* ($, e, next) {
71 if (e.effort !== undefined) {
72 effort = String(e.effort)
73 }
74
75 return yield* next(e)
76 })
77
78 on('turn.complete', async ($, e, next) => {
79 const result = await next(e)
80 await refresh($)
81
82 return result
83 })
84
85 on('session.measure', async ($, e, next) => {
86 const result = await next(e)
87 await refresh($)
88
89 return result
90 })
91
92 // One line under the prompt hint: usage meters and model on the left, the branch at the right edge.
93 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
94 const info = await read($, infoAtom)
95 const theirs = await next(e)
96
97 if (info === null) {
98 return theirs
99 }
100
101 const { Box, Text } = $.ui.resolve(e)
102 const parts = [
103 { text: info.model, color: COLORS.model },
104 info.effort === null ? null : { text: info.effort, color: COLORS.effort },
105 info.window === null ? null : { text: `${windowLabel(info.window)} ctx`, color: COLORS.window },
106 ].filter((part): part is { text: string; color: string } => part !== null)
107
108 return (
109 <Box flexDirection="column">
110 {theirs}
111 <Box width="100%" justifyContent="space-between">
112 <Box>
113 {info.meters.map(meter => (
114 <Text key={meter.label}>
115 <Text color={COLORS.muted}>{`${meter.label} `}</Text>
116 <Text color={meterColor(meter.percent)}>{bar(meter.percent)}</Text>
117 <Text color={COLORS.muted}>{` ${meter.percent}% `}</Text>
118 </Text>
119 ))}
120 {parts.map(part => (
121 <Text key={part.text}>
122 <Text color={COLORS.muted}>{'· '}</Text>
123 <Text color={part.color}>{`${part.text} `}</Text>
124 </Text>
125 ))}
126 </Box>
127 {info.branch === null ? null : (
128 <Text>
129 <Text color={COLORS.branch}>{`⎇ ${info.branch}`}</Text>
130 {info.dirty ? <Text color={COLORS.warn}>{'*'}</Text> : null}
131 </Text>
132 )}
133 </Box>
134 </Box>
135 )
136 })
137}
138types/index.d.ts 10 lines1export type Meter = { label: string; percent: number }
2
3export type Info = { model: string; effort: string | null; window: number | null; branch: string | null; dirty: boolean; meters: Meter[] }
4
5declare module 'claude-code' {
6 interface PluginState {
7 'session-info': { info: Info | null }
8 }
9}
10