SLOPSHOPPER

prompt-band

프롬프트 위 띠(AbovePrompt)에 세션 지표를 오른쪽 끝에 붙여 표시: 지금은 마지막 응답의 프롬프트 캐시 적중률. 다른 플러그인의 띠와 위아래로 쌓인다

band
v0.1.0no licenseupdated 2026-10-13DevooKim/my-claude/plugins/prompt-band
A shopper browsing a rack in a slop shop
README

my-claude

개인용 Claude Code 플러그인 마켓플레이스.

설치

Claude Code 프롬프트에서:

/plugin install statusbar --marketplace DevooKim/my-claude
/plugin install prompt-band --marketplace DevooKim/my-claude

마켓플레이스 추가 질문에는 y, 범위는 user를 고르면 됩니다.

플러그인

statusbar

프롬프트 입력창 아래 힌트 줄 밑에 git 줄과 사용량 줄을 그립니다.

my-claude/fix-bar · worktree-fix-bar · !3+2?1⇡1 · PR #12
✻ Opus 5.5  ●●●○○ high · C ━━━━━━━─── 71%  715k/1M · S ━───────── 13%  ↻3h35m   W ━───────── 4%  ↻5d

git 줄은 starship 프롬프트와 같은 기호와 색으로 그려 셸 프롬프트와 똑같이 보입니다.

사용량 줄

  • 모델과 effort: /effort 선택 창과 같은 색, xhigh는 반짝임, max는 흐르는 무지개
  • 컨텍스트 사용률과 토큰
  • 5시간·주간 사용 한도 사용률과 초기화까지 남은 시간, Fable 모델이면 Fable 주간 한도
  • 바 색은 남은 비율에 따라 초록 → 노랑(50% 이하) → 주황(25% 이하) → 빨강(10% 이하, !)

