An Oh My Pi style status band: model, cwd, git branch, PR, plan, context use and window.

A garage of Claude Code mods: a plugin marketplace that grows one mod at a time.
# From GitHub
claude plugin marketplace add yi-john-huang/claude-garage
# Or from a local clone
claude plugin marketplace add ~/workspace/claude-garage
claude plugin install omp-statusline@claude-garage
Then run /reload-plugins in a session, or start a new one.
| Mod | What it does |
|---|---|
| omp-statusline | An Oh My Pi style band above the prompt: model, cwd, git branch, PR, (sub), context use and window. Needs git; the PR number needs gh. |
claude plugin validate . # the marketplace
claude plugin validate plugins/omp-statusline # one mod
claude plugin test plugins/omp-statusline
Bump version in both the mod's plugin.json and marketplace.json when releasing a change, so claude plugin update picks it up.
hooks/register.tsx 118 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Git } from '../types'
5
6const git = atom({ plugin: 'omp-statusline', key: 'git' } as const, null)
7// Bumped whenever usage or the model may have moved, so the band redraws.
8const tick = atom({ plugin: 'omp-statusline', key: 'tick' } as const, 0)
9
10const SEP = ' › '
11
12const shortPath = (path: string): string =>
13 path.replace(/^\/(Users|home)\/[^/]+/, '~')
14
15const shortTokens = (n: number): string =>
16 n >= 1_000_000
17 ? `${+(n / 1_000_000).toFixed(1)}M`
18 : n >= 1000
19 ? `${Math.round(n / 1000)}K`
20 : `${n}`
21
22const percentColor = (p: number): string =>
23 p >= 85 ? 'red' : p >= 60 ? 'yellow' : 'magenta'
24
25async function run($: EngineInterface, argv: string[]): Promise<string | null> {
26 try {
27 const r = await $.process.run(argv, { timeoutMs: 5000 })
28 return r.exitCode === 0 ? r.stdout.trim() : null
29 } catch {
30 return null
31 }
32}
33
34// PR lookups hit the network, so only redo one when the branch changes.
35let prFor: string | null = null
36let pr: number | null = null
37
38async function refreshGit($: EngineInterface): Promise<void> {
39 const branch = await run($, ['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
40 if (branch === null) {
41 await update($, git, () => null)
42 return
43 }
44 if (branch !== prFor) {
45 prFor = branch
46 const n = await run($, ['gh', 'pr', 'view', '--json', 'number', '-q', '.number'])
47 pr = n !== null && /^\d+$/.test(n) ? Number(n) : null
48 }
49 const next: Git = { branch, pr }
50 await update($, git, () => next)
51}
52
53async function bump($: EngineInterface): Promise<void> {
54 await update($, tick, n => n + 1)
55}
56
57export const register: Register = on => {
58 on('session.start', async ($, e, next) => {
59 const started = await next(e)
60 void refreshGit($)
61 return started
62 })
63
64 on('turn.complete', async ($, e, next) => {
65 const done = await next(e)
66 if (e.agentId === undefined) {
67 void refreshGit($)
68 await bump($)
69 }
70 return done
71 })
72
73 on('session.measure', async ($, e, next) => {
74 const measured = await next(e)
75 await bump($)
76 return measured
77 })
78
79 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
80 if (e.props.hasSurvey) return next(e)
81
82 await read($, tick)
83 const [model, cwd, usage, repo] = await Promise.all([
84 $.session.model(),
85 $.session.cwd(),
86 $.session.usage(),
87 read($, git),
88 ])
89 const { context, rateLimits } = usage
90 const isSub = rateLimits.length > 0
91
92 const { Box, Text } = $.ui.resolve(e)
93 const sep = <Text dimColor>{SEP}</Text>
94
95 return (
96 <Box flexDirection="row">
97 <Text dimColor>π</Text>
98 {sep}
99 <Text color="magenta">◑ {model}</Text>
100 {sep}
101 <Text color="cyan">📁 {shortPath(cwd)}</Text>
102 {repo && sep}
103 {repo && <Text color="green">⑂ {repo.branch}</Text>}
104 {repo?.pr != null && sep}
105 {repo?.pr != null && <Text color="magenta">↗#{repo.pr}</Text>}
106 {isSub && sep}
107 {isSub && <Text color="magenta">(sub)</Text>}
108 {sep}
109 {context.percent !== undefined && (
110 <Text color={percentColor(context.percent)}>{context.percent}% </Text>
111 )}
112 <Text dimColor>│ </Text>
113 <Text color="magenta">{shortTokens(context.window)}</Text>
114 </Box>
115 )
116 })
117}
118types/index.d.ts 8 lines1export type Git = { branch: string; pr: number | null }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'omp-statusline': { git: Git | null; tick: number }
6 }
7}
8