SLOPSHOPPER

context-bars

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

newband
v0.1.1no licenseupdated 2026-10-08nalpari/claude-mods/context-bars
A shopper browsing a rack in a slop shop
README

claude-mods

Claude Code mod 모음. 이 저장소 자체가 마켓플레이스(.claude-plugin/marketplace.json)라서 누구나 아래 방법으로 설치할 수 있다.

Mod설명
context-bars컨텍스트 윈도우를 카테고리별 비례 막대 하나로 프롬프트 위에 표시
token-weather컨텍스트 윈도우 사용량을 날씨 아이콘·퍼센트·최근 턴 차트로 프롬프트 위에 표시
y-changeClaude가 코드를 수정할 때마다 diff와 변경 이유(처음 보는 언어의 문법 설명 포함)를 오른쪽 pane에 표시 (기본은 숨김, /y-change로 열기/닫기)
enhance-prompt입력한 프롬프트를 AI가 더 잘 알아듣도록 [THE JOB]/[THE WHY]/[GUARDRAILS]/[DONE MEAN] 구조로 개선해 입력창 위에 보여주고(불명확한 부분은 먼저 입력창 위 질문으로 되묻고 답하면 반복, 최대 3개), [허용]하면 개선본을, [거부]하면 원본을 전송. enhance-prompt: ON/OFF 버튼(데스크탑 입력창 위)을 눌러, 또는 /enhance-prompt로 켜기/끄기
task-progressClaude의 작업 목록(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           # 제거

로컬 checkout 에서 시험

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

참고

  • mod 는 Claude Code 2.1.287 이상에서 기본으로 로드된다 (token-weather/README.md 기준).
  • /plugin install 은 터미널 전용이다. 데스크톱 앱 Code 탭에서는 쓸 수 없지만, 터미널에서 user scope 로 설치하면 로컬 세션에서도 로드된다.
Source 1 files
hooks/register.tsx 119 lines
1import 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