SLOPSHOPPER

git-band

A one-line band above the prompt showing the git branch, changed-file count and ahead/behind, refreshed after every turn.

newbandprocesstimer
A shopper browsing a rack in a slop shop
README

git-band

Branch, dirty count and ahead/behind always visible above the prompt.

Adds one dim line above the prompt with the current git branch, how many files changed, and commits ahead or behind upstream. It refreshes after every turn and every 15 seconds, so you notice at a glance when Claude has modified files or you are on the wrong branch.

Install

/plugin marketplace add cloudcruncher/claude-mod-garage
/plugin install git-band@mod-garage

Run /reload-plugins (or restart Claude Code) afterwards. To try it from a local clone without installing: claude --plugin-dir ./dev-mods/git-band

Use it

  • Automatic once installed. Nothing to run.

What you'll see

Illustrative output.

Above the prompt

⎇ feature/login ● 3 changed ↑1
> _

After committing

⎇ feature/login ✓ clean ↑2
> _

Notes

  • Shows nothing outside a git repository.
Source 2 files
hooks/register.tsx 39 lines
1import type { Register } from 'claude-code'
2import { format, parse, type GitInfo } from './lib'
3
4let info: GitInfo | undefined
5
6async function refresh($: any) {
7  const r = await $.process.run(['git', 'status', '--porcelain=v1', '-b'], { timeoutMs: 5000 }).catch(() => undefined)
8  const next = r && r.exitCode === 0 ? parse(r.stdout) : undefined
9  if (JSON.stringify(next) !== JSON.stringify(info)) {
10    info = next
11    $.ui.invalidate('ui.render')
12  }
13}
14
15export const register: Register = on => {
16  on('session.start', async ($, e, next) => {
17    await refresh($)
18    $.clock.every(15_000, () => refresh($))
19    return next(e)
20  })
21
22  on('turn.complete', async ($, e, next) => {
23    if (!e.agentId) await refresh($)
24    return next(e)
25  })
26
27  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
28    const theirs = await next(e)
29    if (!info || e.props.hasSurvey) return theirs
30    const { Box, Text } = $.ui.resolve(e)
31    return (
32      <Box flexDirection="column">
33        {theirs}
34        <Text dimColor>{format(info)}</Text>
35      </Box>
36    )
37  })
38}
39
hooks/lib.ts 22 lines
1export type GitInfo = { branch: string; changed: number; ahead: number; behind: number }
2
3/** Parses `git status --porcelain=v1 -b` output. */
4export function parse(out: string): GitInfo | undefined {
5  const lines = out.split('\n').filter(Boolean)
6  const head = lines[0]
7  if (!head || !head.startsWith('## ')) return undefined
8  const m = head.slice(3).match(/^(?:No commits yet on |Initial commit on )?([^\s.]+(?:\.[^\s.]+)*?)(?:\.\.\.\S+)?(?: \[(.*)\])?$/)
9  const branch = m?.[1] ?? head.slice(3)
10  const flags = m?.[2] ?? ''
11  return {
12    branch,
13    changed: lines.length - 1,
14    ahead: Number(flags.match(/ahead (\d+)/)?.[1] ?? 0),
15    behind: Number(flags.match(/behind (\d+)/)?.[1] ?? 0),
16  }
17}
18
19export function format(g: GitInfo): string {
20  return `⎇ ${g.branch}` + (g.changed ? ` ● ${g.changed} changed` : ' ✓ clean') + (g.ahead ? ` ↑${g.ahead}` : '') + (g.behind ? ` ↓${g.behind}` : '')
21}
22