Claude Code v2.1.80에서 statusline JSON에 rate_limits 필드가 추가되면서 5시간/7일 사용량을 실시간으로 표시할 수 있게 된 내용을 소개하고, 이를 표시하기 위한 5가지 비주얼 패턴의 Python 스크립트를 정리한다.
안녕하세요! 역세가와 (@gyakuse) 입니다!
Claude Code v2.1.80에서 기다리고 기다리던 스테이터스라인용 rate_limits 필드가 추가되었습니다. 이것이 없었기 때문에 정말 다들 버텨 왔던 겁니다. 정말 괴로운 세계였습니다.
2026년 3월 19일 릴리스된 v2.1.80에서, 스테이터스라인에 전달되는 JSON에 rate_limits 필드가 추가되었습니다.
changelog의 기재는 다음과 같습니다.
Added rate_limits field to statusline scripts for displaying Claude.ai rate limit usage (5-hour and 7-day windows with used_percentage and resets_at)
이로써 Claude Code의 구독 사용량(5시간 윈도우와 7일 윈도우)을 스테이터스라인에 실시간으로 표시할 수 있게 되었습니다.
Claude Code의 스테이터스라인은 단순한 구조로 동작합니다.
이번에 추가된 rate_limits 필드는 이런 구조입니다.
{
"rate_limits": {
"five_hour": {
"used_percentage": 42.3,
"resets_at": "2026-03-20T15:00:00Z"
},
"seven_day": {
"used_percentage": 85.7,
"resets_at": "2026-03-24T00:00:00Z"
}
}
}
used_percentage가 사용률, resets_at이 리셋 시각입니다. 기존의 context_window.used_percentage(컨텍스트 윈도우 사용률)와 함께 표시하면, 남은 리소스를 한눈에 파악할 수 있습니다.
~/.claude/settings.json에 statusLine을 추가하기만 하면 됩니다.
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.py"
}
}
스크립트는 chmod +x로 실행 권한을 부여해 둘 필요가 있습니다.
기왕이니 몇 가지 패턴을 만들어 봤습니다. 모든 패턴에서 아래 정보를 표시합니다.
색상은 모두 TrueColor 그라데이션이며, 사용률에 따라 초록→노랑→빨강으로 연속적으로 변화합니다.
컬러 도트와 숫자만 있는 미니멀 스타일입니다.

정보 밀도는 낮지만 깔끔하고, 스테이터스바의 공간을 많이 차지하지 않습니다.
블록 요소를 사용한 스파크라인 스타일입니다.

세로 방향 높이로 사용률을 표현하므로, 가로 폭을 줄일 수 있습니다.
원그래프 풍 아이콘을 사용한 스타일입니다.

가장 컴팩트한 패턴입니다. 5단계의 거친 표현이지만, 색상과 조합하면 충분히 실용적입니다.
1% 정밀도의 세밀한 프로그레스바입니다.

가장 정보량이 많고 시각적으로도 돋보이는 패턴입니다.
점자 패턴을 사용한 도트 스타일입니다.

