What's the best Claude Code status line? โ
The best Claude Code status line is the one that answers, in a glance between prompts, the questions you actually act on: where are we, what are we using (which model, at what cost, and how close to a forced auto-compact), and what are our usage limits. Rendered, that is the whole answer:
~/projects/dotfiles | ๐ฑ main โ2 +45/-12 ?3 | ๐ฆ Fable 5 $0.42 24m โโโโ 42% 316k | โณ 5h โโโโ 31% 2h15m โ 7d โโโโ 68% Tue 09:00Which tool renders it is the secondary question. Get the signals right and a single shell script, jq and git its only dependencies, is enough to render them; the rest of this post builds exactly that line, section by section.
Search best Claude Code statusline and you land in a now-familiar arms race: powerline arrows, theme galleries, thirty-widget configurators, burn-rate dashboards. They are genuinely impressive projects, and this post gets to them. But it spends its weight where the leverage is: what belongs on the line, in what order of importance, and how to build exactly that yourself.
How the status line works โ
Claude Code renders whatever one command prints. You set statusLine.command in ~/.claude/settings.json, and on each refresh Claude Code pipes session context as JSON to that command's stdin: the model, the workspace directory, cumulative session cost and duration, the transcript path, the context window fill, and on Pro/Max plans your 5-hour and 7-day rate-limit windows. The first line of stdout becomes the status line, ANSI colors included.
That contract has two consequences the tool listings skip:
- The command runs constantly. Its startup cost is paid on every refresh, all session long, in the terminal you are trying to work in.
- It receives everything it needs on stdin. A status line does not need a runtime, a package manager, or network access, it needs to reformat a JSON blob into one readable line.
What belongs on the line โ
Glanceability is a budget, and every segment spends from it, so rank before you render. The line answers three questions, and the order you ask them is the order they sit on the line, left to right:
- Where are we the directory and the git branch, whether the tree is dirty, and whether this is a linked worktree. When five terminals hold five worktrees this is what tells them apart, and your shell prompt is hidden while Claude Code owns the terminal, so the status line is the only thing answering it.
- What are we using the model and the session it runs: which model (Fable, Opus, and Haiku sessions look identical until the invoice), the session cost and duration (colored so drift gets noticed while it is still a coffee, not a dinner), and the context-window fill, which doubles as how close you are to a forced auto-compact. Context is the signal that is invisible until it hurts and only actionable before it hurts: compact, keep going, or hand off to a fresh session while there is still room, rather than discovering the wall against it.
- What are our limits the subscription usage windows, the 5-hour and the 7-day (Pro and Max). They pace work across a day and a week, the far-right gauge you glance at before starting something large, not mid-keystroke.
What does not belong: your battery, the weather, the time (your desktop has one), or seven shades of powerline chrome around each of them. If a segment never changes what you do next, it is spending your glance budget on nothing.
Build it: one shell script โ
Six rules that outlive the tool โ
Before any code, the rules. They outlive every tool choice on this page, so they apply whether you write bash or configure someone's widget engine:
- Trust the payload. Claude Code already computes cost, context fill, and rate limits. Tools that reconstruct these externally are how status lines ship wrong numbers.
- Run light. You execute on every refresh. No
npxresolution, no runtime boot, nothing slower than a fork and a pipe. - Never take locks. A status line that stalls
git rebaseby grabbing the index lock is sabotage on a timer. - Degrade, don't break. Older Claude Code, no
rate_limitson the API plan, a directory outside git: a segment should vanish, never error. A status line that prints garbage is worse than none. - Color is a threshold, not decoration. Green/yellow/red should mean something crossed a line you chose.
- Keep the layout stable. Fixed section order makes positions muscle memory, so the glance stays a glance.
What the script does โ
Applied, those rules produce a script with a simple anatomy: read stdin once, extract every field in a single jq pass with a default for each (a missing field renders as an absent section, not an error), make one git status --porcelain=v2 --no-optional-locks call for the whole git story, then print the sections that have something to say:
~/projects/dotfiles | ๐ฑ main โ2 +45/-12 ?3 | ๐ฆ Fable 5 $0.42 24m โโโโ 42% 316k | โณ 5h โโโโ 31% 2h15m โ 7d โโโโ 68% Tue 09:00Reading left to right: the path, the git section (branch colored by cleanliness, ahead/behind arrows, added/removed lines in green and red, untracked count), the claude section (a per-model icon, then cost colored by threshold, session duration, a context-window bar, and total session tokens), and the usage section (both subscription windows, the 5-hour with the time left before it resets, since that is the number you act on mid-session, the 7-day with its reset moment). In a linked worktree the branch icon becomes ๐ชด, a branch potted in its own directory, which is exactly the terminal-telling-apart problem worktrees create. Every icon also has a Nerd Font mono variant for emoji-free terminals.
Bars that read at a glance โ
The percentage bars deserve a word, because most status lines get them wrong in one of two directions: a raw number you have to read, or a bar so coarse it sits at empty until half the budget is gone. These fill each cell through the โ โ โ โ density ramp, so the boundary cell fades as the value climbs. The glyph choice is deliberate: the finer eighth-block set (โโโโโโโ) paints only the left fraction of a cell, leaving what reads as a seam of bare background in the middle of the bar, while every shade glyph owns its whole cell, so the bar stays gapless at any value. Low-but-nonzero progress registers at a glance, and the difference between 70% and 95% arrives as color, yellow then red, before you hit the wall. The sweep:
0% โโโโ
10% โโโโ
25% โโโโ
42% โโโโ
70% โโโโ
88% โโโโ
100% โโโโA countdown to auto-compact โ
The context bar answers "how full"; the question you actually act on is "how close to a forced auto-compact", and that number is nowhere in the payload. Claude Code triggers auto-compact a roughly fixed number of tokens below the window (a reservation for writing the summary plus a safety buffer, about 33k on recent versions), so the trigger sits near 83% of a 200k window but near 97% of a 1M one. A status line that warns at a flat percentage therefore nags one model and misses the other. The script derives the point instead, context_window_size minus the reserve, and once you're within 20 window points of it the session section grows a countdown to the compaction point:
63% โโโโ 63% โป๏ธ 20%
78% โโโโ 78% โป๏ธ 5%
83% โโโโ 83% โป๏ธ 0%Claude Code's own tuning knobs win when set: CLAUDE_AUTOCOMPACT_PCT_OVERRIDE replaces the derived trigger with a direct percentage, and CLAUDE_CODE_AUTO_COMPACT_WINDOW replaces the window the reserve is subtracted from. Both are environment variables (settable via the env block in settings.json), and since Claude Code runs the status line as a child process, the script reads the same values and mirrors whatever you've tuned. One honest caveat, twice over: both variables are undocumented, community reports differ on which Claude Code versions honor them faithfully, and the payload's used_percentage is known to under-report overhead, so the marker is a good approximation of the wall, not a survey of it. The full story (where the reserve comes from, why every source cites a different percentage, and what does and doesn't move the trigger) has a post of its own.
An icon per model โ
The per-model icon deserves one too, because it applies the same trick to the model signal. A model name is text you have to read; an icon is shape and color you notice before reading begins. Give each model its own mark, ๐ฆ Fable, ๐ผ Opus, ๐ Sonnet, ๐ธ Haiku, and the wrong one jumps out on its own: the terminal you forgot on Haiku after a quick /model detour announces itself from across the room. Mechanically it is a substring map over the model's display name, first matching key wins, and anything unmatched falls back to Claude's own โณ, so next month's model still renders.
The script, ready to copy โ
Before the listing, two of the rules earn specifics. Context fill comes from the payload's own context_window figure when Claude Code provides it, with a transcript-derived fallback for older versions (trust the payload). And there is no set -e anywhere: a malformed payload degrades to a bare path, never a blank bar (degrade, don't break).
Here is the whole thing, rendered with its default settings so it runs as-is โ jq and git its only dependencies, no dotfiles manager and no template step. The block below is the complete file; hit the copy button in its top-right corner and paste it into ~/.claude/statusline.sh. The icons, separator, and threshold colors are named variables at the top you can edit in place; the deeper tunables (section order, the auto-compact reserve and lead, bar, percent, or both) are baked to the defaults you see rendered above. In the claude role the same script is maintained as a template that lifts every one of those into a variable in your dotfiles repo.
The whole script โ copy-paste ready, runs as-is
#!/usr/bin/env bash
# Claude Code status line โ self-contained (jq + git only, no starship).
# Managed by the dotpro `claude` role (rendered from statusline.sh.j2). To
# customize: set claude_code_statusline_deploy=false and manage this file
# yourself, or override claude_code_settings.statusLine.command to point
# elsewhere; the claude_code_statusline_* vars tune the rendering.
# Renders the configured sections (location, git, session, usage):
# ~/projects/dotfiles | ๐ฑ main โ2 +45/-12 ?3 | ๐ฆ Fable 5 $0.42 24m โโโโ 42% 1.2M | โณ 5h โโโโ 31% 2h15m โ 7d โโโโ 68% Tue 09:00
# (in a linked worktree the git icon becomes the worktree icon: | ๐ชด fix-branch โฆ)
# No `set -e`: a broken payload or repo must degrade to a partial line, never
# blank the status bar. LC_ALL=C keeps printf's decimal point locale-proof.
set -u
export LC_ALL=C
sep=" | "
icon_dir=""
icon_git="๐ฑ"
icon_wt="๐ชด"
icon_usage="โณ"
icon_compact="โป๏ธ"
icon_compact_active="โจ"
c_dim=$'\033[2m'; c_grn=$'\033[32m'; c_ylw=$'\033[33m'; c_red=$'\033[31m'; c_rst=$'\033[0m'
# pbar <pct> โ percentage indicator: a mini progress bar (โโโโ) whose cells
# fill through โ โ โ โ. Every glyph paints its whole cell (unlike the finer
# eighth-blocks, which leave a seam of bare background mid-bar), the value is
# floored to one step so any progress stays visible
# (claude_code_statusline_progress_style: bar, percent, or both).
pbar() {
local lv=(โ โ โ) w=4 out="" i=0 u seg
u=$(($1 * w * 3 / 100)); [ "$u" -eq 0 ] && [ "$1" -gt 0 ] && u=1
while [ "$i" -lt "$w" ]; do
seg=$((u - i * 3))
if [ "$seg" -ge 3 ]; then out+="โ"
elif [ "$seg" -gt 0 ]; then out+="${lv[seg]}"
else out+="โ"; fi
i=$((i + 1))
done
printf '%s' "$out"
printf ' '
printf '%s%%' "$1"
}
# ---- payload: one jq pass; degrade to a bare path if jq/payload is broken ----
input=$(cat)
model="" dir="" cost=0 dur_ms=0 transcript="" ctx=-1 cwsize=0 u5=-1 u5r=0 u7=-1 u7r=0 cusnull=0
# \x1f (unit separator) instead of @tsv: tab is IFS *whitespace*, so an empty
# field (no transcript_path) would collapse and shift every field after it.
if command -v jq >/dev/null 2>&1; then
IFS=$'\x1f' read -r model dir cost dur_ms transcript ctx cwsize u5 u5r u7 u7r cusnull <<EOF
$(jq -r -j '[.model.display_name // "Claude",
.workspace.current_dir // env.PWD,
.cost.total_cost_usd // 0,
(.cost.total_duration_ms // 0 | floor),
.transcript_path // "",
(.context_window.used_percentage // -1 | floor),
(.context_window.context_window_size // 0 | floor),
(.rate_limits.five_hour.used_percentage // -1 | floor),
(.rate_limits.five_hour.resets_at // 0 | floor),
(.rate_limits.seven_day.used_percentage // -1 | floor),
(.rate_limits.seven_day.resets_at // 0 | floor),
(if (.context_window | type) == "object" and .context_window.current_usage == null then 1 else 0 end)] | map(tostring) | join("\u001f")' <<<"$input" 2>/dev/null)
EOF
fi
[ -n "$dir" ] || { printf '%s\n' "${PWD/#$HOME/\~}"; exit 0; }
# A linked worktree's git dir differs from the repo's common one; the git
# section then leads with the worktree icon instead of the branch one (๐ฑ a
# sprout on the tree, ๐ชด the same sprout in a pot of its own).
gd=$(git -C "$dir" rev-parse --git-dir --git-common-dir 2>/dev/null) || gd=""
[ -n "$gd" ] && [ "${gd%%$'\n'*}" != "${gd##*$'\n'}" ] && icon_git="$icon_wt"
# ---- location section ----
path="${dir/#$HOME/\~}"
location_part="$icon_dir $path"
# ---- session section: model icon, cost, session duration, context usage ----
# Model icon: first configured key contained in the model name wins, so the
# model is identifiable at a glance; anything unmatched gets the fallback.
icon_model="โณ"
case "$(tr '[:upper:]' '[:lower:]' <<<"$model")" in
*fable*) icon_model="๐ฆ" ;;
*opus*) icon_model="๐ผ" ;;
*sonnet*) icon_model="๐" ;;
*haiku*) icon_model="๐ธ" ;;
esac
# Cost, colour-coded by spend.
lvl=$(awk -v c="$cost" 'BEGIN{print (c >= 10) ? 2 : (c >= 2) ? 1 : 0}' 2>/dev/null)
case "${lvl:-0}" in 2) cc="$c_red" ;; 1) cc="$c_ylw" ;; *) cc="$c_grn" ;; esac
meta="${cc}\$$(printf '%.2f' "$cost" 2>/dev/null || printf '%s' "$cost")${c_rst}"
# Session duration.
if [ "${dur_ms:-0}" -gt 0 ] 2>/dev/null; then
s=$((dur_ms / 1000))
if [ "$s" -ge 3600 ]; then dur="$((s / 3600))h$(((s % 3600) / 60))m"
elif [ "$s" -ge 60 ]; then dur="$((s / 60))m"
else dur="${s}s"; fi
meta+=" $dur"
fi
# fmt_tokens <n> โ compact token count (950, 12k, 1.2M)
fmt_tokens() {
if [ "$1" -ge 1000000 ] 2>/dev/null; then awk -v n="$1" 'BEGIN{printf "%.1fM", n / 1000000}'
elif [ "$1" -ge 1000 ] 2>/dev/null; then printf '%sk' "$(($1 / 1000))"
else printf '%s' "$1"; fi
}
# Context usage and session tokens. The payload's own context percentage wins
# when present (newer Claude Code; knows the real window size), falling back to
# the last main-chain message's input-side tokens vs the 200k window. Session
# tokens always come from the same transcript (JSONL) pass: every main-chain
# message's tokens summed (input, output, cache writes and reads โ the volume
# the API processed).
used=0 total=0
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
read -r used total <<EOF2
$(jq -rn 'reduce (inputs | select(.isSidechain != true) | .message.usage // empty
| select(.input_tokens != null)) as $u
({c: 0, t: 0};
{c: ($u.input_tokens + ($u.cache_read_input_tokens // 0) + ($u.cache_creation_input_tokens // 0)),
t: (.t + $u.input_tokens + ($u.output_tokens // 0)
+ ($u.cache_creation_input_tokens // 0) + ($u.cache_read_input_tokens // 0))})
| "\(.c) \(.t)"' "$transcript" 2>/dev/null)
EOF2
fi
pct="$ctx"
if ! [ "${pct:--1}" -ge 0 ] 2>/dev/null; then
# No payload figure: derive from session tokens, defaulting to 0 (a fresh
# session shows an empty bar, it does not drop the context indicator).
pct=0
[ "${used:-0}" -gt 0 ] 2>/dev/null && pct=$((used * 100 / 200000))
fi
if [ "$pct" -ge 0 ]; then
[ "$pct" -gt 100 ] && pct=100
if [ "$pct" -ge 90 ]; then xc="$c_red"
elif [ "$pct" -ge 70 ]; then xc="$c_ylw"
else xc=""; fi
meta+=" ${xc}$(pbar "$pct")${xc:+$c_rst}"
# Nearing a forced auto-compact. Claude Code triggers it a roughly fixed number
# of tokens below the window (a summary reservation plus a safety buffer,
# compact_reserve here), so the trigger point is (window - reserve): ~83% of a
# 200k window but ~97% of a 1M one. Deriving it from the actual
# context_window_size keeps the marker honest on any model instead of a flat
# percentage. Claude Code's own tuning env vars win when set; reserve 0 hides
# it. (used_percentage under-reports overhead, so it stays an approximation.)
# Within compact_lead points of the trigger the marker shows with the headroom
# left, "โป๏ธ N%" counting down to 0 at the compaction point.
compact_reserve=33000
if [ "$compact_reserve" -gt 0 ] 2>/dev/null; then
cw="$cwsize"; [ "${cw:-0}" -gt 0 ] 2>/dev/null || cw=200000
[ "${CLAUDE_CODE_AUTO_COMPACT_WINDOW:-0}" -gt 0 ] 2>/dev/null && cw="$CLAUDE_CODE_AUTO_COMPACT_WINDOW"
if [ "${CLAUDE_AUTOCOMPACT_PCT_OVERRIDE:-0}" -gt 0 ] 2>/dev/null; then
cthr="$CLAUDE_AUTOCOMPACT_PCT_OVERRIDE"
else
cthr=$(( (cw - compact_reserve) * 100 / cw ))
fi
if [ "$pct" -ge $((cthr - 20)) ] 2>/dev/null; then
left=$((cthr - pct)); [ "$left" -lt 0 ] && left=0
meta+=" ${c_red}${icon_compact} ${left}%${c_rst}"
fi
fi
fi
# Just-compacted marker. Claude Code re-runs the status line once /compact
# finishes (never during it) and the payload's context_window.current_usage
# comes back null until the next API call repopulates it โ the only signal a
# status line gets that a compaction just happened, since no in-progress flag
# exists. Show the compact icon with its companion for that one render; gated on
# session tokens so a fresh session (current_usage also absent) does not trip it.
if [ "${cusnull:-0}" = 1 ] && [ "${total:-0}" -gt 0 ] 2>/dev/null; then
meta+=" ${c_grn}${icon_compact} ${icon_compact_active}${c_rst}"
fi
if [ -n "${total:-}" ] && [ "$total" -gt 0 ] 2>/dev/null; then
meta+=" ${c_dim}$(fmt_tokens "$total")${c_rst}"
fi
session_part="$icon_model $model $meta"
# ---- git section: branch, worktree marker, ahead/behind, changes ----
# One --no-optional-locks porcelain call (branch, ahead/behind, dirty,
# untracked) so the status line never competes for the index lock; line counts
# only when dirty.
git_part=""
gs=$(git --no-optional-locks -C "$dir" status --porcelain=v2 --branch 2>/dev/null) || gs=""
if [ -n "$gs" ]; then
branch=$(sed -n 's/^# branch\.head //p' <<<"$gs")
[ "$branch" = "(detached)" ] && branch="@$(git -C "$dir" rev-parse --short HEAD 2>/dev/null || echo '?')"
dirty=$(grep -c '^[12u]' <<<"$gs") || dirty=0
untracked=$(grep -c '^?' <<<"$gs") || untracked=0
bc="$c_grn"; [ "$((dirty + untracked))" -gt 0 ] && bc="$c_ylw"
git_part="$icon_git ${bc}${branch}${c_rst}"
ab=$(sed -n 's/^# branch\.ab //p' <<<"$gs") # "+A -B" vs upstream
if [ -n "$ab" ]; then
ahead="${ab%% *}"; behind="${ab##* }"; arrows=""
[ "${ahead#+}" != "0" ] && arrows+="โ${ahead#+}"
[ "${behind#-}" != "0" ] && arrows+="โ${behind#-}"
[ -n "$arrows" ] && git_part+=" ${c_dim}${arrows}${c_rst}"
fi
if [ "$dirty" -gt 0 ]; then
stat=$(git --no-optional-locks -C "$dir" diff --shortstat HEAD 2>/dev/null) || stat=""
ins=$(sed -n 's/.* \([0-9][0-9]*\) insertion.*/\1/p' <<<"$stat")
del=$(sed -n 's/.* \([0-9][0-9]*\) deletion.*/\1/p' <<<"$stat")
git_part+=" ${c_grn}+${ins:-0}${c_rst}/${c_red}-${del:-0}${c_rst}"
fi
[ "$untracked" -gt 0 ] && git_part+=" ${c_dim}?${untracked}${c_rst}"
fi
# ---- usage section: subscription windows (Pro/Max; absent until the first
# response, each window independently optional โ render what's there). The
# 5-hour window gets the time left (the number you act on mid-session), the
# 7-day one its reset moment. ----
fmt_reset() { # <epoch> <format> โ GNU date first, BSD (macOS) fallback
[ "${1:-0}" -gt 0 ] 2>/dev/null || return 0
date -d "@$1" +"$2" 2>/dev/null || date -r "$1" +"$2" 2>/dev/null || true
}
fmt_left() { # <epoch> โ time until it (2h15m / 45m), empty once passed
[ "${1:-0}" -gt 0 ] 2>/dev/null || return 0
local s=$(($1 - $(date +%s)))
[ "$s" -gt 0 ] || return 0
if [ "$s" -ge 3600 ]; then printf '%sh%sm' "$((s / 3600))" "$(((s % 3600) / 60))"
else printf '%sm' "$((s / 60))"; fi
}
usage_win() { # <label> <pct> <reset-string>
local uc=""
[ "${2:--1}" -ge 0 ] 2>/dev/null || return 0
if [ "$2" -ge 90 ]; then uc="$c_red"
elif [ "$2" -ge 70 ]; then uc="$c_ylw"; fi
printf '%s %s%s%s' "$1" "$uc" "$(pbar "$2")" "${uc:+$c_rst}"
[ -n "$3" ] && printf ' %s%s%s' "$c_dim" "$3" "$c_rst"
}
usage_part=""
w5=$(usage_win "5h" "$u5" "$(fmt_left "$u5r")")
w7=$(usage_win "7d" "$u7" "$(fmt_reset "$u7r" "%a %H:%M")")
if [ -n "$w5" ] && [ -n "$w7" ]; then
usage_part="$icon_usage $w5 ${c_dim}โ${c_rst} $w7"
elif [ -n "$w5$w7" ]; then
usage_part="$icon_usage ${w5}${w7}"
fi
# ---- assemble the configured sections ----
line=""
for s in location git session usage; do
case "$s" in
location) part="$location_part" ;;
session) part="$session_part" ;;
git) part="$git_part" ;;
usage) part="$usage_part" ;;
*) part="" ;;
esac
[ -n "$part" ] || continue
[ -n "$line" ] && line+="$sep"
line+="$part"
done
printf '%s\n' "$line"Then wire it up: make it executable and point your settings.json at it.
chmod +x ~/.claude/statusline.sh{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 0
}
}Or let your agent do it โ
You are already in a terminal with a coding agent. Hand it the whole job โ fetching, installing, wiring, and tuning to your taste โ by pasting this prompt into Claude Code and editing the last sentence to your preferences:
Copy this prompt to your coding agent
Fetch https://dotfiles.pro/blog/best-claude-code-statusline and take the
complete status line script from the "whole script" block. Save it to
~/.claude/statusline.sh, make it executable, and add the statusLine command
to ~/.claude/settings.json (type "command", padding 0). Then verify it by
piping a sample JSON payload through the script and showing me the rendered
line. Finally, tune the variables at the top for me: change the separator from
" | " to " ยท ", and replace the emoji icons (icon_git, icon_model, icon_usage)
with plain Nerd Font glyphs.The script tunes from the top of the file, so anything you can describe โ icon set, separator, which sections appear, where a color flips โ the agent can change without you learning its internals. Change the last sentence and you change the result.
Iterate without a live session โ
Because the line renders from whatever JSON arrives on stdin, iterating on the look does not need a live session โ pipe it a payload:
printf '%s' '{
"model": {"display_name": "Fable 5"},
"workspace": {"current_dir": "'"$PWD"'"},
"cost": {"total_cost_usd": 1.23, "total_duration_ms": 5040000},
"context_window": {"used_percentage": 42},
"rate_limits": {
"five_hour": {"used_percentage": 31, "resets_at": '$(($(date +%s) + 8100))'},
"seven_day": {"used_percentage": 68, "resets_at": '$(($(date +%s) + 250000))'}
}
}' | ~/.claude/statusline.shDrop a field and watch it degrade instead of break: no rate_limits, no usage section; no valid JSON at all, a bare path.
If you'd rather adopt than build โ
The tools, honestly:
- ccstatusline the community favorite and the most capable by a distance. An interactive TUI configurator, dozens of widgets, powerline rendering with themes, multi-line layouts. Built on Node or Bun, typically run through
npx. If you want maximum customization and enjoy configuring it, this is the one. - ccusage statusline the cost specialist: session cost, daily total, the current 5-hour block, burn rate. If you bill work to a team or track spend across days, its numbers are the draw, and it also embeds as a widget inside ccstatusline.
- claude-powerline vim-style powerline aesthetics with usage tracking and themes, for people who want the prompt to match their editor.
- Compiled rewrites (CCometixLine in Rust, claude-statusline in Go with Starship-style presets) the same widget model as a fast single binary, trading the npm toolchain for a release download or a brew tap.
The trade they share is the toolchain in the loop, measured against the rules above: a process boot on every refresh, and configuration that lives wherever the tool wrote it, on that one machine, outside whatever manages the rest of your setup. None of that is fatal. All of it is a lot, for one line of text built from data Claude Code already hands you for free.
Which to pick โ
Decide what the line is for before deciding what renders it:
- Know your signals and want them with nothing to install? The script above is the whole answer: limits first, then place, model, spend, and the six rules do the rest.
- Want a themed, widget-rich prompt and enjoy tuning it? ccstatusline is the best in that class, and it is not close.
- Tracking spend across days or a team? ccusage, standalone or as a widget.
The best status line, like the best dotfiles manager, is the least machinery that covers your actual scope. One bash script is enough.
Provisioned, not pasted: the claude role โ
The script above is maintained as part of Dotfiles Pro, where the claude role renders and deploys it. That is also the answer to the question the standalone version leaves open: who fills in the tunables, and who puts the same line on your next machine?
The role does, from a few variables in your dotfiles repo, versioned and identical everywhere:
claude_code_statusline_icon_style: mono # default: emoji
claude_code_statusline_location_style: short # default: full
claude_code_statusline_sections: # default: location, git, session, usage
- location
- git
- sessionThe same apply installs Claude Code itself, writes the statusLine wiring into settings.json, and delivers the Nerd Font (the nerdfonts role) the mono glyphs want. Your next laptop gets the identical line with zero ceremony, and so does the twenty-person fleet if you run one. No TUI to re-answer, no npx cache to warm, nothing to drift.
And it stays an escape hatch, not a lock-in: claude_code_statusline_deploy: false plus claude_code_statusline_path lets you fork the script into your own repo and have the role deploy yours instead, or override statusLine.command outright and point it at anything, ccstatusline included. The role provisions the wiring either way.