입력창 위에 현재 git 브랜치를 표시한다

Claude Code 입력창 위에 현재 git 레포지토리 이름과 브랜치를 표시하는 mod 예요.
[ im-ground-app-server ] feature/MP-2621
Claude Code 세션 입력창에서 실행해요.
/plugin install branch-band --marketplace izel6446/claude-branch-band
Add marketplace? 에 y, 설치 범위는 user 를 고르면 이후 여는 모든 세션에 로드돼요. 이미 떠 있는 세션은 /reload-plugins 를 실행해요.
리포지토리에 새 커밋을 올린 뒤 각 PC 에서 실행해요.
claude plugin update branch-band@bjh-mods
그다음 세션에서 /reload-plugins 를 실행해요.
| 파일 | 내용 |
|---|---|
.claude-plugin/plugin.json | mod 정보 |
.claude-plugin/marketplace.json | 이 리포지토리를 marketplace 로 쓰기 위한 목록 |
hooks/register.tsx | band 를 그리고 브랜치를 읽는 코드 |
types/index.d.ts | mod 가 쓰는 상태 값의 타입 |
hooks/register.tsx 66 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { BranchBandGit } from '../types'
5
6const git = atom({ plugin: 'branch-band', key: 'git' } as const, null)
7
8// 그리는 중에 git 을 부르면 렌더마다 프로세스가 뜨므로, 브랜치가 바뀔 수 있는 시점에만 다시 읽는다.
9async function refresh($: EngineInterface) {
10 try {
11 const top = await $.process.run(['git', 'rev-parse', '--show-toplevel'], { timeoutMs: 5000 })
12 const head = await $.process.run(['git', 'branch', '--show-current'], { timeoutMs: 5000 })
13 const root = top.exitCode === 0 ? top.stdout.trim() : ''
14 const branch = head.exitCode === 0 ? head.stdout.trim() : ''
15 // 하위 폴더에서 세션을 열어도 레포 이름이 나오도록 cwd 가 아니라 최상위 경로의 마지막 이름을 쓴다.
16 const repo = root.split('/').pop() ?? ''
17 const next: BranchBandGit | null = repo && branch ? { repo, branch } : null
18 await update($, git, () => next)
19 } catch {
20 await update($, git, () => null)
21 }
22}
23
24export const register: Register = on => {
25 on('session.start', async ($, e, next) => {
26 const result = await next(e)
27 await refresh($)
28 return result
29 })
30
31 // 사용자가 터미널에서 직접 checkout 한 경우도 다음 턴이 끝나면 반영된다.
32 on('turn.complete', async ($, e, next) => {
33 const result = await next(e)
34 await refresh($)
35 return result
36 })
37
38 // 도구 결과가 브랜치 조회를 기다리지 않도록 기다리지 않고 넘긴다.
39 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
40 const result = await next(e)
41 refresh($).catch(() => {})
42 return result
43 })
44
45 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
46 const current = await read($, git)
47
48 if (e.props.hasSurvey || current === null) {
49 return next(e)
50 }
51
52 const { Box, Text } = $.ui.resolve(e)
53
54 return (
55 <Box gap={1}>
56 {/* 다크 모드에서 antd 라이트 Tag 색은 너무 밝아서 antd 다크 blue Tag 계열로 맞췄다. */}
57 {/* 테두리를 두면 band 가 세 줄을 차지하고 Box 에는 radius 가 없어서, 둥근 모서리 없이 배경색으로만 구분한다. */}
58 <Box backgroundColor="#1a2f4d" paddingX={1}>
59 <Text color="#69b1ff">{current.repo}</Text>
60 </Box>
61 <Text>{current.branch}</Text>
62 </Box>
63 )
64 })
65}
66types/index.d.ts 8 lines1export type BranchBandGit = { repo: string; branch: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'branch-band': { git: BranchBandGit | null }
6 }
7}
8