Status line: model, effort, git branch, context and rate limits

hooks/register.tsx 105 lines1import { atom, read, update } from 'claude-code'
2import type { Register, SessionRateLimit } from 'claude-code'
3
4import type { Line } from '../types'
5
6const LEVELS = ['⡀', '⣀', '⣄', '⣤', '⣦', '⣶', '⣷', '⣿']
7
8export const braille = (p: number): string =>
9 LEVELS[Math.min(7, Math.max(0, Math.floor((p * 7) / 100)))]
10
11const gauge = (p: number): string => {
12 const n = Math.floor(p)
13 return `${n}% ${braille(n)}`
14}
15
16const limit = (limits: SessionRateLimit[], kind: string): number =>
17 limits.find(l => l.kind === kind)?.percentUsed ?? 0
18
19export const head = (l: Line): string => `[${l.model} ${l.effort ?? ''} ${gauge(l.context)}]`
20
21export const tail = (l: Line): string =>
22 [
23 ...(l.dir || l.branch ? [[l.dir, l.branch].filter(Boolean).join(' ')] : []),
24 `5h ${gauge(l.fiveHour)}`,
25 `7d ${gauge(l.sevenDay)}`,
26 ].join(' / ')
27
28const line = atom({ plugin: 'statusline', key: 'line' } as const, {
29 model: '',
30 context: 0,
31 fiveHour: 0,
32 sevenDay: 0,
33})
34
35export const register: Register = on => {
36 const gitBranch = async (
37 run: (argv: string[]) => Promise<{ exitCode: number; stdout: string }>,
38 ): Promise<string | undefined> => {
39 const { exitCode, stdout } = await run(['git', 'symbolic-ref', '--short', 'HEAD'])
40 return exitCode === 0 ? stdout.trim() : undefined
41 }
42
43 const cwdBasename = async (
44 run: (argv: string[]) => Promise<{ exitCode: number; stdout: string }>,
45 ): Promise<string | undefined> => {
46 const { exitCode, stdout } = await run(['pwd'])
47 return exitCode === 0 ? stdout.trim().split('/').pop() || '/' : undefined
48 }
49
50 on('session.start', async ($, e, next) => {
51 const started = await next(e)
52 const model = await $.session.model()
53 const effort = (await $.settings.read()).effortLevel
54 const usage = await $.session.usage()
55 const branch = await gitBranch(argv => $.process.run(argv))
56 const dir = await cwdBasename(argv => $.process.run(argv))
57 await update($, line, l => ({
58 ...l,
59 model,
60 effort: typeof effort === 'string' ? effort : l.effort,
61 dir,
62 branch,
63 context: usage.context.percent ?? 0,
64 fiveHour: limit(usage.rateLimits, 'five_hour'),
65 sevenDay: limit(usage.rateLimits, 'seven_day'),
66 }))
67 return started
68 })
69
70 on('turn.step', async function* ($, e, next) {
71 if (e.agentId === undefined) {
72 await update($, line, l => ({ ...l, model: e.model, effort: e.effort }))
73 }
74 return yield* next(e)
75 })
76
77 on('session.measure', async ($, e, next) => {
78 const branch = await gitBranch(argv => $.process.run(argv))
79 const dir = await cwdBasename(argv => $.process.run(argv))
80 await update($, line, l => ({
81 ...l,
82 dir,
83 branch,
84 context: e.context.percent ?? 0,
85 fiveHour: limit(e.rateLimits, 'five_hour'),
86 sevenDay: limit(e.rateLimits, 'seven_day'),
87 }))
88 return next(e)
89 })
90
91 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
92 const l = await read($, line)
93 if (e.props.hasSurvey || l.model === '') return next(e)
94
95 const { Box, Text } = $.ui.resolve(e)
96
97 return (
98 <Box>
99 <Text color="claude">{head(l)} </Text>
100 <Text>{tail(l)}</Text>
101 </Box>
102 )
103 })
104}
105types/index.d.ts 16 lines1export type Line = {
2 model: string
3 effort?: string | number
4 dir?: string
5 branch?: string
6 context: number
7 fiveHour: number
8 sevenDay: number
9}
10
11declare module 'claude-code' {
12 interface PluginState {
13 statusline: { line: Line }
14 }
15}
16