SLOPSHOPPER

statusline

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

newbandprocess
★ 1v0.1.0no licenseupdated 2026-10-08Tsukina-7mochi/dotfiles/config/agents/skills/statusline
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · statusline
› 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 [claude-opus-5-5 49% ⣤] / feat/auth-refresh / 5h 31% ⣄ / 7d 0% ⡀ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
[claude-opus-5-5 49% ⣤] / feat/auth-refresh / 5h 31% ⣄ / 7d 0% ⡀
Source 2 files
hooks/register.tsx 105 lines
1import { 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}
105
types/index.d.ts 16 lines
1export 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