git 줄 (저장소 안에서만)

  • 프로젝트[/워크트리] · 브랜치 · 상태를 ·로 나눠 이어 씀. 경로는 굵은 하늘색(#57C7FF), 브랜치는 굵은 회색, 상태는 노랑
  • 상태 기호(starship git_status와 같음): = 충돌, $ 스태시, × 삭제, » 이름 바꿈, ! 수정, + 스테이징, ? 추적 안 함, ⇡ 앞섬, ⇣ 뒤처짐. 깨끗하고 upstream과 같으면 아무것도 없음
  • HEAD가 떨어지면 브랜치 자리에 (커밋), 리베이스·병합·체리픽·revert·bisect 중이면 브랜치 뒤에 (REBASING)처럼 표시
  • 브랜치 PR 번호(gh 로그인 필요): 누르면 기본 브라우저로 PR 페이지가 열림(전체 화면 모드에서 클릭). 색이 상태: draft 회색, 머지할 수 있지만 승인 전(리뷰 전·CI 진행 중 포함) 주황, 승인까지 끝나 머지 가능 초록, 충돌·CI 실패·변경 요청으로 머지 불가 빨강, 병합됨 보라. Claude Code의 PR 표시와 겹치면 /config의 Show PR status footer를 끄면 됨
  • 파일을 바꾸는 도구가 끝날 때마다, 그리고 30초마다 다시 읽음. PR 상태는 브랜치가 바뀌거나, 푸시·PR 명령 뒤, 또는 다른 터미널의 push·pull로 ⇡⇣가 바뀌면 곧바로, 그 밖에는 3분마다(GitHub가 아직 계산 중이면 다음 git 확인 때)

서브에이전트와 할 일(Task)은 Claude Code가 직접 보여 주므로 따로 그리지 않습니다.

테스트: claude plugin test plugins/statusbar

prompt-band

프롬프트 입력창 바로 위 띠(AbovePrompt)의 오른쪽 끝, 엔진의 접기 버튼 [-] 옆에 세션 지표를 그립니다.

                                                        Cache ◕ 93%  160k/172k  [-]
  • 캐시: 메인 스레드의 마지막 응답에서 입력 토큰 가운데 프롬프트 캐시가 읽어 준 비율과 읽은 양/전체. 서브에이전트 응답은 세지 않음
  • 원 기호는 25% 단위로 내림(○ ◔ ◑ ◕ ●, 꽉 찬 원은 100%만), 숫자는 1% 단위
  • 색: 90% 이상 초록, 70% 이상 노랑, 40% 이상 주황, 그 아래 빨강
  • 첫 응답 전이나 설문이 떠 있을 때는 그리지 않음
  • 띠 자리는 하나라서 아래 플러그인이 그린 띠를 받아 그 위에 쌓음: 다른 띠 플러그인과 함께 켜도 둘 다 보임(그 플러그인도 next(e)를 품어야 함)

테스트: claude plugin test plugins/prompt-band

Source 2 files
hooks/register.tsx 102 lines
1import { atom, read, update } from 'claude-code'
2import type { Register, TurnUsage } from 'claude-code'
3
4import type { PromptBandCache } from '../types'
5
6const cacheAtom = atom({ plugin: 'prompt-band', key: 'cache' } as const, null)
7
8// 글자와 장식은 사용자의 테마(라이트·다크)를 따르는 테마 키로 쓴다.
9const MUTED = 'inactive'
10// 상태 색: statusbar와 같은, 라이트·다크 양쪽에서 검증된 고정 색이다.
11const STATUS_COLORS = {
12  good: '#0ca30c',
13  warning: '#fab219',
14  serious: '#ec835a',
15  critical: '#d03b3b',
16} as const
17// 캐시 적중률을 25% 단위로 내려 원 조각으로 보인다: 꽉 찬 원은 100%일 때만. 숫자는 1% 단위 그대로.
18const PIE = ['○', '◔', '◑', '◕', '●']
19
20export type SpanStyle = { color?: string; bold?: boolean }
21export type Span = { text: string; style?: SpanStyle }
22
23export const register: Register = on => {
24  // 메인 스레드의 응답 스트림은 그대로 흘려보내고, 응답이 끝나는 stop 조각에서 캐시 사용량만 읽는다.
25  on('turn.step', async function* ($, e, next) {
26    const stream = next(e)
27    let step = await stream.next()
28    while (!step.done) {
29      if (e.agentId === undefined && step.value.kind === 'stop') {
30        const cache = cacheOf(step.value.usage)
31        if (cache !== undefined) await update($, cacheAtom, () => cache)
32      }
33      yield step.value
34      step = await stream.next()
35    }
36
37    return step.value
38  })
39
40  // 띠 자리는 하나라서, 아래 플러그인(또는 엔진)이 그린 것을 받아 그 위에 이 줄들을 쌓는다.
41  // 설문이 떠 있거나 보일 값이 없으면 손대지 않고 넘긴다.
42  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
43    if (e.props.hasSurvey) return next(e)
44    const cache = await read($, cacheAtom)
45    const rows = cache === null ? [] : [cacheRow(cache)]
46    if (rows.length === 0) return next(e)
47    const below = await next(e)
48    const { Box, Text } = $.ui.resolve(e)
49    const draw = (part: Span) =>
50      part.style === undefined ? part.text : <Text {...part.style}>{part.text}</Text>
51
52    // 줄마다 띠의 본문 너비(엔진의 [-] 자리를 뺀 칸)만큼 넓혀 오른쪽 끝에 붙인다.
53    return (
54      <Box flexDirection="column">
55        {rows.map(row => (
56          <Box flexDirection="row" justifyContent="flex-end" width={e.props.bodyColumns}>
57            <Text wrap="truncate-end">{row.map(draw)}</Text>
58          </Box>
59        ))}
60        {below}
61      </Box>
62    )
63  })
64}
65
66export function cacheOf(usage: TurnUsage | null): PromptBandCache | undefined {
67  if (usage === null) return undefined
68  const read = usage.cache_read_input_tokens
69  const total = usage.input_tokens + read + usage.cache_creation_input_tokens
70
71  return total > 0 ? { read, total } : undefined
72}
73
74export function cacheRow({ read, total }: PromptBandCache): Span[] {
75  const hit = Math.round((read / total) * 100)
76  const color = STATUS_COLORS[cacheStatusOf(hit)]
77  const pie = PIE[Math.floor((hit / 100) * (PIE.length - 1))] ?? PIE[0]
78
79  return [
80    { text: 'Cache ', style: { color: MUTED } },
81    { text: pie, style: { color } },
82    { text: ` ${hit}%`, style: { color, bold: true } },
83    { text: `  ${formatTokens(read)}/${formatTokens(total)}`, style: { color: MUTED } },
84  ]
85}
86
87// 캐시 적중률의 상태 단계. 이어지는 대화는 보통 90%를 넘으므로 높게 끊는다.
88export function cacheStatusOf(hit: number): keyof typeof STATUS_COLORS {
89  if (hit >= 90) return 'good'
90  if (hit >= 70) return 'warning'
91  if (hit >= 40) return 'serious'
92
93  return 'critical'
94}
95
96export function formatTokens(count: number): string {
97  if (count >= 999_500) return `${(count / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
98  if (count >= 1000) return `${Math.round(count / 1000)}k`
99
100  return String(count)
101}
102
types/index.d.ts 9 lines
1// 마지막 응답의 입력 토큰 가운데 프롬프트 캐시가 읽어 준 몫: read가 total 중 얼마.
2export type PromptBandCache = { read: number; total: number }
3
4declare module 'claude-code' {
5  interface PluginState {
6    'prompt-band': { cache: PromptBandCache | null }
7  }
8}
9