Pane that draws the session's context window as a bar chart, one bar per category

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 119 lines1import type { Register } from 'claude-code'
2
3// legend labels, lower-cased; a category not listed keeps its own name
4const SHORT: Record<string, string> = {
5 'system tools': 'tools',
6 'custom agents': 'agents',
7 'free space': 'free',
8}
9
10// the engine names each row by theme key, and the terminal's dark theme paints System prompt
11// (promptBorder) and System tools (inactive) in near-identical greys; the desktop has its own palette.
12// A category not listed keeps the engine's color.
13const TERMINAL_COLORS: Record<string, string> = {
14 'System prompt': '#5b8db8',
15 'System tools': '#4fb6b2',
16 'MCP tools': '#9d7bf0',
17 'Custom agents': '#7fbf6a',
18 'Memory files': '#e6b655',
19 'Skills': '#f08fb4',
20 'Messages': '#d9644a',
21}
22
23// 4200 -> 4.2k, 52000 -> 52k, 1000000 -> 1M
24const fmt = (n: number) =>
25 n >= 999_500 ? `${+(n / 1e6).toFixed(1)}M`
26 : n >= 1e4 ? `${Math.round(n / 1e3)}k`
27 : n >= 1e3 ? `${+(n / 1e3).toFixed(1)}k`
28 : `${n}`
29
30export const register: Register = on => {
31 // redraw once the window has changed
32 on('turn.complete', ($, e, next) => {
33 $.ui.invalidate('ui.render')
34
35 return next(e)
36 })
37
38 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
39 // the band is one instance: a hook that answers without next hides every other mod's band, so draw above what is beneath
40 const below = await next(e)
41 const { context } = await $.session.usage({ breakdown: 'summary' })
42 const b = context.breakdown
43
44 if (e.props.hasSurvey || !b) return below
45
46 const { Box, Text } = $.ui.resolve(e)
47 // deferred rows sit outside the window and the buffer is drawn as part of the track
48 const used = b.categories.filter(c => c.kind === 'used')
49 .map(c => e.surface === 'terminal' ? { ...c, color: TERMINAL_COLORS[c.name] ?? c.color } : c)
50 // the engine paints Free space promptBorder, System prompt's color too: recolor it so the track reads as empty
51 const track = 'subtle'
52 const found = b.categories.find(c => c.kind === 'free')
53 const free = found && { ...found, color: track }
54 if (used.length === 0) return below
55
56 const total = Math.max(b.rawMaxTokens, b.totalTokens)
57 const pct = (n: number) => Math.round((n / total) * 100)
58 const compact = b.autoCompactThreshold
59 const percent = Math.round(b.percentage)
60
61 // whole-percent widths from cumulative edges, so they never add past 100%
62 let sum = 0
63 const segments = used.map(c => {
64 const from = pct(sum)
65 sum += c.tokens
66
67 return { c, w: pct(sum) - from }
68 }).filter(s => s.w > 0)
69 // the track up to the compaction marker; the flexible one after it takes the rest
70 const gap = compact === undefined ? 0 : Math.floor((compact / total) * 100) - pct(sum)
71
72 const legend = free ? [...used, free] : used
73 const cols = e.props.bodyColumns >= 80 ? 4 : 2
74 const lines = Array.from({ length: Math.ceil(legend.length / cols) }, (_, i) => legend.slice(i * cols, (i + 1) * cols))
75
76 return (
77 <Box flexDirection="column">
78 {below}
79 <Box justifyContent="space-between">
80 <Text wrap="truncate">
81 <Text color="claude">◆</Text> <Text bold>context</Text>
82 </Text>
83 <Text wrap="truncate">
84 <Text bold>{fmt(b.totalTokens)}</Text>
85 <Text dimColor> of {fmt(b.rawMaxTokens)}{compact === undefined ? '' : ' · compacts at '}</Text>
86 {compact === undefined ? null : <Text bold>{fmt(compact)}</Text>}
87 {' '}
88 <Text bold inverse color={percent >= 90 ? 'error' : percent >= 70 ? 'warning' : 'success'}> {percent}% </Text>
89 </Text>
90 </Box>
91 {/* segments are percent-wide Boxes with a background, so the layout fits the band
92 whatever its cell metric */}
93 <Box width="100%" height={1}>
94 {segments.map(({ c, w }) => <Box width={`${w}%`} height={1} backgroundColor={c.color} />)}
95 {gap > 0 ? <Box width={`${gap}%`} height={1} backgroundColor={track} /> : null}
96 {compact === undefined ? null : <Box width={1} height={1} backgroundColor="warning" />}
97 <Box flexGrow={1} height={1} backgroundColor={track} />
98 </Box>
99 {lines.map(line => (
100 <Box>
101 {line.map(c => {
102 const name = c.name.toLowerCase()
103
104 return (
105 <Box width={`${100 / cols}%`}>
106 <Text wrap="truncate">
107 <Text color={c.color}>■</Text> {SHORT[name] ?? name} <Text bold>{fmt(c.tokens)}</Text>
108 {c.kind === 'free' ? null : <Text dimColor> {pct(c.tokens)}%</Text>}
109 </Text>
110 </Box>
111 )
112 })}
113 </Box>
114 ))}
115 </Box>
116 )
117 })
118}
119