도트의 밀도로 사용률을 표현합니다. 레트로한 분위기가 있어서 개인적으로는 이걸 사용하고 있습니다.
rate_limits 필드가 추가되어 5시간/7일 사용량을 스테이터스라인에 표시할 수 있게 되었다전부 Python 스크립트입니다. ~/.claude/statusline.py로 저장하고, chmod +x로 실행 권한을 부여해 주세요.
#!/usr/bin/env python3
"""Pattern 1: Minimal dots - colored circles with numbers only"""
import json, sys
data = json.load(sys.stdin)
R = '\033[0m'
DIM = '\033[2m'
BOLD = '\033[1m'
def gradient(pct):
if pct < 50:
r = int(pct * 5.1)
return f'\033[38;2;{r};200;80m'
else:
g = int(200 - (pct - 50) * 4)
return f'\033[38;2;255;{max(g, 0)};60m'
def dot(pct):
p = round(pct)
return f'{gradient(pct)}●{R} {BOLD}{p}%{R}'
model = data.get('model', {}).get('display_name', 'Claude')
parts = [f'{BOLD}{model}{R}']
ctx = data.get('context_window', {}).get('used_percentage')
if ctx is not None:
parts.append(f'ctx {dot(ctx)}')
five = data.get('rate_limits', {}).get('five_hour', {}).get('used_percentage')
if five is not None:
parts.append(f'5h {dot(five)}')
week = data.get('rate_limits', {}).get('seven_day', {}).get('used_percentage')
if week is not None:
parts.append(f'7d {dot(week)}')
print(f' {DIM}·{R} '.join(parts), end='')
#!/usr/bin/env python3
"""Pattern 2: Sparkline gauge - vertical block characters"""
import json, sys
data = json.load(sys.stdin)
SPARKS = ' ▁▂▃▄▅▆▇█'
R = '\033[0m'
DIM = '\033[2m'
def gradient(pct):
if pct < 50:
r = int(pct * 5.1)
return f'\033[38;2;{r};200;80m'
else:
g = int(200 - (pct - 50) * 4)
return f'\033[38;2;255;{max(g, 0)};60m'
def spark_gauge(pct, width=8):
pct = min(max(pct, 0), 100)
level = pct / 100
gauge = ''
for i in range(width):
seg_start = i / width
seg_end = (i + 1) / width
if level >= seg_end:
gauge += SPARKS[8]
elif level <= seg_start:
gauge += SPARKS[0]
else:
frac = (level - seg_start) / (seg_end - seg_start)
gauge += SPARKS[int(frac * 8)]
return gauge
def fmt(label, pct):
p = round(pct)
return f'{DIM}{label}{R} {gradient(pct)}{spark_gauge(pct)}{R} {p}%'
model = data.get('model', {}).get('display_name', 'Claude')
parts = [model]
ctx = data.get('context_window', {}).get('used_percentage')
if ctx is not None:
parts.append(fmt('ctx', ctx))
five = data.get('rate_limits', {}).get('five_hour', {}).get('used_percentage')
if five is not None:
parts.append(fmt('5h', five))
week = data.get('rate_limits', {}).get('seven_day', {}).get('used_percentage')
if week is not None:
parts.append(fmt('7d', week))
print(f' {DIM}│{R} '.join(parts), end='')
#!/usr/bin/env python3
"""Pattern 3: Ring meter - pie-like circle segments"""
import json, sys
data = json.load(sys.stdin)
R = '\033[0m'
DIM = '\033[2m'
BOLD = '\033[1m'
RINGS = ['○', '◔', '◑', '◕', '●']
def gradient(pct):
if pct < 50:
r = int(pct * 5.1)
return f'\033[38;2;{r};200;80m'
else:
g = int(200 - (pct - 50) * 4)
return f'\033[38;2;255;{max(g, 0)};60m'
def ring(pct):
idx = min(int(pct / 25), 4)
return RINGS[idx]
def fmt(label, pct):
p = round(pct)
return f'{DIM}{label}{R} {gradient(pct)}{ring(pct)} {p}%{R}'
model = data.get('model', {}).get('display_name', 'Claude')
parts = [f'{BOLD}{model}{R}']
ctx = data.get('context_window', {}).get('used_percentage')
if ctx is not None:
parts.append(fmt('ctx', ctx))
five = data.get('rate_limits', {}).get('five_hour', {}).get('used_percentage')
if five is not None:
parts.append(fmt('5h', five))
week = data.get('rate_limits', {}).get('seven_day', {}).get('used_percentage')
if week is not None:
parts.append(fmt('7d', week))
print(' '.join(parts), end='')
#!/usr/bin/env python3
"""Pattern 4: Fine-grained progress bar with true color gradient"""
import json, sys
data = json.load(sys.stdin)
BLOCKS = ' ▏▎▍▌▋▊▉█'
R = '\033[0m'
DIM = '\033[2m'
def gradient(pct):
if pct < 50:
r = int(pct * 5.1)
return f'\033[38;2;{r};200;80m'
else:
g = int(200 - (pct - 50) * 4)
return f'\033[38;2;255;{max(g,0)};60m'
def bar(pct, width=10):
pct = min(max(pct, 0), 100)
filled = pct * width / 100
full = int(filled)
frac = int((filled - full) * 8)
b = '█' * full
if full < width:
b += BLOCKS[frac]
b += '░' * (width - full - 1)
return b
def fmt(label, pct):
p = round(pct)
return f'{label} {gradient(pct)}{bar(pct)} {p}%{R}'
model = data.get('model', {}).get('display_name', 'Claude')
parts = [model]
ctx = data.get('context_window', {}).get('used_percentage')
if ctx is not None:
parts.append(fmt('ctx', ctx))
five = data.get('rate_limits', {}).get('five_hour', {}).get('used_percentage')
if five is not None:
parts.append(fmt('5h', five))
week = data.get('rate_limits', {}).get('seven_day', {}).get('used_percentage')
if week is not None:
parts.append(fmt('7d', week))
print(f'{DIM}│{R}'.join(f' {p} ' for p in parts), end='')
#!/usr/bin/env python3
"""Pattern 5: Braille dots - dotted progress bar using braille characters"""
import json, sys
data = json.load(sys.stdin)
BRAILLE = ' ⣀⣄⣤⣦⣶⣷⣿'
R = '\033[0m'
DIM = '\033[2m'
def gradient(pct):
if pct < 50:
r = int(pct * 5.1)
return f'\033[38;2;{r};200;80m'
else:
g = int(200 - (pct - 50) * 4)
return f'\033[38;2;255;{max(g, 0)};60m'
def braille_bar(pct, width=8):
pct = min(max(pct, 0), 100)
level = pct / 100
bar = ''
for i in range(width):
seg_start = i / width
seg_end = (i + 1) / width
if level >= seg_end:
bar += BRAILLE[7]
elif level <= seg_start:
bar += BRAILLE[0]
else:
frac = (level - seg_start) / (seg_end - seg_start)
bar += BRAILLE[min(int(frac * 7), 7)]
return bar
def fmt(label, pct):
p = round(pct)
return f'{DIM}{label}{R} {gradient(pct)}{braille_bar(pct)}{R} {p}%'
model = data.get('model', {}).get('display_name', 'Claude')
parts = [model]
ctx = data.get('context_window', {}).get('used_percentage')
if ctx is not None:
parts.append(fmt('ctx', ctx))
five = data.get('rate_limits', {}).get('five_hour', {}).get('used_percentage')
if five is not None:
parts.append(fmt('5h', five))
week = data.get('rate_limits', {}).get('seven_day', {}).get('used_percentage')
if week is not None:
parts.append(fmt('7d', week))
print(f' {DIM}│{R} '.join(parts), end='')