CyberPunk/BladeRunner styling. Two lines with kanji field labels: repo 庫, branch 枝, worktree 樹, and model 脳 on top; context 文, 5-hour 時, and weekly 週 meters below. The rate-limit bars carry a ┃ pace cursor marking how far through the reset window you are — cyan when your usage trails the cursor (on track), amber/red when it outruns it.
Updated 2026-07-10
Preview
Clean repo
庫 app · 枝 main · 脳 OPUS-4.8
文 ▓▓░░░░░░ 22% · 時 ▓▓░░┃░░░ 26% · 週 ▓░░░░┃░░ 7%
New session
庫 app · 枝 main · 脳 OPUS-4.8
文 ░░░░░░░░ -- · 時 ░░░░░░░░ -- · 週 ░░░░░░░░ --
Dirty branch
庫 app · 枝 feat/auth · 樹 feat-auth · 脳 SONNET-4.6
文 ▓▓▓▓░░░░ 48% · 時 ▓▓▓░░┃░░ 40% · 週 ▓░░░┃░░░ 18%
Near-full
庫 app · 枝 main · 脳 OPUS-4.8
文 ▓▓▓▓▓▓▓░ 91% · 時 ▓▓▓▓▓▓▓┃ 88% · 週 ▓▓▓▓▓░┃░ 61%
1M context
庫 app · 枝 main · 脳 FABLE-5
文 ▓▓▓▓▓░░░ 64% · 時 ▓┃▓░░░░░ 33% · 週 ▓┃░░░░░░ 12%
Post-compact
庫 app · 枝 main · 脳 HAIKU-4.5
文 ░░░░░░░░ -- · 時 ▓┃▓▓░░░░ 52% · 週 ░░░░░░░░ --
Worktree
庫 app · 枝 worktree-feature · 樹 feature · 脳 OPUS-4.8
文 ▓▓▓░░░░░ 37% · 時 ▓▓┃▓░░░░ 44% · 週 ▓▓░░░┃░░ 20%
Non-git
脳 OPUS-4.8
文 ▓▓░░░░░░ 22% · 時 ░░░░░░░░ -- · 週 ░░░░░░░░ --
Source
#!/usr/bin/env bash
# Kanji Rain — statusline for Claude Code.
#
# Claude Code pipes session JSON on stdin (see ~/.claude/settings.json
# "statusLine"); this prints two ANSI-colored lines:
#
# 庫 repo · 枝 branch · 樹 worktree · 脳 model
# 文 context ▓▓▓░░░┃░ 42% · 時 5h · 週 weekly
#
# Rate-limit meters (時/週) carry a ┃ pace cursor marking how far through the
# reset window you are: fill behind the cursor means you're consuming slower
# than the window refills (on track, cyan); fill past it means you're outrunning
# it (amber, then red for way over). Absolute floors still apply — >= 85% used
# is always red no matter the pace. The context meter (文) has no window, so it
# uses plain 60/85 thresholds. Fields with no data (rate limits before the
# first response, context at session start) render as a dim empty bar. The 樹
# worktree segment only appears inside a linked worktree; 庫/枝/樹 all
# disappear outside a git repo.
#
# Run it from a terminal (no piped stdin) for a preview of the
# normal / critical / no-data states. Requires jq; git is optional.
# ── Palette (truecolor) ───────────────────────────────────────────────────────
MAG=$'\033[38;2;255;79;208m' # kanji field icons
CYN=$'\033[38;2;77;240;224m' # values, healthy meters
AMB=$'\033[38;2;255;180;84m' # meters >= 60%
RED=$'\033[38;2;255;75;87m' # meters >= 85%
WHT=$'\033[38;2;232;238;250m' # model name
DIM=$'\033[38;2;74;88;113m' # separators, missing data
RST=$'\033[0m'
SEP=" ${DIM}·${RST} "
BAR_CELLS=8
# ▓/░ render one column wide in every monospace font; fancier glyphs like ▰/▱
# are East-Asian-ambiguous width and overlap in many terminal fonts.
FILL="▓"
EMPTY="░"
MARK="┃"
# ── Meter: "▓▓▓░░░┃░ 42%" ─────────────────────────────────────────────────────
# $1 usage pct (-1 = no data), $2 elapsed pct of the reset window (-1 = none).
# With a window, the ┃ cursor marks "now" and color keys off usage - elapsed:
# on pace cyan, > 5 over amber, > 20 over red, with absolute floors at 70/85.
# Without one, plain 60/85 thresholds.
meter() {
local pct=${1:--1} elapsed=${2:--1} color filled mark=0 i bar=""
if (( pct < 0 )); then
for (( i = 1; i <= BAR_CELLS; i++ )); do bar+="$EMPTY"; done
printf '%s' "${DIM}${bar} --${RST}"
return
fi
(( pct > 100 )) && pct=100
local sev=0 # 0 cyan · 1 amber · 2 red
if (( elapsed >= 0 )); then
local over=$(( pct - elapsed ))
if (( over > 20 )); then sev=2
elif (( over > 5 )); then sev=1
fi
(( pct >= 70 && sev < 1 )) && sev=1
(( pct >= 85 )) && sev=2
mark=$(( (elapsed * BAR_CELLS + 50) / 100 ))
(( mark < 1 )) && mark=1
(( mark > BAR_CELLS )) && mark=BAR_CELLS
else
(( pct >= 60 )) && sev=1
(( pct >= 85 )) && sev=2
fi
case $sev in
2) color=$RED ;;
1) color=$AMB ;;
*) color=$CYN ;;
esac
filled=$(( (pct * BAR_CELLS + 50) / 100 ))
for (( i = 1; i <= BAR_CELLS; i++ )); do
if (( i == mark )); then bar+="${WHT}${MARK}${color}"
elif (( i <= filled )); then bar+="$FILL"
else bar+="$EMPTY"
fi
done
printf '%s' "${color}${bar} ${pct}%${RST}"
}
# ── Elapsed % of a reset window, from its resets_at epoch ─────────────────────
# $1 resets_at (-1 = unknown), $2 window length in seconds.
elapsed_pct() {
local resets_at=${1:--1} window=$2 remaining now=${EPOCHSECONDS:-$(date +%s)}
if (( resets_at <= 0 )); then printf '%s' -1; return; fi
remaining=$(( resets_at - now ))
(( remaining < 0 )) && remaining=0
(( remaining > window )) && remaining=window
printf '%s' $(( (window - remaining) * 100 / window ))
}
# ── Render one statusline from a JSON payload ─────────────────────────────────
render() {
local json="$1"
local fields model dir wt repo ctx five wk five_reset wk_reset branch
fields=$(printf '%s' "$json" | jq -r '[
(.model.display_name? // .model.id? // "claude"),
(.workspace.current_dir? // .cwd? // ""),
(.workspace.git_worktree? // .worktree.name? // ""),
(.workspace.repo.name? // ""),
((.context_window.used_percentage? // -1) | round),
((.rate_limits.five_hour.used_percentage? // -1) | round),
((.rate_limits.seven_day.used_percentage? // -1) | round),
((.rate_limits.five_hour.resets_at? // -1) | round),
((.rate_limits.seven_day.resets_at? // -1) | round)
] | map(tostring) | join("\u001f")' 2>/dev/null) \
|| { printf '%s\n' "${DIM}claude${RST}"; return; }
# unit separator, not tab: tab is IFS whitespace, so empty fields (e.g. no
# worktree) would collapse and shift everything after them
IFS=$'\x1f' read -r model dir wt repo ctx five wk five_reset wk_reset <<< "$fields"
branch=""
if [[ -n "$dir" ]]; then
branch=$(command git -C "$dir" symbolic-ref --short -q HEAD 2>/dev/null) \
|| branch=$(command git -C "$dir" rev-parse --short HEAD 2>/dev/null)
fi
if [[ -n "$branch" ]]; then
local git_dir common_dir
git_dir=$(command git -C "$dir" rev-parse --absolute-git-dir 2>/dev/null)
common_dir=$(command git -C "$dir" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
# the common dir is <repo>/.git even inside a worktree, so its parent names
# the repo when workspace.repo.name is absent (e.g. no GitHub remote)
if [[ -z "$repo" && -n "$common_dir" ]]; then
repo=$(basename "$(dirname "$common_dir")")
fi
# Claude Code only fills workspace.git_worktree for worktrees it created;
# detect hand-made ones (git worktree add) by comparing git dirs.
if [[ -z "$wt" && -n "$git_dir" && "$git_dir" != "$common_dir" ]]; then
wt=$(basename "$(command git -C "$dir" rev-parse --show-toplevel 2>/dev/null)")
fi
fi
# tr, not ${model^^}: macOS ships bash 3.2, which lacks case conversion
model=$(printf '%s' "$model" | tr '[:lower:]' '[:upper:]')
model=${model// /-}
local -a parts=() meters=()
[[ -n "$repo" ]] && parts+=("${MAG}庫${RST} ${CYN}${repo}${RST}")
[[ -n "$branch" ]] && parts+=("${MAG}枝${RST} ${CYN}${branch}${RST}")
[[ -n "$wt" ]] && parts+=("${MAG}樹${RST} ${CYN}${wt}${RST}")
parts+=("${MAG}脳${RST} ${WHT}${model}${RST}")
meters+=("${MAG}文${RST} $(meter "$ctx")")
meters+=("${MAG}時${RST} $(meter "$five" "$(elapsed_pct "$five_reset" $(( 5 * 3600 )))")")
meters+=("${MAG}週${RST} $(meter "$wk" "$(elapsed_pct "$wk_reset" $(( 7 * 86400 )))")")
join_line "${parts[@]}"
join_line "${meters[@]}"
}
# ── Join segments with the dim separator and print as one line ────────────────
join_line() {
local line="" part
for part in "$@"; do
[[ -n "$line" ]] && line+="$SEP"
line+="$part"
done
printf '%s\n' "$line"
}
# ── Preview mode: sample states, using the real repo for branch info ─────────
preview() {
local base now=${EPOCHSECONDS:-$(date +%s)}
base=$(printf '{"model":{"display_name":"Fable 5"},"workspace":{"current_dir":"%s"}}' "$PWD")
# on track: 5h at 31% used / 75% elapsed, weekly at 68% used / 80% elapsed
printf 'on track\n'
render "$(printf '%s' "$base" | jq --argjson now "$now" '. + {
context_window:{used_percentage:42},
rate_limits:{five_hour:{used_percentage:31, resets_at:($now + 4500)},
seven_day:{used_percentage:68, resets_at:($now + 120960)}}}')"
# burning hot: 5h at 91% used / 40% elapsed, weekly at 76% used / 50% elapsed
printf 'critical\n'
render "$(printf '%s' "$base" | jq --argjson now "$now" '.workspace.git_worktree="feat-statusline" | . + {
context_window:{used_percentage:74},
rate_limits:{five_hour:{used_percentage:91, resets_at:($now + 10800)},
seven_day:{used_percentage:76, resets_at:($now + 302400)}}}')"
printf 'no data\n'
render "$base"
}
if [[ "${1:-}" == "--test" || -t 0 ]]; then
preview
exit 0
fi
render "$(cat)"What it shows
- Repository name, from workspace.repo.name or the parent directory of the git common dir
- Current git branch, falling back to a short commit hash when HEAD is detached
- Git worktree name, shown only when the session is inside a linked worktree
- The model name in uppercase with spaces turned into hyphens, like OPUS-4.8
- Context window usage as an 8-cell bar with a percentage
- Five-hour rate limit usage as a bar and percentage, with a pace cursor marking how far the reset window has elapsed
- Seven-day rate limit usage with the same bar and pace cursor
Requirements
- Bash, and it stays compatible with the bash 3.2 that ships on macOS
- jq, which is required for parsing the session JSON
- git is optional, without it the repo, branch, and worktree segments just stay empty
- A terminal with truecolor ANSI support, since all colors are 24-bit escape codes
- A monospace font that renders the block characters and the kanji labels; no Nerd Font glyphs are used
- No network access, the script makes no network calls
Behavior notes
- Outside a git repo the whole top row collapses to just the model segment, as in the scratch-dir scenario
- The worktree segment appears only in worktree sessions, either from workspace.git_worktree or by detecting a hand-made worktree through differing git dirs
- Meters with no data (context right after /compact, rate limits before the first response) render as a dim empty bar with -- instead of a number
- Rate limit bars color by pace, cyan when usage trails the elapsed-time cursor, amber when it runs more than 5 points ahead, red past 20 over, with hard floors that force amber at 70% and red at 85% used
- The context bar has no reset window, so it switches to amber at 60% and red at 85%, which is why 64% shows amber and 91% shows red in the previews
- The white pace cursor moves along the rate limit bars as the reset window elapses, sitting near the left early in a window and near the right close to reset
- In the near-full scenario the five-hour bar goes red at 88% used even though its window has barely elapsed, from the 85% floor
- When only the five-hour rate limit is reported, the weekly meter alone falls back to the dim no-data bar
- Run with no piped stdin or with --test, it prints a preview of on-track, critical, and no-data sample states instead of reading a session
More status lines
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0h ✧ ███████████▋ 73% 🗝 Ultima Weapon ✦ app ∙ main
♥ ████████████████████████ 100% 「SAVE POINT」 ✶ Limit Form ◉ 41
╭─ CONTEXT ────────────────────────────────────────────╮
│ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │
├─ STATS ──────────────────────────────────────────────┤
│ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │
├─ REPO ───────────────────────────────────────────────┤
│ ◆ Opus 4.8 · main · 17:27 │
╰──────────────────────────────────────────────────────╯
Opus 4.8 | main | ██░░░░░░░░ 22.0% | 44k / 200k | 02:07 26% | Wed 7%
Opus 4.8 [high] ~/app main
17:27:05 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Opus 4.8 │ main │ app │ ▓▓░░░░░░░░ 44k/200k │ $0.41