Band above the prompt that shows the progress of Claude's task list as a bar

Claude Code mod 모음. 이 저장소 자체가 마켓플레이스(.claude-plugin/marketplace.json)라서 누구나 아래 방법으로 설치할 수 있다.
| Mod | 설명 |
|---|---|
context-bars | 컨텍스트 윈도우를 카테고리별 비례 막대 하나로 프롬프트 위에 표시 |
token-weather | 컨텍스트 윈도우 사용량을 날씨 아이콘·퍼센트·최근 턴 차트로 프롬프트 위에 표시 |
y-change | Claude가 코드를 수정할 때마다 diff와 변경 이유(처음 보는 언어의 문법 설명 포함)를 오른쪽 pane에 표시 (기본은 숨김, /y-change로 열기/닫기) |
enhance-prompt | 입력한 프롬프트를 AI가 더 잘 알아듣도록 [THE JOB]/[THE WHY]/[GUARDRAILS]/[DONE MEAN] 구조로 개선해 입력창 위에 보여주고(불명확한 부분은 먼저 입력창 위 질문으로 되묻고 답하면 반복, 최대 3개), [허용]하면 개선본을, [거부]하면 원본을 전송. enhance-prompt: ON/OFF 버튼(데스크탑 입력창 위)을 눌러, 또는 /enhance-prompt로 켜기/끄기 |
task-progress | Claude의 작업 목록(TodoWrite·TaskCreate/TaskUpdate) 진행률을 막대와 완료/전체, 진행 중인 항목 이름으로 프롬프트 위(스피너 아래)에 표시 |
mod-switch | 설치된 mod 목록을 오른쪽 pane에 토글 스위치와 함께 표시하고, 스위치를 누르면 해당 mod를 켜거나 끔 (기본은 숨김, /mod-switch로 열기/닫기) |
터미널의 Claude Code 세션 프롬프트에서 입력한다. 마켓플레이스 등록과 설치가 한 번에 된다.
/plugin install context-bars --marketplace nalpari/claude-mods
/plugin install token-weather --marketplace nalpari/claude-mods
/plugin install y-change --marketplace nalpari/claude-mods
/plugin install task-progress --marketplace nalpari/claude-mods
/plugin install enhance-prompt --marketplace nalpari/claude-mods
/plugin install mod-switch --marketplace nalpari/claude-mods
처음이면 Add marketplace? 에 y 로 답하고 scope(user 권장)를 고르면 Installed <mod>. Plugin is now active. 가 뜬다. 재시작이나 리로드 없이 바로 적용되고, user scope 는 이후 세션에도 유지된다. 마켓플레이스를 이미 추가했다면 확인 질문은 건너뛴다.
마켓플레이스를 먼저 등록하고 나중에 골라 설치해도 된다.
/plugin marketplace add nalpari/claude-mods
/plugin install context-bars@claude-mods
셸에서는 claude plugin marketplace add nalpari/claude-mods 와 claude plugin install context-bars@claude-mods --scope user 로 같은 일을 한다.
claude plugin marketplace update claude-mods # 마켓플레이스 최신화
claude plugin update context-bars@claude-mods # mod 업데이트
claude plugin list # 설치 확인
claude plugin uninstall context-bars # 제거
git clone https://github.com/nalpari/claude-mods.git
cd claude-mods
# 이번 세션에서만 시험
claude --plugin-dir ./context-bars
# 로컬 마켓플레이스로 설치
claude plugin marketplace add ./
claude plugin install context-bars@claude-mods --scope user
token-weather/README.md 기준)./plugin install 은 터미널 전용이다. 데스크톱 앱 Code 탭에서는 쓸 수 없지만, 터미널에서 user scope 로 설치하면 로컬 세션에서도 로드된다.hooks/register.tsx 103 lines1import type { Register } from 'claude-code'
2
3type Item = { id: string; label: string; status: 'pending' | 'in_progress' | 'completed' }
4
5// widest the bar gets, in cells
6const WIDTH = 20
7
8// the task list as the tools below last left it; TodoWrite and TaskList send the whole list,
9// TaskCreate and TaskUpdate one change. A task made before the mod loaded is unknown to
10// TaskCreate/TaskUpdate until TaskList is called.
11let items: Item[] = []
12
13export const register: Register = on => {
14 on('session.start', ($, e, next) => {
15 items = []
16
17 return next(e)
18 })
19
20 // /clear (or a resume) ends the session and starts the next one without a session.start: the old list is not the new session's
21 on('session.end', ($, e, next) => {
22 items = []
23 $.ui.invalidate('ui.render')
24
25 return next(e)
26 })
27
28 // subagents keep their own lists: only the main loop's calls count
29 on('tool.call', { tool: 'TodoWrite' }, async ($, e, next) => {
30 const ran = await next(e)
31 if (ran.deny !== undefined || ran.isError === true || e.agentId !== undefined) return ran
32
33 items = ran.result.newTodos.map((t, i) => ({ id: String(i), label: t.activeForm, status: t.status }))
34 $.ui.invalidate('ui.render')
35
36 return ran
37 })
38
39 on('tool.call', { tool: 'TaskCreate' }, async ($, e, next) => {
40 const ran = await next(e)
41 if (ran.deny !== undefined || ran.isError === true || e.agentId !== undefined) return ran
42
43 items = [...items, { id: ran.result.task.id, label: e.activeForm ?? ran.result.task.subject, status: 'pending' }]
44 $.ui.invalidate('ui.render')
45
46 return ran
47 })
48
49 on('tool.call', { tool: 'TaskUpdate' }, async ($, e, next) => {
50 const ran = await next(e)
51 if (ran.deny !== undefined || ran.isError === true || e.agentId !== undefined || !ran.result.success) return ran
52
53 const { taskId, status } = e
54 items = status === 'deleted'
55 ? items.filter(i => i.id !== taskId)
56 : items.map(i => i.id === taskId ? { ...i, label: e.activeForm ?? e.subject ?? i.label, status: status ?? i.status } : i)
57 $.ui.invalidate('ui.render')
58
59 return ran
60 })
61
62 on('tool.call', { tool: 'TaskList' }, async ($, e, next) => {
63 const ran = await next(e)
64 if (ran.deny !== undefined || ran.isError === true || e.agentId !== undefined) return ran
65
66 // TaskList has no activeForm: keep the label a known task already has
67 items = ran.result.tasks.map(t => ({ id: t.id, label: items.find(i => i.id === t.id)?.label ?? t.subject, status: t.status }))
68 $.ui.invalidate('ui.render')
69
70 return ran
71 })
72
73 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
74 // the band is one instance: a hook that answers without next hides every other mod's band, so draw above what is beneath
75 const below = await next(e)
76 const done = items.filter(i => i.status === 'completed').length
77 const isFinished = items.length > 0 && done === items.length
78
79 // an empty list shows 0/0; a finished one stays while the turn runs, then goes
80 if (e.props.hasSurvey || (isFinished && !e.props.isWorking)) return below
81
82 const { Box, Text } = $.ui.resolve(e)
83 const active = items.find(i => i.status === 'in_progress')
84 const width = Math.max(5, Math.min(WIDTH, Math.floor(e.props.bodyColumns / 4)))
85 // floor, so the bar is only full when every task is done
86 const filled = items.length === 0 ? 0 : Math.floor((width * done) / items.length)
87
88 return (
89 <Box flexDirection="column">
90 <Text wrap="truncate">
91 <Text color="claude">task-progress</Text>{' '}
92 <Text color="success">{'▰'.repeat(filled)}</Text>
93 <Text dimColor>{'▱'.repeat(width - filled)}</Text>
94 {' '}
95 <Text bold>{`${done}/${items.length}`}</Text>
96 {active && <Text dimColor>{` · ${active.label}…`}</Text>}
97 </Text>
98 {below}
99 </Box>
100 )
101 })
102}
103