์ํ ํ์์ค ์ฌ์ฉ์ ์ ์
Claude Code์์ ์ปจํ ์คํธ ์๋์ฐ ์ฌ์ฉ๋, ๋น์ฉ ๋ฐ git ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ธฐ ์ํด ์ฌ์ฉ์ ์ ์ ์ํ ํ์์ค ๊ตฌ์ฑ
์ํ ํ์์ค์ Claude Code ํ๋จ์ ์ฌ์ฉ์ ์ ์ ๊ฐ๋ฅํ ๋ง๋๋ก, ๊ตฌ์ฑํ ๋ชจ๋ ์ ธ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํฉ๋๋ค. stdin์ ํตํด JSON ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ณ ์คํฌ๋ฆฝํธ๊ฐ ์ถ๋ ฅํ๋ ๋ชจ๋ ๋ด์ฉ์ ํ์ํ์ฌ ์ปจํ ์คํธ ์ฌ์ฉ๋, ๋น์ฉ, git ์ํ ๋๋ ์ถ์ ํ๋ ค๋ ๋ค๋ฅธ ํญ๋ชฉ์ ํ๋์ ๋ณผ ์ ์๋ ์ง์์ ์ธ ๋ณด๊ธฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ํ ํ์์ค์ ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค:
- ์์ ์ค ์ปจํ ์คํธ ์๋์ฐ ์ฌ์ฉ๋์ ๋ชจ๋ํฐ๋งํ๋ ค๋ ๊ฒฝ์ฐ
- ์ธ์ ๋น์ฉ์ ์ถ์ ํด์ผ ํ๋ ๊ฒฝ์ฐ
- ์ฌ๋ฌ ์ธ์ ์์ ์์ ํ๊ณ ์ด๋ค์ ๊ตฌ๋ถํด์ผ ํ๋ ๊ฒฝ์ฐ
- git ๋ธ๋์น ๋ฐ ์ํ๋ฅผ ํญ์ ํ์ํ๋ ค๋ ๊ฒฝ์ฐ
์ํ ํ์์ค์ ๊ธฐ๋ณธ ์ ๊ณต ๋ฐ๋ฅ๊ธ ๋ฐฐ์ง ์์ ์์ฒด ํ์์ ๋ ๋๋ง๋๋ฉฐ ์ด๋ฅผ ๋์ฒดํ์ง ์์ต๋๋ค. ๋ํ์ ID๊ฐ ๋ํ๋ ๋ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์ง ์๊ณ ๋ฐ๋ฅ๊ธ์ ํด๋ฆญ ๊ฐ๋ฅํ ๋งํฌ ๋ฐฐ์ง๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด ๋์ footerLinksRegexes๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
๋ค์์ ์ฒซ ๋ฒ์งธ ์ค์ git ์ ๋ณด๋ฅผ ํ์ํ๊ณ ๋ ๋ฒ์งธ ์ค์ ์์์ผ๋ก ๊ตฌ๋ถ๋ ์ปจํ ์คํธ ๋ง๋๋ฅผ ํ์ํ๋ ๋ค์ค ์ค ์ํ ํ์์ค์ ์์ ๋๋ค.
์ด ํ์ด์ง๋ ๊ธฐ๋ณธ ์ํ ํ์์ค ์ค์ ์ ์๋ดํ๊ณ , ๋ฐ์ดํฐ ํ๋ฆ์ด Claude Code์์ ์คํฌ๋ฆฝํธ๋ก ์ด๋ป๊ฒ ํ๋ฅด๋์ง ์ค๋ช ํ๋ฉฐ, ํ์ํ ์ ์๋ ๋ชจ๋ ํ๋๋ฅผ ๋์ดํ๊ณ , git ์ํ, ๋น์ฉ ์ถ์ ๋ฐ ์งํ๋ฅ ํ์์ค๊ณผ ๊ฐ์ ์ผ๋ฐ์ ์ธ ํจํด์ ๋ํ ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅํ ์์ ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ํ ํ์์ค ์ค์
/statusline ๋ช
๋ น์ ์ฌ์ฉํ์ฌ Claude Code๊ฐ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๋๋ก ํ๊ฑฐ๋, ์๋์ผ๋ก ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค๊ณ ์ค์ ์ ์ถ๊ฐํฉ๋๋ค.
/statusline ๋ช ๋ น ์ฌ์ฉ
/statusline ๋ช
๋ น์ ํ์ํ๋ ค๋ ๋ด์ฉ์ ์ค๋ช
ํ๋ ์์ฐ์ด ์ง์์ฌํญ์ ํ์ฉํฉ๋๋ค. Claude Code๋ ~/.claude/ ๋๋ ํ ๋ฆฌ์ ์คํฌ๋ฆฝํธ ํ์ผ์ ์์ฑํ๊ณ ์ค์ ์ ์๋์ผ๋ก ์
๋ฐ์ดํธํฉ๋๋ค:
/statusline show model name and context percentage with a progress bar
์ํ ํ์์ค ์๋ ๊ตฌ์ฑ
์ฌ์ฉ์ ์ค์ (~/.claude/settings.json, ์ฌ๊ธฐ์ ~๋ ํ ๋๋ ํ ๋ฆฌ) ๋๋ ํ๋ก์ ํธ ์ค์ ์ statusLine ํ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. type์ "command"๋ก ์ค์ ํ๊ณ command๋ฅผ ์คํฌ๋ฆฝํธ ๊ฒฝ๋ก ๋๋ ์ธ๋ผ์ธ ์
ธ ๋ช
๋ น์ผ๋ก ์ง์ ํฉ๋๋ค. ์คํฌ๋ฆฝํธ ์์ฑ์ ๋ํ ์ ์ฒด ์ค๋ช
์ ์ํ ํ์์ค ๋จ๊ณ๋ณ ๊ตฌ์ถ์ ์ฐธ์กฐํ์ธ์.
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2
}
}
command ํ๋๋ ์
ธ์์ ์คํ๋๋ฏ๋ก ์คํฌ๋ฆฝํธ ํ์ผ ๋์ ์ธ๋ผ์ธ ๋ช
๋ น์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ์ด ์์ ๋ jq๋ฅผ ์ฌ์ฉํ์ฌ JSON ์
๋ ฅ์ ๊ตฌ๋ฌธ ๋ถ์ํ๊ณ ๋ชจ๋ธ ์ด๋ฆ๊ณผ ์ปจํ
์คํธ ๋ฐฑ๋ถ์จ์ ํ์ํฉ๋๋ค:
{
"statusLine": {
"type": "command",
"command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}
}
์ ํ์ padding ํ๋๋ ์ํ ํ์์ค ์ฝํ
์ธ ์ ์ถ๊ฐ ์ํ ๊ฐ๊ฒฉ(๋ฌธ์ ๋จ์)์ ์ถ๊ฐํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 0์
๋๋ค. ์ด ํจ๋ฉ์ ์ธํฐํ์ด์ค์ ๊ธฐ๋ณธ ์ ๊ณต ๊ฐ๊ฒฉ์ ์ถ๊ฐ๋๋ฏ๋ก ํฐ๋ฏธ๋ ๊ฐ์ฅ์๋ฆฌ๋ก๋ถํฐ์ ์ ๋ ๊ฑฐ๋ฆฌ๊ฐ ์๋ ์๋ ๋ค์ฌ์ฐ๊ธฐ๋ฅผ ์ ์ดํฉ๋๋ค.
์ ํ์ refreshInterval ํ๋๋ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์
๋ฐ์ดํธ์ ์ถ๊ฐ๋ก N์ด๋ง๋ค ๋ช
๋ น์ ๋ค์ ์คํํฉ๋๋ค. ์ต์๊ฐ์ 1์
๋๋ค. ์ํ ํ์์ค์ด ์๊ณ์ ๊ฐ์ ์๊ฐ ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ฅผ ํ์ํ๊ฑฐ๋ ์ฃผ ์ธ์
์ด ์ ํด ์ํ์ผ ๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋ธ์์ด์ ํธ๊ฐ git ์ํ๋ฅผ ๋ณ๊ฒฝํ ๋ ์ด๋ฅผ ์ค์ ํฉ๋๋ค. ์ด๋ฒคํธ์์๋ง ์คํํ๋ ค๋ฉด ์ค์ ํ์ง ์์ ์ํ๋ก ๋์ธ์.
์ ํ์ hideVimModeIndicator ํ๋๋ ํ๋กฌํํธ ์๋์ ๊ธฐ๋ณธ ์ ๊ณต -- INSERT -- ํ
์คํธ๋ฅผ ์จ๊น๋๋ค. ์คํฌ๋ฆฝํธ๊ฐ vim.mode ์์ฒด๋ฅผ ๋ ๋๋งํ ๋ ์ด๋ฅผ true๋ก ์ค์ ํ์ฌ ๋ชจ๋๊ฐ ๋ ๋ฒ ํ์๋์ง ์๋๋ก ํฉ๋๋ค.
์ํ ํ์์ค ๋นํ์ฑํ
/statusline์ ์คํํ๊ณ ์ํ ํ์์ค์ ์ ๊ฑฐํ๊ฑฐ๋ ์ง์ฐ๋๋ก ์์ฒญํฉ๋๋ค(์: /statusline delete, /statusline clear, /statusline remove it). settings.json์์ statusLine ํ๋๋ฅผ ์๋์ผ๋ก ์ญ์ ํ ์๋ ์์ต๋๋ค.
์ํ ํ์์ค ๋จ๊ณ๋ณ ๊ตฌ์ถ
์ด ์ค๋ช ์๋ ํ์ฌ ๋ชจ๋ธ, ์์ ๋๋ ํ ๋ฆฌ ๋ฐ ์ปจํ ์คํธ ์๋์ฐ ์ฌ์ฉ๋ ๋ฐฑ๋ถ์จ์ ํ์ํ๋ ์ํ ํ์์ค์ ์๋์ผ๋ก ๋ง๋ค์ด ๋ด๋ถ ๋์์ ๋ณด์ฌ์ค๋๋ค.
/statusline์ ์ํ๋ ๋ด์ฉ ์ค๋ช
๊ณผ ํจ๊ป ์คํํ๋ฉด ์ด ๋ชจ๋ ๊ฒ์ด ์๋์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
์ด ์์ ๋ macOS ๋ฐ Linux์์ ์๋ํ๋ Bash ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค. Windows์์๋ Windows ๊ตฌ์ฑ์ ์ฐธ์กฐํ์ฌ PowerShell ๋ฐ Git Bash ์์ ๋ฅผ ํ์ธํ์ธ์.
JSON์ ์ฝ๊ณ ์ถ๋ ฅ์ ์ธ์ํ๋ ์คํฌ๋ฆฝํธ ๋ง๋ค๊ธฐ
Claude Code๋ stdin์ ํตํด JSON ๋ฐ์ดํฐ๋ฅผ ์คํฌ๋ฆฝํธ๋ก ๋ณด๋
๋๋ค. ์ด ์คํฌ๋ฆฝํธ๋ jq(์ค์นํด์ผ ํ ์ ์๋ ๋ช
๋ น์ค JSON ํ์)๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ธ ์ด๋ฆ, ๋๋ ํ ๋ฆฌ ๋ฐ ์ปจํ
์คํธ ๋ฐฑ๋ถ์จ์ ์ถ์ถํ ๋ค์ ํ์์ด ์ง์ ๋ ์ค์ ์ธ์ํฉ๋๋ค.
์ด๋ฅผ ~/.claude/statusline.sh์ ์ ์ฅํฉ๋๋ค(์ฌ๊ธฐ์ ~๋ ํ ๋๋ ํ ๋ฆฌ์ด๋ฉฐ, macOS์์๋ /Users/username, Linux์์๋ /home/username):
#!/bin/bash
# Claude Code๊ฐ stdin์ผ๋ก ๋ณด๋ด๋ JSON ๋ฐ์ดํฐ ์ฝ๊ธฐ
input=$(cat)
# jq๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ ์ถ์ถ
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
# "// 0"์ ํ๋๊ฐ null์ธ ๊ฒฝ์ฐ ํด๋ฐฑ์ ์ ๊ณตํฉ๋๋ค
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
# ์ํ ํ์์ค ์ถ๋ ฅ - ${DIR##*/}๋ ํด๋ ์ด๋ฆ๋ง ์ถ์ถํฉ๋๋ค
echo "[$MODEL] ๐ ${DIR##*/} | ${PCT}% context"
์คํ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ธฐ
์ ธ์ด ์คํํ ์ ์๋๋ก ์คํฌ๋ฆฝํธ๋ฅผ ์คํ ๊ฐ๋ฅํ๊ฒ ํ์ํฉ๋๋ค:
chmod +x ~/.claude/statusline.sh
์ค์ ์ ์ถ๊ฐ
Claude Code์ ์คํฌ๋ฆฝํธ๋ฅผ ์ํ ํ์์ค๋ก ์คํํ๋๋ก ์ง์ํฉ๋๋ค. ์ด ๊ตฌ์ฑ์ ~/.claude/settings.json์ ์ถ๊ฐํฉ๋๋ค. ์ด๋ type์ "command"๋ก ์ค์ ํ๊ณ (์๋ฏธ: "์ด ์
ธ ๋ช
๋ น ์คํ") command๋ฅผ ์คํฌ๋ฆฝํธ๋ก ์ง์ ํฉ๋๋ค:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
}
์ํ ํ์์ค์ด ์ธํฐํ์ด์ค ํ๋จ์ ๋ํ๋ฉ๋๋ค. ์ค์ ์ ์๋์ผ๋ก ๋ค์ ๋ก๋๋์ง๋ง Claude Code์์ ๋ค์ ์ํธ ์์ฉ๊น์ง ๋ณ๊ฒฝ ์ฌํญ์ด ๋ํ๋์ง ์์ต๋๋ค.
์ํ ํ์์ค ์๋ ๋ฐฉ์
Claude Code๋ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ณ stdin์ ํตํด JSON ์ธ์ ๋ฐ์ดํฐ๋ฅผ ํ์ดํํฉ๋๋ค. ์คํฌ๋ฆฝํธ๋ JSON์ ์ฝ๊ณ ํ์ํ ๊ฒ์ ์ถ์ถํ ๋ค์ stdout์ ํ ์คํธ๋ฅผ ์ธ์ํฉ๋๋ค. Claude Code๋ ์คํฌ๋ฆฝํธ๊ฐ ์ธ์ํ๋ ๋ชจ๋ ๊ฒ์ ํ์ํฉ๋๋ค.
์ ๋ฐ์ดํธ ์๊ธฐ
์คํฌ๋ฆฝํธ๋ ์๋ก์ด ์ด์์คํดํธ ๋ฉ์์ง ํ, /compact ์๋ฃ ํ, ๊ถํ ๋ชจ๋๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋๋ vim ๋ชจ๋๊ฐ ์ ํ๋ ๋ ์คํ๋ฉ๋๋ค. ์
๋ฐ์ดํธ๋ 300ms์์ ๋๋ฐ์ด์ค๋๋ฏ๋ก ๋น ๋ฅธ ๋ณ๊ฒฝ์ด ํจ๊ป ์ผ๊ด ์ฒ๋ฆฌ๋๊ณ ์คํฌ๋ฆฝํธ๋ ์ํฉ์ด ์์ ํ๋๋ฉด ํ ๋ฒ ์คํ๋ฉ๋๋ค. ์คํฌ๋ฆฝํธ๊ฐ ์ฌ์ ํ ์คํ ์ค์ธ ๋์ ์ ์
๋ฐ์ดํธ๊ฐ ํธ๋ฆฌ๊ฑฐ๋๋ฉด ์งํ ์ค์ธ ์คํ์ด ์ทจ์๋ฉ๋๋ค. ์คํฌ๋ฆฝํธ๋ฅผ ํธ์งํ๋ฉด Claude Code์์ ๋ค์ ์ํธ ์์ฉ์ด ์
๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ ๋๊น์ง ๋ณ๊ฒฝ ์ฌํญ์ด ๋ํ๋์ง ์์ต๋๋ค.
์ด๋ฌํ ํธ๋ฆฌ๊ฑฐ๋ ์ฃผ ์ธ์
์ด ์ ํด ์ํ์ผ ๋(์: ์ฝ๋๋ค์ดํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋ธ์์ด์ ํธ๋ฅผ ๊ธฐ๋ค๋ฆด ๋) ์กฐ์ฉํด์ง ์ ์์ต๋๋ค. ์ ํด ๊ธฐ๊ฐ ๋์ ์๊ฐ ๊ธฐ๋ฐ ๋๋ ์ธ๋ถ ์์ค ์ธ๊ทธ๋จผํธ๋ฅผ ์ต์ ์ํ๋ก ์ ์งํ๋ ค๋ฉด refreshInterval์ ์ค์ ํ์ฌ ๊ณ ์ ํ์ด๋จธ์์๋ ๋ช
๋ น์ ๋ค์ ์คํํฉ๋๋ค.
์คํฌ๋ฆฝํธ๊ฐ ์ถ๋ ฅํ ์ ์๋ ๊ฒ
- ์ฌ๋ฌ ์ค: ๊ฐ
echo๋๋print๋ฌธ์ ๋ณ๋์ ํ์ผ๋ก ํ์๋ฉ๋๋ค. ๋ค์ค ์ค ์์ ๋ฅผ ์ฐธ์กฐํ์ธ์. - ์์: ๋
น์์ ๊ฒฝ์ฐ
\033[32m๊ณผ ๊ฐ์ ANSI ์ด์ค์ผ์ดํ ์ฝ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค(ํฐ๋ฏธ๋์ด ์ง์ํด์ผ ํจ). git ์ํ ์์ ๋ฅผ ์ฐธ์กฐํ์ธ์. - ๋งํฌ: OSC 8 ์ด์ค์ผ์ดํ ์ํ์ค๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ํด๋ฆญ ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค(macOS์์๋ Cmd+ํด๋ฆญ, Windows/Linux์์๋ Ctrl+ํด๋ฆญ). iTerm2, Kitty ๋๋ WezTerm๊ณผ ๊ฐ์ด ํ์ดํผ๋งํฌ๋ฅผ ์ง์ํ๋ ํฐ๋ฏธ๋์ด ํ์ํฉ๋๋ค. ํด๋ฆญ ๊ฐ๋ฅํ ๋งํฌ ์์ ๋ฅผ ์ฐธ์กฐํ์ธ์.
ํฐ๋ฏธ๋์ ๋ง๊ฒ ์ถ๋ ฅ ํฌ๊ธฐ ์กฐ์
Claude Code๋ ์คํฌ๋ฆฝํธ์ ์ถ๋ ฅ์ ์บก์ฒํ๋ฏ๋ก ํฐ๋ฏธ๋์ ์ง์ ์ฐ๊ฒฐํ์ง ์์ ์คํฌ๋ฆฝํธ ๋ด๋ถ์์ tput cols์ ์ธ์ด ์์ค์ ๋๋น ๊ฐ์ง๊ฐ ํฐ๋ฏธ๋ ํฌ๊ธฐ๋ฅผ ์ฝ์ ์ ์์ต๋๋ค. {/* min-version: 2.1.153 */}COLUMNS ๋ฐ LINES ํ๊ฒฝ ๋ณ์๋ฅผ ๋์ ์ฝ์ผ์ธ์. Claude Code๋ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ธฐ ์ ์ ์ด๋ฌํ ๋ณ์๋ฅผ ํ์ฌ ํฐ๋ฏธ๋ ํฌ๊ธฐ๋ก ์ค์ ํฉ๋๋ค. Claude Code v2.1.153 ์ด์์ด ํ์ํฉ๋๋ค.
์ํ ํ์์ค์ ๋ก์ปฌ์์ ์คํ๋๋ฉฐ API ํ ํฐ์ ์๋นํ์ง ์์ต๋๋ค. ์๋ ์์ฑ ์ ์, ๋์๋ง ๋ฉ๋ด ๋ฐ ๊ถํ ํ๋กฌํํธ๋ฅผ ํฌํจํ ํน์ UI ์ํธ ์์ฉ ์ค์ ์ผ์์ ์ผ๋ก ์จ๊ฒจ์ง๋๋ค.
์ฌ์ฉ ๊ฐ๋ฅํ ๋ฐ์ดํฐ
Claude Code๋ stdin์ ํตํด ์คํฌ๋ฆฝํธ์ ๋ค์ JSON ํ๋๋ฅผ ๋ณด๋ ๋๋ค:
| ํ๋ | ์ค๋ช |
|---|---|
model.id, model.display_name |
ํ์ฌ ๋ชจ๋ธ ์๋ณ์ ๋ฐ ํ์ ์ด๋ฆ |
cwd, workspace.current_dir |
ํ์ฌ ์์
๋๋ ํ ๋ฆฌ. ๋ ํ๋ ๋ชจ๋ ๋์ผํ ๊ฐ์ ํฌํจํฉ๋๋ค. workspace.current_dir์ workspace.project_dir๊ณผ์ ์ผ๊ด์ฑ์ ์ํด ์ ํธ๋ฉ๋๋ค. |
workspace.project_dir |
Claude Code๊ฐ ์์๋ ๋๋ ํ ๋ฆฌ๋ก, ์ธ์
์ค์ ์์
๋๋ ํ ๋ฆฌ๊ฐ ๋ณ๊ฒฝ๋๋ฉด cwd์ ๋ค๋ฅผ ์ ์์ต๋๋ค |
workspace.added_dirs |
/add-dir ๋๋ --add-dir์ ํตํด ์ถ๊ฐ๋ ์ถ๊ฐ ๋๋ ํ ๋ฆฌ. ์ถ๊ฐ๋ ๊ฒ์ด ์์ผ๋ฉด ๋น ๋ฐฐ์ด |
workspace.git_worktree |
git worktree add๋ก ์์ฑ๋ ์ฐ๊ฒฐ๋ worktree ๋ด์ ํ์ฌ ๋๋ ํ ๋ฆฌ๊ฐ ์์ ๋ Git worktree ์ด๋ฆ. ์ฃผ ์์
ํธ๋ฆฌ์๋ ์์ต๋๋ค. worktree.*์ ๋ฌ๋ฆฌ --worktree ์ธ์
์๋ง ์ ์ฉ๋๋ ๊ฒ์ด ์๋๋ผ ๋ชจ๋ git worktree์ ๋ํด ์ฑ์์ง๋๋ค |
workspace.repo.host, workspace.repo.owner, workspace.repo.name |
origin ์๊ฒฉ์์ ํ์ฑ๋ ์ ์ฅ์ ์๋ณ์(์: "github.com", "anthropics", "claude-code"). git ์ ์ฅ์ ์ธ๋ถ์ ์๊ฑฐ๋ origin ์๊ฒฉ์ด ๊ตฌ์ฑ๋์ง ์์ ๊ฒฝ์ฐ ์์ |
cost.total_cost_usd |
USD ๋จ์์ ์ด ์ธ์ ๋น์ฉ(ํด๋ผ์ด์ธํธ ์ธก์์ ๊ณ์ฐ). ์ค์ ์ฒญ๊ตฌ์์ ๋ค๋ฅผ ์ ์์ต๋๋ค |
cost.total_duration_ms |
์ธ์ ์์ ์ดํ์ ์ด ๋ฒฝ์๊ณ ์๊ฐ(๋ฐ๋ฆฌ์ด) |
cost.total_api_duration_ms |
API ์๋ต ๋๊ธฐ์ ์๋น๋ ์ด ์๊ฐ(๋ฐ๋ฆฌ์ด) |
cost.total_lines_added, cost.total_lines_removed |
๋ณ๊ฒฝ๋ ์ฝ๋ ์ค |
context_window.total_input_tokens, context_window.total_output_tokens |
์ปจํ ์คํธ ์๋์ฐ์ ํ์ฌ ์๋ ํ ํฐ ์(๊ฐ์ฅ ์ต๊ทผ API ์๋ต์์). ์ ๋ ฅ์๋ ์บ์ ์ฝ๊ธฐ ๋ฐ ์ฐ๊ธฐ๊ฐ ํฌํจ๋ฉ๋๋ค. v2.1.132 ์ด์ ์๋ ๋์ ์ธ์ ํฉ๊ณ์์ต๋๋ค |
context_window.context_window_size |
ํ ํฐ ๋จ์์ ์ต๋ ์ปจํ ์คํธ ์๋์ฐ ํฌ๊ธฐ. ๊ธฐ๋ณธ๊ฐ์ 200,000์ด๊ฑฐ๋ ํ์ฅ๋ ์ปจํ ์คํธ๊ฐ ์๋ ๋ชจ๋ธ์ ๊ฒฝ์ฐ 1,000,000์ ๋๋ค. |
context_window.used_percentage |
์ฌ์ฉ๋ ์ปจํ ์คํธ ์๋์ฐ์ ์ฌ์ ๊ณ์ฐ๋ ๋ฐฑ๋ถ์จ |
context_window.remaining_percentage |
๋จ์ ์ปจํ ์คํธ ์๋์ฐ์ ์ฌ์ ๊ณ์ฐ๋ ๋ฐฑ๋ถ์จ |
context_window.current_usage |
๋ง์ง๋ง API ํธ์ถ์ ํ ํฐ ์(์ปจํ ์คํธ ์๋์ฐ ํ๋์ ์ค๋ช ๋จ) |
exceeds_200k_tokens |
๊ฐ์ฅ ์ต๊ทผ API ์๋ต์ ์ด ํ ํฐ ์(์ ๋ ฅ, ์บ์ ๋ฐ ์ถ๋ ฅ ํ ํฐ ๊ฒฐํฉ)๊ฐ 200k๋ฅผ ์ด๊ณผํ๋์ง ์ฌ๋ถ. ์ด๋ ์ค์ ์ปจํ ์คํธ ์๋์ฐ ํฌ๊ธฐ์ ๊ด๊ณ์์ด ๊ณ ์ ๋ ์๊ณ๊ฐ์ ๋๋ค. |
effort.level |
ํ์ฌ ์ถ๋ก ๋
ธ๋ ฅ(low, medium, high, xhigh ๋๋ max). ๋ผ์ด๋ธ ์ธ์
๊ฐ์ ๋ฐ์ํ๋ฉฐ, ์ค๊ฐ ์ธ์
/effort ๋ณ๊ฒฝ์ ํฌํจํฉ๋๋ค. Ultracode๋ ๋ณ๊ฐ์ ์์ค์ด ์๋๋ฉฐ xhigh๋ก ๋ณด๊ณ ๋ฉ๋๋ค. ํ์ฌ ๋ชจ๋ธ์ด ๋
ธ๋ ฅ ๋งค๊ฐ๋ณ์๋ฅผ ์ง์ํ์ง ์์ ๋๋ ์์ต๋๋ค |
thinking.enabled |
์ธ์ ์ ๋ํด ํ์ฅ๋ ์ฌ๊ณ ๊ฐ ํ์ฑํ๋์ด ์๋์ง ์ฌ๋ถ |
rate_limits.five_hour.used_percentage, rate_limits.seven_day.used_percentage |
5์๊ฐ ๋๋ 7์ผ ์๋ ์ ํ์ ์๋น๋ ๋ฐฑ๋ถ์จ(0~100) |
rate_limits.five_hour.resets_at, rate_limits.seven_day.resets_at |
5์๊ฐ ๋๋ 7์ผ ์๋ ์ ํ ์๋์ฐ๊ฐ ์ฌ์ค์ ๋๋ Unix epoch ์ด |
session_id |
๊ณ ์ ์ธ์ ์๋ณ์ |
session_name |
--name ํ๋๊ทธ ๋๋ /rename์ผ๋ก ์ค์ ๋ ์ฌ์ฉ์ ์ ์ ์ธ์
์ด๋ฆ. ์ฌ์ฉ์ ์ ์ ์ด๋ฆ์ด ์ค์ ๋์ง ์์ ๊ฒฝ์ฐ ์์ |
transcript_path |
๋ํ ๊ธฐ๋ก ํ์ผ์ ๊ฒฝ๋ก |
version |
Claude Code ๋ฒ์ |
output_style.name |
ํ์ฌ ์ถ๋ ฅ ์คํ์ผ์ ์ด๋ฆ |
vim.mode |
vim ๋ชจ๋๊ฐ ํ์ฑํ๋์ด ์์ ๋ ํ์ฌ vim ๋ชจ๋(NORMAL, INSERT, VISUAL ๋๋ VISUAL LINE) |
agent.name |
--agent ํ๋๊ทธ ๋๋ ์์ด์ ํธ ์ค์ ์ด ๊ตฌ์ฑ๋์ด ์์ ๋ ์์ด์ ํธ ์ด๋ฆ |
pr.number, pr.url |
ํ์ฌ ๋ธ๋์น์ ๋ํ ์ด๋ฆฐ ํ ์์ฒญ. ํ๋จ ์ํ ํ์์ค์ PR ๋ฐฐ์ง๋ฅผ ๋ฐ์ํฉ๋๋ค. PR์ ์ฐพ์ ๋๊น์ง, git ์ ์ฅ์์ ์์ ๋, ๋๋ PR์ด ๋ณํฉ๋๊ฑฐ๋ ๋ซํ ํ์๋ ์์ |
pr.review_state |
์ด๋ฆฐ PR์ ๊ฒํ ์ํ: approved, pending, changes_requested ๋๋ draft. pr์ด ์์ ๋๋ ๋
๋ฆฝ์ ์ผ๋ก ์์ ์ ์์ |
worktree.name |
ํ์ฑ worktree์ ์ด๋ฆ. --worktree ์ธ์
์ค์๋ง ํ์๋ฉ๋๋ค |
worktree.path |
worktree ๋๋ ํ ๋ฆฌ์ ์ ๋ ๊ฒฝ๋ก |
worktree.branch |
worktree์ Git ๋ธ๋์น ์ด๋ฆ(์: "worktree-my-feature"). ํ
๊ธฐ๋ฐ worktree์ ๊ฒฝ์ฐ ์์ |
worktree.original_cwd |
worktree์ ๋ค์ด๊ฐ๊ธฐ ์ ์ Claude๊ฐ ์๋ ๋๋ ํ ๋ฆฌ |
worktree.original_branch |
worktree์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ์ฒดํฌ์์๋ Git ๋ธ๋์น. ํ ๊ธฐ๋ฐ worktree์ ๊ฒฝ์ฐ ์์ |
์ ์ฒด JSON ์คํค๋ง
์ํ ํ์์ค ๋ช ๋ น์ stdin์ ํตํด ์ด JSON ๊ตฌ์กฐ๋ฅผ ์์ ํฉ๋๋ค:
{
"cwd": "/current/working/directory",
"session_id": "abc123...",
"session_name": "my-session",
"transcript_path": "/path/to/transcript.jsonl",
"model": {
"id": "claude-opus-4-8",
"display_name": "Opus"
},
"workspace": {
"current_dir": "/current/working/directory",
"project_dir": "/original/project/directory",
"added_dirs": [],
"git_worktree": "feature-xyz",
"repo": {
"host": "github.com",
"owner": "anthropics",
"name": "claude-code"
}
},
"version": "2.1.90",
"output_style": {
"name": "default"
},
"cost": {
"total_cost_usd": 0.01234,
"total_duration_ms": 45000,
"total_api_duration_ms": 2300,
"total_lines_added": 156,
"total_lines_removed": 23
},
"context_window": {
"total_input_tokens": 15500,
"total_output_tokens": 1200,
"context_window_size": 200000,
"used_percentage": 8,
"remaining_percentage": 92,
"current_usage": {
"input_tokens": 8500,
"output_tokens": 1200,
"cache_creation_input_tokens": 5000,
"cache_read_input_tokens": 2000
}
},
"exceeds_200k_tokens": false,
"effort": {
"level": "high"
},
"thinking": {
"enabled": true
},
"rate_limits": {
"five_hour": {
"used_percentage": 23.5,
"resets_at": 1738425600
},
"seven_day": {
"used_percentage": 41.2,
"resets_at": 1738857600
}
},
"vim": {
"mode": "NORMAL"
},
"agent": {
"name": "security-reviewer"
},
"pr": {
"number": 1234,
"url": "https://github.com/anthropics/claude-code/pull/1234",
"review_state": "pending"
},
"worktree": {
"name": "my-feature",
"path": "/path/to/.claude/worktrees/my-feature",
"branch": "worktree-my-feature",
"original_cwd": "/path/to/project",
"original_branch": "main"
}
}
์์ ์ ์๋ ํ๋ (JSON์ ์์):
session_name:--name๋๋/rename์ผ๋ก ์ฌ์ฉ์ ์ ์ ์ด๋ฆ์ด ์ค์ ๋์์ ๋๋ง ๋ํ๋จworkspace.git_worktree: ํ์ฌ ๋๋ ํ ๋ฆฌ๊ฐ ์ฐ๊ฒฐ๋ git worktree ๋ด์ ์์ ๋๋ง ๋ํ๋จworkspace.repo: git ์ ์ฅ์ ๋ด์ ์๊ณorigin์๊ฒฉ์ด ๊ตฌ์ฑ๋์ด ์์ ๋๋ง ๋ํ๋จeffort: ํ์ฌ ๋ชจ๋ธ์ด ์ถ๋ก ๋ ธ๋ ฅ ๋งค๊ฐ๋ณ์๋ฅผ ์ง์ํ ๋๋ง ๋ํ๋จvim: vim ๋ชจ๋๊ฐ ํ์ฑํ๋์ด ์์ ๋๋ง ๋ํ๋จagent:--agentํ๋๊ทธ ๋๋ ์์ด์ ํธ ์ค์ ์ด ๊ตฌ์ฑ๋์ด ์์ ๋๋ง ๋ํ๋จpr: ํ์ฌ ๋ธ๋์น์ ๋ํด ์ด๋ฆฐ PR์ ์ฐพ์์ ๋๋ง ๋ํ๋๋ฉฐ, PR์ด ๋ณํฉ๋๊ฑฐ๋ ๋ซํ๋ฉด ์ ๊ฑฐ๋ฉ๋๋ค.pr.review_state๋ ๋ ๋ฆฝ์ ์ผ๋ก ์์ ์ ์์ต๋๋คworktree:--worktree์ธ์ ์ค์๋ง ๋ํ๋จ. ์กด์ฌํ ๋branch๋ฐoriginal_branch๋ ํ ๊ธฐ๋ฐ worktree์ ๊ฒฝ์ฐ ์์ ์ ์์ต๋๋คrate_limits: Claude.ai ๊ตฌ๋ ์(Pro/Max)์ ๊ฒฝ์ฐ ์ฒซ ๋ฒ์งธ API ์๋ต ํ์๋ง ๋ํ๋จ. ๊ฐ ์๋์ฐ(five_hour,seven_day)๋ ๋ ๋ฆฝ์ ์ผ๋ก ์์ ์ ์์ต๋๋ค. ๋ถ์ฌ๋ฅผ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํ๋ ค๋ฉดjq -r '.rate_limits.five_hour.used_percentage // empty'๋ฅผ ์ฌ์ฉํฉ๋๋ค.
null์ผ ์ ์๋ ํ๋:
context_window.current_usage: ์ธ์ ์ ์ฒซ ๋ฒ์งธ API ํธ์ถ ์ ์null์ด๊ณ ,/compactํ์ ๋ค์null์ด ๋์๋ค๊ฐ ๋ค์ API ํธ์ถ์ด ์ด๋ฅผ ๋ค์ ์ฑ์ธ ๋๊น์ง ์ ์ง๋ฉ๋๋คcontext_window.used_percentage,context_window.remaining_percentage: ์ธ์ ์ด๊ธฐ์null์ผ ์ ์์
์คํฌ๋ฆฝํธ์์ ์กฐ๊ฑด๋ถ ์ก์ธ์ค๋ก ๋๋ฝ๋ ํ๋๋ฅผ ์ฒ๋ฆฌํ๊ณ null ๊ฐ์ ํด๋ฐฑ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
์ปจํ ์คํธ ์๋์ฐ ํ๋
context_window ๊ฐ์ฒด๋ ๊ฐ์ฅ ์ต๊ทผ API ์๋ต์ ๋ผ์ด๋ธ ์ปจํ
์คํธ ์๋์ฐ๋ฅผ ์ค๋ช
ํฉ๋๋ค. v2.1.132๋ถํฐ total_input_tokens ๋ฐ total_output_tokens๋ ๋์ ์ธ์
ํฉ๊ณ๊ฐ ์๋ ํ์ฌ ์ปจํ
์คํธ ์ฌ์ฉ๋์ ๋ฐ์ํฉ๋๋ค.
- ๊ฒฐํฉ๋ ํฉ๊ณ (
total_input_tokens,total_output_tokens): ์ปจํ ์คํธ ์๋์ฐ์ ํ์ฌ ์๋ ํ ํฐ.total_input_tokens๋input_tokens,cache_creation_input_tokens๋ฐcache_read_input_tokens์ ํฉ๊ณ์ ๋๋ค.total_output_tokens๋ ๊ฐ์ฅ ์ต๊ทผ ์๋ต์ ์ถ๋ ฅ ํ ํฐ์ ๋๋ค. ๋ ๋ค ์ฒซ ๋ฒ์งธ API ์๋ต ์ ์๋0์ ๋๋ค. - ๊ตฌ์ฑ ์์๋ณ ์ฌ์ฉ๋ (
current_usage): ์นดํ ๊ณ ๋ฆฌ๋ณ๋ก ๋ถ๋ฅ๋ ๋์ผํ ํ ํฐ ์. ์บ์ ํํธ๋ฅผ ์๋ก์ด ์ ๋ ฅ๊ณผ ๋ถ๋ฆฌํด์ผ ํ ๋ ์ด๋ฅผ ์ฌ์ฉํฉ๋๋ค.
current_usage ๊ฐ์ฒด์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค:
input_tokens: ํ์ฌ ์ปจํ ์คํธ์ ์ ๋ ฅ ํ ํฐoutput_tokens: ์์ฑ๋ ์ถ๋ ฅ ํ ํฐcache_creation_input_tokens: ์บ์์ ๊ธฐ๋ก๋ ํ ํฐcache_read_input_tokens: ์บ์์์ ์ฝ์ ํ ํฐ
์บ์ ํ๋์ ์๋ฏธ์ ์ฒญ๊ตฌ ๋ฐฉ์์ ๋ํด์๋ ์บ์ ์ฑ๋ฅ ํ์ธ์ ์ฐธ์กฐํ์ธ์.
used_percentage ํ๋๋ ์
๋ ฅ ํ ํฐ๋ง์ผ๋ก ๊ณ์ฐ๋ฉ๋๋ค: input_tokens + cache_creation_input_tokens + cache_read_input_tokens. output_tokens๋ ํฌํจํ์ง ์์ต๋๋ค.
current_usage์์ ์ปจํ
์คํธ ๋ฐฑ๋ถ์จ์ ์๋์ผ๋ก ๊ณ์ฐํ๋ ๊ฒฝ์ฐ ๋์ผํ ์
๋ ฅ ์ ์ฉ ๊ณต์์ ์ฌ์ฉํ์ฌ used_percentage์ ์ผ์น์ํต๋๋ค.
current_usage ๊ฐ์ฒด๋ ์ธ์
์ ์ฒซ ๋ฒ์งธ API ํธ์ถ ์ ์ null์ด๊ณ , /compact ์งํ์ ๋ค์ null์ด ๋์๋ค๊ฐ ๋ค์ API ํธ์ถ์ด ์ด๋ฅผ ๋ค์ ์ฑ์ธ ๋๊น์ง ์ ์ง๋ฉ๋๋ค.
์์
์ด ์์ ๋ ์ผ๋ฐ์ ์ธ ์ํ ํ์์ค ํจํด์ ๋ณด์ฌ์ค๋๋ค. ์์ ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด:
- ์คํฌ๋ฆฝํธ๋ฅผ
~/.claude/statusline.sh(๋๋.py/.js)์ ๊ฐ์ ํ์ผ์ ์ ์ฅํฉ๋๋ค - ์คํ ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค:
chmod +x ~/.claude/statusline.sh - ์ค์ ์ ๊ฒฝ๋ก๋ฅผ ์ถ๊ฐํฉ๋๋ค
Bash ์์ ๋ jq๋ฅผ ์ฌ์ฉํ์ฌ JSON์ ๊ตฌ๋ฌธ ๋ถ์ํฉ๋๋ค. Python ๋ฐ Node.js๋ ๊ธฐ๋ณธ ์ ๊ณต JSON ๊ตฌ๋ฌธ ๋ถ์์ ๊ฐ์ง๋๋ค.
์ปจํ ์คํธ ์๋์ฐ ์ฌ์ฉ๋
ํ์ฌ ๋ชจ๋ธ๊ณผ ์ปจํ
์คํธ ์๋์ฐ ์ฌ์ฉ๋์ ์๊ฐ์ ์งํ๋ฅ ํ์์ค๊ณผ ํจ๊ป ํ์ํฉ๋๋ค. ๊ฐ ์คํฌ๋ฆฝํธ๋ stdin์์ JSON์ ์ฝ๊ณ , used_percentage ํ๋๋ฅผ ์ถ์ถํ๊ณ , ์ฑ์์ง ๋ธ๋ก(โ)์ด ์ฌ์ฉ๋์ ๋ํ๋ด๋ 10์ ๋ง๋๋ฅผ ๊ตฌ์ถํฉ๋๋ค:
#!/bin/bash
# stdin์ ๋ชจ๋ ๋ด์ฉ์ ๋ณ์๋ก ์ฝ๊ธฐ
input=$(cat)
# jq๋ก ํ๋ ์ถ์ถ, "// 0"์ null์ ๋ํ ํด๋ฐฑ ์ ๊ณต
MODEL=$(echo "$input" | jq -r '.model.display_name')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
# ์งํ๋ฅ ํ์์ค ๊ตฌ์ถ: printf -v๋ ๊ณต๋ฐฑ์ ๋ง๋ค๊ณ ,
# ${var// /โ}๋ ๊ฐ ๊ณต๋ฐฑ์ ๋ธ๋ก ๋ฌธ์๋ก ๋ฐ๊ฟ
BAR_WIDTH=10
FILLED=$((PCT * BAR_WIDTH / 100))
EMPTY=$((BAR_WIDTH - FILLED))
BAR=""
[ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" && BAR="${FILL// /โ}"
[ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" && BAR="${BAR}${PAD// /โ}"
echo "[$MODEL] $BAR $PCT%"
#!/usr/bin/env python3
import json, sys
# json.load๋ ํ ๋จ๊ณ๋ก stdin์ ์ฝ๊ณ ๊ตฌ๋ฌธ ๋ถ์ํฉ๋๋ค
data = json.load(sys.stdin)
model = data['model']['display_name']
# "or 0"์ null ๊ฐ์ ์ฒ๋ฆฌํฉ๋๋ค
pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
# ๋ฌธ์์ด ๊ณฑ์
์ด ๋ง๋๋ฅผ ๊ตฌ์ถํฉ๋๋ค
filled = pct * 10 // 100
bar = 'โ' * filled + 'โ' * (10 - filled)
print(f"[{model}] {bar} {pct}%")
#!/usr/bin/env node
// Node.js๋ ์ด๋ฒคํธ๋ก stdin์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฝ์ต๋๋ค
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
// ์ ํ์ ์ฒด์ด๋(?.)์ null ํ๋๋ฅผ ์์ ํ๊ฒ ์ฒ๋ฆฌํฉ๋๋ค
const pct = Math.floor(data.context_window?.used_percentage || 0);
// String.repeat()์ด ๋ง๋๋ฅผ ๊ตฌ์ถํฉ๋๋ค
const filled = Math.floor(pct * 10 / 100);
const bar = 'โ'.repeat(filled) + 'โ'.repeat(10 - filled);
console.log(`[${model}] ${bar} ${pct}%`);
});
์์์ด ์๋ git ์ํ
์์์ผ๋ก ๊ตฌ๋ถ๋ ์คํ
์ด์ง ๋ฐ ์์ ๋ ํ์ผ ํ์๊ธฐ๊ฐ ์๋ git ๋ธ๋์น๋ฅผ ํ์ํฉ๋๋ค. ์ด ์คํฌ๋ฆฝํธ๋ ํฐ๋ฏธ๋ ์์์ ANSI ์ด์ค์ผ์ดํ ์ฝ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค: \033[32m์ ๋
น์, \033[33m์ ๋
ธ๋์, \033[0m์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ค์ ํฉ๋๋ค.
๊ฐ ์คํฌ๋ฆฝํธ๋ ํ์ฌ ๋๋ ํ ๋ฆฌ๊ฐ git ์ ์ฅ์์ธ์ง ํ์ธํ๊ณ , ์คํ ์ด์ง ๋ฐ ์์ ๋ ํ์ผ์ ๊ณ์ฐํ๊ณ , ์์์ผ๋ก ๊ตฌ๋ถ๋ ํ์๊ธฐ๋ฅผ ํ์ํฉ๋๋ค:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
GREEN='\033[32m'
YELLOW='\033[33m'
RESET='\033[0m'
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
GIT_STATUS=""
[ "$STAGED" -gt 0 ] && GIT_STATUS="${GREEN}+${STAGED}${RESET}"
[ "$MODIFIED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}${YELLOW}~${MODIFIED}${RESET}"
echo -e "[$MODEL] ๐ ${DIR##*/} | ๐ฟ $BRANCH $GIT_STATUS"
else
echo "[$MODEL] ๐ ${DIR##*/}"
fi
#!/usr/bin/env python3
import json, sys, subprocess, os
data = json.load(sys.stdin)
model = data['model']['display_name']
directory = os.path.basename(data['workspace']['current_dir'])
GREEN, YELLOW, RESET = '\033[32m', '\033[33m', '\033[0m'
try:
subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL)
branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True).strip()
staged_output = subprocess.check_output(['git', 'diff', '--cached', '--numstat'], text=True).strip()
modified_output = subprocess.check_output(['git', 'diff', '--numstat'], text=True).strip()
staged = len(staged_output.split('\n')) if staged_output else 0
modified = len(modified_output.split('\n')) if modified_output else 0
git_status = f"{GREEN}+{staged}{RESET}" if staged else ""
git_status += f"{YELLOW}~{modified}{RESET}" if modified else ""
print(f"[{model}] ๐ {directory} | ๐ฟ {branch} {git_status}")
except:
print(f"[{model}] ๐ {directory}")
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
const dir = path.basename(data.workspace.current_dir);
const GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RESET = '\x1b[0m';
try {
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
const staged = execSync('git diff --cached --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
const modified = execSync('git diff --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
let gitStatus = staged ? `${GREEN}+${staged}${RESET}` : '';
gitStatus += modified ? `${YELLOW}~${modified}${RESET}` : '';
console.log(`[${model}] ๐ ${dir} | ๐ฟ ${branch} ${gitStatus}`);
} catch {
console.log(`[${model}] ๐ ${dir}`);
}
});
๋น์ฉ ๋ฐ ๊ธฐ๊ฐ ์ถ์
์ธ์
์ API ๋น์ฉ ๋ฐ ๊ฒฝ๊ณผ ์๊ฐ์ ์ถ์ ํฉ๋๋ค. cost.total_cost_usd ํ๋๋ ํ์ฌ ์ธ์
์ ๋ชจ๋ API ํธ์ถ ๋น์ฉ์ ๋์ ํฉ๋๋ค. cost.total_duration_ms ํ๋๋ ์ธ์
์์ ์ดํ์ ์ด ๊ฒฝ๊ณผ ์๊ฐ์ ์ธก์ ํ๋ ๋ฐ๋ฉด, cost.total_api_duration_ms๋ API ์๋ต ๋๊ธฐ์ ์๋น๋ ์๊ฐ๋ง ์ถ์ ํฉ๋๋ค.
๊ฐ ์คํฌ๋ฆฝํธ๋ ๋น์ฉ์ ํตํ๋ก ํ์ํํ๊ณ ๋ฐ๋ฆฌ์ด๋ฅผ ๋ถ๊ณผ ์ด๋ก ๋ณํํฉ๋๋ค:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
COST_FMT=$(printf '$%.2f' "$COST")
DURATION_SEC=$((DURATION_MS / 1000))
MINS=$((DURATION_SEC / 60))
SECS=$((DURATION_SEC % 60))
echo "[$MODEL] ๐ฐ $COST_FMT | โฑ๏ธ ${MINS}m ${SECS}s"
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
model = data['model']['display_name']
cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
duration_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
duration_sec = duration_ms // 1000
mins, secs = duration_sec // 60, duration_sec % 60
print(f"[{model}] ๐ฐ ${cost:.2f} | โฑ๏ธ {mins}m {secs}s")
#!/usr/bin/env node
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
const cost = data.cost?.total_cost_usd || 0;
const durationMs = data.cost?.total_duration_ms || 0;
const durationSec = Math.floor(durationMs / 1000);
const mins = Math.floor(durationSec / 60);
const secs = durationSec % 60;
console.log(`[${model}] ๐ฐ $${cost.toFixed(2)} | โฑ๏ธ ${mins}m ${secs}s`);
});
์ฌ๋ฌ ์ค ํ์
์คํฌ๋ฆฝํธ๋ ์ฌ๋ฌ ์ค์ ์ถ๋ ฅํ์ฌ ๋ ํ๋ถํ ๋์คํ๋ ์ด๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ๊ฐ echo ๋ฌธ์ ์ํ ์์ญ์์ ๋ณ๋์ ํ์ ์์ฑํฉ๋๋ค.
์ด ์์ ๋ ์ฌ๋ฌ ๊ธฐ๋ฒ์ ๊ฒฐํฉํฉ๋๋ค: ์๊ณ๊ฐ ๊ธฐ๋ฐ ์์(70% ๋ฏธ๋ง์ผ ๋ ๋
น์, 70-89% ๋
ธ๋์, 90%+ ๋นจ๊ฐ์), ์งํ๋ฅ ํ์์ค ๋ฐ git ๋ธ๋์น ์ ๋ณด. ๊ฐ print ๋๋ echo ๋ฌธ์ ๋ณ๋์ ํ์ ๋ง๋ญ๋๋ค:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
# ์ปจํ
์คํธ ์ฌ์ฉ๋์ ๋ฐ๋ผ ๋ง๋ ์์ ์ ํ
if [ "$PCT" -ge 90 ]; then BAR_COLOR="$RED"
elif [ "$PCT" -ge 70 ]; then BAR_COLOR="$YELLOW"
else BAR_COLOR="$GREEN"; fi
FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
printf -v FILL "%${FILLED}s"; printf -v PAD "%${EMPTY}s"
BAR="${FILL// /โ}${PAD// /โ}"
MINS=$((DURATION_MS / 60000)); SECS=$(((DURATION_MS % 60000) / 1000))
BRANCH=""
git rev-parse --git-dir > /dev/null 2>&1 && BRANCH=" | ๐ฟ $(git branch --show-current 2>/dev/null)"
echo -e "${CYAN}[$MODEL]${RESET} ๐ ${DIR##*/}$BRANCH"
COST_FMT=$(printf '$%.2f' "$COST")
echo -e "${BAR_COLOR}${BAR}${RESET} ${PCT}% | ${YELLOW}${COST_FMT}${RESET} | โฑ๏ธ ${MINS}m ${SECS}s"
#!/usr/bin/env python3
import json, sys, subprocess, os
data = json.load(sys.stdin)
model = data['model']['display_name']
directory = os.path.basename(data['workspace']['current_dir'])
cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
duration_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
CYAN, GREEN, YELLOW, RED, RESET = '\033[36m', '\033[32m', '\033[33m', '\033[31m', '\033[0m'
bar_color = RED if pct >= 90 else YELLOW if pct >= 70 else GREEN
filled = pct // 10
bar = 'โ' * filled + 'โ' * (10 - filled)
mins, secs = duration_ms // 60000, (duration_ms % 60000) // 1000
try:
branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True, stderr=subprocess.DEVNULL).strip()
branch = f" | ๐ฟ {branch}" if branch else ""
except:
branch = ""
print(f"{CYAN}[{model}]{RESET} ๐ {directory}{branch}")
print(f"{bar_color}{bar}{RESET} {pct}% | {YELLOW}${cost:.2f}{RESET} | โฑ๏ธ {mins}m {secs}s")
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
const dir = path.basename(data.workspace.current_dir);
const cost = data.cost?.total_cost_usd || 0;
const pct = Math.floor(data.context_window?.used_percentage || 0);
const durationMs = data.cost?.total_duration_ms || 0;
const CYAN = '\x1b[36m', GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RED = '\x1b[31m', RESET = '\x1b[0m';
const barColor = pct >= 90 ? RED : pct >= 70 ? YELLOW : GREEN;
const filled = Math.floor(pct / 10);
const bar = 'โ'.repeat(filled) + 'โ'.repeat(10 - filled);
const mins = Math.floor(durationMs / 60000);
const secs = Math.floor((durationMs % 60000) / 1000);
let branch = '';
try {
branch = execSync('git branch --show-current', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
branch = branch ? ` | ๐ฟ ${branch}` : '';
} catch {}
console.log(`${CYAN}[${model}]${RESET} ๐ ${dir}${branch}`);
console.log(`${barColor}${bar}${RESET} ${pct}% | ${YELLOW}$${cost.toFixed(2)}${RESET} | โฑ๏ธ ${mins}m ${secs}s`);
});
ํด๋ฆญ ๊ฐ๋ฅํ ๋งํฌ
์ด ์์ ๋ GitHub ์ ์ฅ์์ ๋ํ ํด๋ฆญ ๊ฐ๋ฅํ ๋งํฌ๋ฅผ ๋ง๋ญ๋๋ค. git ์๊ฒฉ URL์ ์ฝ๊ณ , sed๋ฅผ ์ฌ์ฉํ์ฌ SSH ํ์์ HTTPS๋ก ๋ณํํ๊ณ , ์ ์ฅ์ ์ด๋ฆ์ OSC 8 ์ด์ค์ผ์ดํ ์ฝ๋๋ก ๋ํํฉ๋๋ค. Cmd(macOS) ๋๋ Ctrl(Windows/Linux)์ ๋๋ฅด๊ณ ํด๋ฆญํ์ฌ ๋ธ๋ผ์ฐ์ ์์ ๋งํฌ๋ฅผ ์ฝ๋๋ค.
๊ฐ ์คํฌ๋ฆฝํธ๋ git ์๊ฒฉ URL์ ๊ฐ์ ธ์ค๊ณ , SSH ํ์์ HTTPS๋ก ๋ณํํ๊ณ , ์ ์ฅ์ ์ด๋ฆ์ OSC 8 ์ด์ค์ผ์ดํ ์ฝ๋๋ก ๋ํํฉ๋๋ค. Bash ๋ฒ์ ์ printf '%b'๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ํ ์
ธ์์ ๋ฐฑ์ฌ๋์ ์ด์ค์ผ์ดํ๋ฅผ ๋ ์์ ์ ์ผ๋ก ํด์ํฉ๋๋ค:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
# git SSH URL์ HTTPS๋ก ๋ณํ
REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/git@github.com:/https:\/\/github.com\//' | sed 's/\.git$//')
if [ -n "$REMOTE" ]; then
REPO_NAME=$(basename "$REMOTE")
# OSC 8 ํ์: \e]8;;URL\a then TEXT then \e]8;;\a
# printf %b๋ ์
ธ ์ ์ฒด์์ ์ด์ค์ผ์ดํ ์ํ์ค๋ฅผ ์์ ์ ์ผ๋ก ํด์ํฉ๋๋ค
printf '%b' "[$MODEL] ๐ \e]8;;${REMOTE}\a${REPO_NAME}\e]8;;\a\n"
else
echo "[$MODEL]"
fi
#!/usr/bin/env python3
import json, sys, subprocess, re, os
data = json.load(sys.stdin)
model = data['model']['display_name']
# git ์๊ฒฉ URL ๊ฐ์ ธ์ค๊ธฐ
try:
remote = subprocess.check_output(
['git', 'remote', 'get-url', 'origin'],
stderr=subprocess.DEVNULL, text=True
).strip()
# SSH๋ฅผ HTTPS ํ์์ผ๋ก ๋ณํ
remote = re.sub(r'^git@github\.com:', 'https://github.com/', remote)
remote = re.sub(r'\.git$', '', remote)
repo_name = os.path.basename(remote)
# OSC 8 ์ด์ค์ผ์ดํ ์ํ์ค
link = f"\033]8;;{remote}\a{repo_name}\033]8;;\a"
print(f"[{model}] ๐ {link}")
except:
print(f"[{model}]")
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
try {
let remote = execSync('git remote get-url origin', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
// SSH๋ฅผ HTTPS ํ์์ผ๋ก ๋ณํ
remote = remote.replace(/^git@github\.com:/, 'https://github.com/').replace(/\.git$/, '');
const repoName = path.basename(remote);
// OSC 8 ์ด์ค์ผ์ดํ ์ํ์ค
const link = `\x1b]8;;${remote}\x07${repoName}\x1b]8;;\x07`;
console.log(`[${model}] ๐ ${link}`);
} catch {
console.log(`[${model}]`);
}
});
์๋ ์ ํ ์ฌ์ฉ๋
Claude.ai ๊ตฌ๋
์๋ ์ ํ ์ฌ์ฉ๋์ ์ํ ํ์์ค์ ํ์ํฉ๋๋ค. rate_limits ๊ฐ์ฒด์๋ five_hour(5์๊ฐ ๋กค๋ง ์๋์ฐ) ๋ฐ seven_day(์ฃผ๊ฐ) ์๋์ฐ๊ฐ ํฌํจ๋ฉ๋๋ค. ๊ฐ ์๋์ฐ๋ used_percentage(0-100) ๋ฐ resets_at(์๋์ฐ๊ฐ ์ฌ์ค์ ๋๋ Unix epoch ์ด)๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ด ํ๋๋ Claude.ai ๊ตฌ๋ ์(Pro/Max)์ ๊ฒฝ์ฐ ์ฒซ ๋ฒ์งธ API ์๋ต ํ์๋ง ๋ํ๋ฉ๋๋ค. ๊ฐ ์คํฌ๋ฆฝํธ๋ ๋ถ์ฌํ ํ๋๋ฅผ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํฉ๋๋ค:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
# "// empty"๋ rate_limits์ด ์์ ๋ ์ถ๋ ฅ์ ์์ฑํ์ง ์์ต๋๋ค
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
LIMITS=""
[ -n "$FIVE_H" ] && LIMITS="5h: $(printf '%.0f' "$FIVE_H")%"
[ -n "$WEEK" ] && LIMITS="${LIMITS:+$LIMITS }7d: $(printf '%.0f' "$WEEK")%"
[ -n "$LIMITS" ] && echo "[$MODEL] | $LIMITS" || echo "[$MODEL]"
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
model = data['model']['display_name']
parts = []
rate = data.get('rate_limits', {})
five_h = rate.get('five_hour', {}).get('used_percentage')
week = rate.get('seven_day', {}).get('used_percentage')
if five_h is not None:
parts.append(f"5h: {five_h:.0f}%")
if week is not None:
parts.append(f"7d: {week:.0f}%")
if parts:
print(f"[{model}] | {' '.join(parts)}")
else:
print(f"[{model}]")
#!/usr/bin/env node
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
const parts = [];
const fiveH = data.rate_limits?.five_hour?.used_percentage;
const week = data.rate_limits?.seven_day?.used_percentage;
if (fiveH != null) parts.push(`5h: ${Math.round(fiveH)}%`);
if (week != null) parts.push(`7d: ${Math.round(week)}%`);
console.log(parts.length ? `[${model}] | ${parts.join(' ')}` : `[${model}]`);
});
๋น์ฉ์ด ๋ง์ด ๋๋ ์์ ์บ์ฑ
์ํ ํ์์ค ์คํฌ๋ฆฝํธ๋ ํ์ฑ ์ธ์
์ค์ ์์ฃผ ์คํ๋ฉ๋๋ค. git status ๋๋ git diff์ ๊ฐ์ ๋ช
๋ น์ ํนํ ํฐ ์ ์ฅ์์์ ๋๋ฆด ์ ์์ต๋๋ค. ์ด ์์ ๋ git ์ ๋ณด๋ฅผ ์์ ํ์ผ์ ์บ์ฑํ๊ณ 5์ด๋ง๋ค๋ง ์๋ก ๊ณ ์นฉ๋๋ค.
์บ์ ํ์ผ ์ด๋ฆ์ ์ธ์
๋ด ์ํ ํ์์ค ํธ์ถ ๊ฐ์ ์์ ์ ์ด์ด์ผ ํ์ง๋ง ๋์ ์ธ์
์ด ์๋ก์ ์บ์๋ git ์ํ๋ฅผ ์ฝ์ง ์๋๋ก ์ธ์
๊ฐ์ ๊ณ ์ ํด์ผ ํฉ๋๋ค. $$, os.getpid() ๋๋ process.pid์ ๊ฐ์ ํ๋ก์ธ์ค ๊ธฐ๋ฐ ์๋ณ์๋ ๋ชจ๋ ํธ์ถ์์ ๋ณ๊ฒฝ๋๊ณ ์บ์๋ฅผ ๋ฌดํจํํฉ๋๋ค. ๋์ JSON ์
๋ ฅ์ session_id๋ฅผ ์ฌ์ฉํ์ธ์: ์ธ์
์ ์๋ช
๋์ ์์ ์ ์ด๊ณ ์ธ์
๋น ๊ณ ์ ํฉ๋๋ค.
๊ฐ ์คํฌ๋ฆฝํธ๋ git ๋ช ๋ น์ ์คํํ๊ธฐ ์ ์ ์บ์ ํ์ผ์ด ๋๋ฝ๋์๊ฑฐ๋ 5์ด๋ณด๋ค ์ค๋๋์๋์ง ํ์ธํฉ๋๋ค:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
SESSION_ID=$(echo "$input" | jq -r '.session_id')
CACHE_FILE="/tmp/statusline-git-cache-$SESSION_ID"
CACHE_MAX_AGE=5 # seconds
cache_is_stale() {
[ ! -f "$CACHE_FILE" ] || \
# stat -f %m์ macOS, stat -c %Y๋ Linux
[ $(($(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0))) -gt $CACHE_MAX_AGE ]
}
if cache_is_stale; then
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
echo "$BRANCH|$STAGED|$MODIFIED" > "$CACHE_FILE"
else
echo "||" > "$CACHE_FILE"
fi
fi
IFS='|' read -r BRANCH STAGED MODIFIED < "$CACHE_FILE"
if [ -n "$BRANCH" ]; then
echo "[$MODEL] ๐ ${DIR##*/} | ๐ฟ $BRANCH +$STAGED ~$MODIFIED"
else
echo "[$MODEL] ๐ ${DIR##*/}"
fi
#!/usr/bin/env python3
import json, sys, subprocess, os, time
data = json.load(sys.stdin)
model = data['model']['display_name']
directory = os.path.basename(data['workspace']['current_dir'])
session_id = data['session_id']
CACHE_FILE = f"/tmp/statusline-git-cache-{session_id}"
CACHE_MAX_AGE = 5 # seconds
def cache_is_stale():
if not os.path.exists(CACHE_FILE):
return True
return time.time() - os.path.getmtime(CACHE_FILE) > CACHE_MAX_AGE
if cache_is_stale():
try:
subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL)
branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True).strip()
staged = subprocess.check_output(['git', 'diff', '--cached', '--numstat'], text=True).strip()
modified = subprocess.check_output(['git', 'diff', '--numstat'], text=True).strip()
staged_count = len(staged.split('\n')) if staged else 0
modified_count = len(modified.split('\n')) if modified else 0
with open(CACHE_FILE, 'w') as f:
f.write(f"{branch}|{staged_count}|{modified_count}")
except:
with open(CACHE_FILE, 'w') as f:
f.write("||")
with open(CACHE_FILE) as f:
branch, staged, modified = f.read().strip().split('|')
if branch:
print(f"[{model}] ๐ {directory} | ๐ฟ {branch} +{staged} ~{modified}")
else:
print(f"[{model}] ๐ {directory}")
#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
const model = data.model.display_name;
const dir = path.basename(data.workspace.current_dir);
const sessionId = data.session_id;
const CACHE_FILE = `/tmp/statusline-git-cache-${sessionId}`;
const CACHE_MAX_AGE = 5; // seconds
const cacheIsStale = () => {
if (!fs.existsSync(CACHE_FILE)) return true;
return (Date.now() / 1000) - fs.statSync(CACHE_FILE).mtimeMs / 1000 > CACHE_MAX_AGE;
};
if (cacheIsStale()) {
try {
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
const staged = execSync('git diff --cached --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
const modified = execSync('git diff --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
fs.writeFileSync(CACHE_FILE, `${branch}|${staged}|${modified}`);
} catch {
fs.writeFileSync(CACHE_FILE, '||');
}
}
const [branch, staged, modified] = fs.readFileSync(CACHE_FILE, 'utf8').trim().split('|');
if (branch) {
console.log(`[${model}] ๐ ${dir} | ๐ฟ ${branch} +${staged} ~${modified}`);
} else {
console.log(`[${model}] ๐ ${dir}`);
}
});
Windows ๊ตฌ์ฑ
Windows์์ Claude Code๋ Git Bash๊ฐ ์ค์น๋์ด ์์ ๋ Git Bash๋ฅผ ํตํด ์ํ ํ์์ค ๋ช ๋ น์ ์คํํ๊ฑฐ๋, Git Bash๊ฐ ์์ ๋ PowerShell์ ํตํด ์คํํฉ๋๋ค.
Git Bash๋ ๋ฐ์ดํ ์๋ ๋ฐฑ์ฌ๋์๋ฅผ ์ด์ค์ผ์ดํ ๋ฌธ์๋ก ์ทจ๊ธํ๋ฏ๋ก, C:\Users\username\script.mjs์ ๊ฐ์ Windows ์คํ์ผ ๊ฒฝ๋ก๋ ๊ตฌ๋ถ ๊ธฐํธ๊ฐ ์ ๊ฑฐ๋ ์ํ๋ก ์คํฌ๋ฆฝํธ ์คํ๊ธฐ์ ๋๋ฌํ๊ณ ๋ช
๋ น์ด ๋ณด์ด๋ ์ค๋ฅ ์์ด ์คํจํฉ๋๋ค. command ๋ฌธ์์ด์ ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ ๋ฐฉํฅ ์ฌ๋์๋ก ์์ฑํ์ธ์(์๋ ์์ ์ ํ์๋จ). ~ ์ฝ์๋ ์๋ํ๋ฉฐ Windows ํ ๋๋ ํ ๋ฆฌ๋ก ํ์ฅ๋ฉ๋๋ค.
PowerShell ์คํฌ๋ฆฝํธ๋ฅผ ์ํ ํ์์ค๋ก ์คํํ๋ ค๋ฉด powershell์ ํตํด ํธ์ถํ์ธ์. ์ด๋ Claude Code๊ฐ ๋ช
๋ น์ Git Bash ๋๋ PowerShell์ ํตํด ๋ผ์ฐํ
ํ๋์ง ์ฌ๋ถ์ ๊ด๊ณ์์ด ์๋ํฉ๋๋ค:
{
"statusLine": {
"type": "command",
"command": "powershell -NoProfile -File C:/Users/username/.claude/statusline.ps1"
}
}
$input_json = $input | Out-String | ConvertFrom-Json
$cwd = $input_json.cwd
$model = $input_json.model.display_name
$used = $input_json.context_window.used_percentage
$dirname = Split-Path $cwd -Leaf
if ($used) {
Write-Host "$dirname [$model] ctx: $used%"
} else {
Write-Host "$dirname [$model]"
}
๋๋ Git Bash๊ฐ ์ค์น๋์ด ์์ ๋ Bash ์คํฌ๋ฆฝํธ๋ฅผ ์ง์ ์คํํฉ๋๋ค:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
}
#!/usr/bin/env bash
input=$(cat)
cwd=$(echo "$input" | grep -o '"cwd":"[^"]*"' | cut -d'"' -f4)
model=$(echo "$input" | grep -o '"display_name":"[^"]*"' | cut -d'"' -f4)
dirname="${cwd##*[/\\]}"
echo "$dirname [$model]"
์๋ธ์์ด์ ํธ ์ํ ํ์์ค
subagentStatusLine ์ค์ ์ ์๋ธ์์ด์ ํธ ํจ๋์ ํ์๋ ๊ฐ ์๋ธ์์ด์ ํธ์ ๋ํ ์ฌ์ฉ์ ์ ์ ํ ๋ณธ๋ฌธ์ ๋ ๋๋งํฉ๋๋ค. ๊ธฐ๋ณธ name ยท description ยท token count ํ์ ์์ ์ ํ์์ผ๋ก ๋ฐ๊พธ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค.
{
"subagentStatusLine": {
"type": "command",
"command": "~/.claude/subagent-statusline.sh"
}
}
๋ช
๋ น์ ์๋ก ๊ณ ์นจ ํฑ๋ง๋ค ํ ๋ฒ ์คํ๋๋ฉฐ ๋ชจ๋ ํ์ ๊ฐ๋ฅํ ์๋ธ์์ด์ ํธ ํ์ด stdin์ ๋จ์ผ JSON ๊ฐ์ฒด๋ก ์ ๋ฌ๋ฉ๋๋ค. ์
๋ ฅ์๋ ๊ธฐ๋ณธ ํ
ํ๋์ columns(์ฌ์ฉ ๊ฐ๋ฅํ ํ ๋๋น) ๋ฐ tasks ๋ฐฐ์ด์ด ํฌํจ๋๋ฉฐ, ๊ฐ ์์
์๋ id, name, type, status, description, label, startTime, tokenCount, tokenSamples ๋ฐ cwd๊ฐ ์์ต๋๋ค.
์ฌ์ ์ํ๋ ค๋ ๊ฐ ํ์ ๋ํด stdout์ ํ ์ค์ JSON์ ์์ฑํฉ๋๋ค: {"id": "<task id>", "content": "<row body>"}. content ๋ฌธ์์ด์ ANSI ์์ ๋ฐ OSC 8 ํ์ดํผ๋งํฌ๋ฅผ ํฌํจํ์ฌ ๊ทธ๋๋ก ๋ ๋๋ง๋ฉ๋๋ค. ์์
์ id๋ฅผ ์๋ตํ์ฌ ํด๋น ํ์ ๊ธฐ๋ณธ ๋ ๋๋ง์ ์ ์งํฉ๋๋ค. ๋น content ๋ฌธ์์ด์ ๋ด๋ณด๋ด ์จ๊น๋๋ค.
statusLine์ ์ ์ฉ๋๋ ๋์ผํ ์ ๋ขฐ ๋ฐ disableAllHooks ๊ฒ์ดํธ๊ฐ ์ฌ๊ธฐ์ ์ ์ฉ๋ฉ๋๋ค. ํ๋ฌ๊ทธ์ธ์ settings.json์์ ๊ธฐ๋ณธ subagentStatusLine์ ์ ๊ณตํ ์ ์์ต๋๋ค.
ํ
- ๋ชจ์ ์
๋ ฅ์ผ๋ก ํ
์คํธ:
echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/user/project"},"context_window":{"used_percentage":25},"session_id":"test-session-abc"}' | ./statusline.sh - ์ถ๋ ฅ์ ์งง๊ฒ ์ ์ง: ์ํ ํ์์ค์ ๋๋น๊ฐ ์ ํ๋์ด ์์ผ๋ฏ๋ก ๊ธด ์ถ๋ ฅ์ด ์๋ฆฌ๊ฑฐ๋ ์ด์ํ๊ฒ ์ค ๋ฐ๊ฟ๋ ์ ์์ต๋๋ค
- ๋๋ฆฐ ์์
์บ์ฑ: ์คํฌ๋ฆฝํธ๋ ํ์ฑ ์ธ์
์ค์ ์์ฃผ ์คํ๋๋ฏ๋ก
git status์ ๊ฐ์ ๋ช ๋ น์ด ์ง์ฐ์ ์ ๋ฐํ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์บ์ฑ ์์ ๋ฅผ ์ฐธ์กฐํ์ธ์.
ccstatusline ๋ฐ starship-claude์ ๊ฐ์ ์ปค๋ฎค๋ํฐ ํ๋ก์ ํธ๋ ํ ๋ง ๋ฐ ์ถ๊ฐ ๊ธฐ๋ฅ์ด ์๋ ์ฌ์ ๊ตฌ์ถ๋ ๊ตฌ์ฑ์ ์ ๊ณตํฉ๋๋ค.
๋ฌธ์ ํด๊ฒฐ
์ํ ํ์์ค์ด ๋ํ๋์ง ์์
- ์คํฌ๋ฆฝํธ๊ฐ ์คํ ๊ฐ๋ฅํ์ง ํ์ธํฉ๋๋ค:
chmod +x ~/.claude/statusline.sh - ์คํฌ๋ฆฝํธ๊ฐ stderr๊ฐ ์๋ stdout์ผ๋ก ์ถ๋ ฅํ๋์ง ํ์ธํฉ๋๋ค
- ์คํฌ๋ฆฝํธ๋ฅผ ์๋์ผ๋ก ์คํํ์ฌ ์ถ๋ ฅ์ ์์ฑํ๋์ง ํ์ธํฉ๋๋ค
- Git Bash๊ฐ ์ค์น๋ Windows์์๋
command๊ฒฝ๋ก์ ๋ฐฑ์ฌ๋์๊ฐ ์คํฌ๋ฆฝํธ ์คํ ์ ์ ์ด์ค์ผ์ดํ ๋ฌธ์๋ก ์ฒ๋ฆฌ๋ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ๊ฒฝ๋ก์์ ์ฌ๋์๋ฅผ ์ฌ์ฉํฉ๋๋ค. Windows ๊ตฌ์ฑ์ ์ฐธ์กฐํฉ๋๋ค. - ์ค์ ์์
disableAllHooks๊ฐtrue๋ก ์ค์ ๋์ด ์์ผ๋ฉด ์ํ ํ์์ค๋ ๋นํ์ฑํ๋ฉ๋๋ค. ์ด ์ค์ ์ ์ ๊ฑฐํ๊ฑฐ๋false๋ก ์ค์ ํ์ฌ ๋ค์ ํ์ฑํํฉ๋๋ค. claude --debug๋ฅผ ์คํํ์ฌ ์ธ์ ์ ์ฒซ ๋ฒ์งธ ์ํ ํ์์ค ํธ์ถ์์ ์ข ๋ฃ ์ฝ๋ ๋ฐ stderr๋ฅผ ๊ธฐ๋กํฉ๋๋ค- Claude์ ์ค์ ํ์ผ์ ์ฝ๊ณ
statusLine๋ช ๋ น์ ์ง์ ์คํํ๋๋ก ์์ฒญํ์ฌ ์ค๋ฅ๋ฅผ ํ์ํฉ๋๋ค
์ํ ํ์์ค์ด -- ๋๋ ๋น ๊ฐ์ ํ์ํจ
- ํ๋๋ ์ฒซ ๋ฒ์งธ API ์๋ต์ด ์๋ฃ๋๊ธฐ ์ ์
null์ผ ์ ์์ต๋๋ค - jq์
// 0๊ณผ ๊ฐ์ ํด๋ฐฑ์ผ๋ก ์คํฌ๋ฆฝํธ์์ null ๊ฐ์ ์ฒ๋ฆฌํฉ๋๋ค - ์ฌ๋ฌ ๋ฉ์์ง ํ์๋ ๊ฐ์ด ๋น์ด ์์ผ๋ฉด Claude Code๋ฅผ ๋ค์ ์์ํฉ๋๋ค
์ปจํ ์คํธ ๋ฐฑ๋ถ์จ์ด ์์์น ๋ชปํ ๊ฐ์ ํ์ํจ
- ๊ฐ์ฅ ๊ฐ๋จํ ์ ํํ ์ปจํ
์คํธ ์ํ๋ฅผ ์ํด
used_percentage๋ฅผ ์ฌ์ฉํฉ๋๋ค - ๊ฐ๊ฐ์ด ๊ณ์ฐ๋๋ ์๊ธฐ๋ก ์ธํด ์ปจํ
์คํธ ๋ฐฑ๋ถ์จ์ด
/context์ถ๋ ฅ๊ณผ ๋ค๋ฅผ ์ ์์ต๋๋ค
OSC 8 ๋งํฌ๋ฅผ ํด๋ฆญํ ์ ์์
-
ํฐ๋ฏธ๋์ด OSC 8 ํ์ดํผ๋งํฌ๋ฅผ ์ง์ํ๋์ง ํ์ธํฉ๋๋ค(iTerm2, Kitty, WezTerm)
-
Terminal.app์ ํด๋ฆญ ๊ฐ๋ฅํ ๋งํฌ๋ฅผ ์ง์ํ์ง ์์ต๋๋ค
-
๋งํฌ ํ ์คํธ๊ฐ ๋ํ๋์ง๋ง ํด๋ฆญํ ์ ์์ผ๋ฉด Claude Code๊ฐ ํฐ๋ฏธ๋์์ ํ์ดํผ๋งํฌ ์ง์์ ๊ฐ์งํ์ง ๋ชปํ์ ์ ์์ต๋๋ค. ์ด๋ ์๋ ๊ฐ์ง ๋ชฉ๋ก์ ์๋ Windows Terminal ๋ฐ ๊ธฐํ ์๋ฎฌ๋ ์ดํฐ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. Claude Code๋ฅผ ์์ํ๊ธฐ ์ ์
FORCE_HYPERLINKํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ์ฌ ๊ฐ์ง๋ฅผ ์ฌ์ ์ํฉ๋๋ค:FORCE_HYPERLINK=1 claudePowerShell์์๋ ๋จผ์ ํ์ฌ ์ธ์ ์์ ๋ณ์๋ฅผ ์ค์ ํฉ๋๋ค:
$env:FORCE_HYPERLINK = "1"; claude -
SSH ๋ฐ tmux ์ธ์ ์ ๊ตฌ์ฑ์ ๋ฐ๋ผ OSC ์ํ์ค๋ฅผ ์ ๊ฑฐํ ์ ์์ต๋๋ค
-
\e]8;;๊ณผ ๊ฐ์ ๋ฆฌํฐ๋ด ํ ์คํธ๋ก ์ด์ค์ผ์ดํ ์ํ์ค๊ฐ ๋ํ๋๋ฉดecho -e๋์printf '%b'๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์์ ์ ์ธ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค
์ด์ค์ผ์ดํ ์ํ์ค๋ก ์ธํ ๋์คํ๋ ์ด ๊ฒฐํจ
- ๋ณต์กํ ์ด์ค์ผ์ดํ ์ํ์ค(ANSI ์์, OSC 8 ๋งํฌ)๋ ๋ค๋ฅธ UI ์ ๋ฐ์ดํธ์ ๊ฒน์น๋ฉด ๊ฐ๋ ์์๋ ์ถ๋ ฅ์ ์ ๋ฐํ ์ ์์ต๋๋ค
- ์์๋ ํ ์คํธ๊ฐ ๋ณด์ด๋ฉด ์คํฌ๋ฆฝํธ๋ฅผ ์ผ๋ฐ ํ ์คํธ ์ถ๋ ฅ์ผ๋ก ๋จ์ํํด ๋ด ๋๋ค
- ์ด์ค์ผ์ดํ ์ฝ๋๊ฐ ์๋ ๋ค์ค ์ค ์ํ ํ์์ค์ ์ผ๋ฐ ํ ์คํธ ๋จ์ผ ์ค๋ณด๋ค ๋ ๋๋ง ๋ฌธ์ ๊ฐ ๋ ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค
์ํฌ์คํ์ด์ค ์ ๋ขฐ ํ์
- ์ํ ํ์์ค ๋ช
๋ น์ ํ์ฌ ๋๋ ํ ๋ฆฌ์ ๋ํ ์ํฌ์คํ์ด์ค ์ ๋ขฐ ๋ํ๋ฅผ ์๋ฝํ ๊ฒฝ์ฐ์๋ง ์คํ๋ฉ๋๋ค.
statusLine์ด ์ ธ ๋ช ๋ น์ ์คํํ๋ฏ๋ก ํ ๋ฐ ๊ธฐํ ์ ธ ์คํ ์ค์ ๊ณผ ๋์ผํ ์ ๋ขฐ ์๋ฝ์ด ํ์ํฉ๋๋ค. - ์ ๋ขฐ๊ฐ ์๋ฝ๋์ง ์์ผ๋ฉด ์ํ ํ์์ค ์ถ๋ ฅ ๋์
statusline skipped ยท restart to fix์๋ฆผ์ด ํ์๋ฉ๋๋ค. Claude Code๋ฅผ ๋ค์ ์์ํ๊ณ ์ ๋ขฐ ํ๋กฌํํธ๋ฅผ ์๋ฝํ์ฌ ํ์ฑํํฉ๋๋ค.
์คํฌ๋ฆฝํธ ์ค๋ฅ ๋๋ ์ค๋จ
- 0์ด ์๋ ์ฝ๋๋ก ์ข ๋ฃ๋๊ฑฐ๋ ์ถ๋ ฅ์ ์์ฑํ์ง ์๋ ์คํฌ๋ฆฝํธ๋ ์ํ ํ์์ค์ ๊ณต๋ฐฑ์ผ๋ก ๋ง๋ญ๋๋ค
- ๋๋ฆฐ ์คํฌ๋ฆฝํธ๋ ์๋ฃ๋ ๋๊น์ง ์ํ ํ์์ค์ด ์ ๋ฐ์ดํธ๋์ง ์๋๋ก ์ฐจ๋จํฉ๋๋ค. ์ค๋๋ ์ถ๋ ฅ์ ํผํ๋ ค๋ฉด ์คํฌ๋ฆฝํธ๋ฅผ ๋น ๋ฅด๊ฒ ์ ์งํฉ๋๋ค
- ๋๋ฆฐ ์คํฌ๋ฆฝํธ๊ฐ ์คํ ์ค์ธ ๋์ ์ ์ ๋ฐ์ดํธ๊ฐ ํธ๋ฆฌ๊ฑฐ๋๋ฉด ์งํ ์ค์ธ ์คํฌ๋ฆฝํธ๊ฐ ์ทจ์๋ฉ๋๋ค
- ๊ตฌ์ฑํ๊ธฐ ์ ์ ๋ชจ์ ์ ๋ ฅ์ผ๋ก ์คํฌ๋ฆฝํธ๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธํฉ๋๋ค
์๋ฆผ์ด ์ํ ํ์์ค ํ์ ๊ณต์ ํจ
- MCP ์๋ฒ ์ค๋ฅ ๋ฐ ์๋ ์ ๋ฐ์ดํธ์ ๊ฐ์ ์์คํ ์๋ฆผ์ ์ํ ํ์์ค๊ณผ ๋์ผํ ํ์ ์ค๋ฅธ์ชฝ์ ํ์๋ฉ๋๋ค. ์ปจํ ์คํธ ๋ถ์กฑ ๊ฒฝ๊ณ ์ ๊ฐ์ ์ผ์์ ์๋ฆผ๋ ์ด ์์ญ์ ์ํํฉ๋๋ค.
- ์์ธํ ๋ชจ๋๋ฅผ ํ์ฑํํ๋ฉด ์ด ์์ญ์ ํ ํฐ ์นด์ดํฐ๊ฐ ์ถ๊ฐ๋ฉ๋๋ค
- ์ข์ ํฐ๋ฏธ๋์์ ์ด๋ฌํ ์๋ฆผ์ด ์ํ ํ์์ค ์ถ๋ ฅ์ ์๋ฅผ ์ ์์ต๋๋ค