Catppuccin Frappé-themed status line with model, git branch and dirty flag, folder, a block context bar, and session cost.
MIT licensed · original source
Updated 2026-06-29
38 copies
Preview
Clean repo
Opus 4.8 │ main │ app │ ▓▓░░░░░░░░ 44k/200k │ $0.41
New session
Opus 4.8 │ main │ app │ ░░░░░░░░░░ 0k/200k │ $0.00
Dirty branch
Sonnet 4.6 │ feat/auth● │ app │ ▓▓▓▓░░░░░░ 96k/200k │ $0.41
Near-full
Opus 4.8 │ main │ app │ ▓▓▓▓▓▓▓▓▓░ 182k/200k │ $4.12
1M context
Fable 5 │ main │ app │ ▓▓▓▓▓▓░░░░ 640k/1000k │ $0.41
Post-compact
Haiku 4.5 │ main │ app │ ░░░░░░░░░░ 0k/200k │ $0.00
Worktree
Opus 4.8 │ worktree-feature │ feature │ ▓▓▓░░░░░░░ 74k/200k │ $0.41
Non-git
Opus 4.8 │ scratch │ ▓▓░░░░░░░░ 44k/200k │ $0.41
Source
#!/bin/bash
# Status line - model, git branch, cwd, context usage, cost
# Catppuccin Frappe theme
# Configuration
CURRENCY='$' # Currency symbol (e.g., '$', '€', '£', '¥')
CURRENCY_CODE='USD' # ISO 4217 code for API lookup (e.g., 'USD', 'GBP', 'EUR', 'JPY')
EXCHANGE_RATE=1 # Fallback rate if API unavailable
# Cache settings
CACHE_DIR="${HOME}/.cache/cc-status-line"
CACHE_FILE="${CACHE_DIR}/exchange-rate.json"
CACHE_MAX_AGE=86400 # 24 hours in seconds
# Get exchange rate (from cache or API)
get_exchange_rate() {
# Use fallback if no currency code set or set to USD
[ -z "$CURRENCY_CODE" ] || [ "$CURRENCY_CODE" = "USD" ] && echo "1" && return
# Check if cache exists and is fresh
if [ -f "$CACHE_FILE" ]; then
cache_age=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0) ))
if [ "$cache_age" -lt "$CACHE_MAX_AGE" ]; then
cached_rate=$(jq -r --arg code "$CURRENCY_CODE" '.rates[$code] // empty' "$CACHE_FILE" 2>/dev/null)
[ -n "$cached_rate" ] && echo "$cached_rate" && return
fi
fi
# Fetch fresh rate (in background to avoid blocking)
mkdir -p "$CACHE_DIR"
rate=$(curl -sf --max-time 2 "https://api.frankfurter.app/latest?from=USD&to=${CURRENCY_CODE}" 2>/dev/null)
if [ -n "$rate" ]; then
echo "$rate" > "$CACHE_FILE"
jq -r --arg code "$CURRENCY_CODE" '.rates[$code] // empty' <<< "$rate" 2>/dev/null && return
fi
# Fallback to configured rate
echo "$EXCHANGE_RATE"
}
data=$(cat)
# Single jq call - extract all values at once (tab-separated)
IFS=$'\t' read -r model cwd max_ctx used_pct cost_usd <<< "$(echo "$data" | jq -r '[
(.model.display_name // .model.id // "unknown"),
(.workspace.current_dir // ""),
(.context_window.context_window_size // 200000),
(.context_window.used_percentage // ""),
(.cost.total_cost_usd // 0)
] | @tsv')"
# Get Claude Code version
cc_version=$(claude --version 2>/dev/null | awk '{print $1}')
# Folder name from path
folder="${cwd##*/}"
[ -z "$folder" ] && folder="?"
# Git: branch + dirty status (fast combined check)
branch=""
dirty=""
if git rev-parse --git-dir > /dev/null 2>&1; then
branch=$(git branch --show-current 2>/dev/null)
[ -z "$branch" ] && branch=$(git rev-parse --short HEAD 2>/dev/null)
# Truncate long branches (max 20 chars)
if [ "${#branch}" -gt 20 ]; then
branch="${branch:0:19}…"
fi
# Check for uncommitted changes (fast: just check if output exists)
if [ -n "$(git status --porcelain 2>/dev/null | head -1)" ]; then
dirty="●"
fi
fi
# Catppuccin Frappe colors (24-bit true color)
BLUE='\033[38;2;140;170;238m' # Blue - context bar (low)
RED='\033[38;2;231;130;132m' # Red - context bar (high)
TEAL='\033[38;2;129;200;190m' # Teal - folder
MAUVE='\033[38;2;202;158;230m' # Mauve - git branch
LAVENDER='\033[38;2;186;187;241m' # Lavender - model
PEACH='\033[38;2;239;159;118m' # Peach - dirty indicator
GREEN='\033[38;2;166;209;137m' # Green - cost
OVERLAY='\033[38;2;115;121;148m' # Overlay 0 - separators
SUBTEXT='\033[38;2;165;173;206m' # Subtext 0 - secondary text
RESET='\033[0m'
# Format context bar
if [ -z "$used_pct" ] || [ "$used_pct" = "null" ]; then
context_info="${OVERLAY}░░░░░░░░░░${RESET}"
else
pct=$(printf "%.0f" "$used_pct" 2>/dev/null || echo "$used_pct")
[ "$pct" -gt 100 ] 2>/dev/null && pct=100
# Calculate tokens in k
used_k=$(( max_ctx * pct / 100 / 1000 ))
max_k=$(( max_ctx / 1000 ))
# Build block bar (10 segments)
filled=$(( pct / 10 ))
# Blue by default, red when > 60%
[ "$pct" -gt 60 ] && COLOR="$RED" || COLOR="$BLUE"
bar=""
for i in 0 1 2 3 4 5 6 7 8 9; do
if [ "$i" -lt "$filled" ]; then
bar="${bar}${COLOR}▓${RESET}"
else
bar="${bar}${OVERLAY}░${RESET}"
fi
done
context_info="${bar} ${SUBTEXT}${used_k}k/${max_k}k${RESET}"
fi
# Format cost (convert from USD using exchange rate)
if [ -n "$cost_usd" ] && [ "$cost_usd" != "0" ] && [ "$cost_usd" != "null" ]; then
rate=$(get_exchange_rate)
cost_converted=$(echo "$cost_usd * $rate" | bc -l 2>/dev/null || echo "$cost_usd")
cost_fmt=$(printf "%.2f" "$cost_converted" 2>/dev/null || echo "0.00")
cost_display="${GREEN}${CURRENCY}${cost_fmt}${RESET}"
else
cost_display="${OVERLAY}${CURRENCY}0.00${RESET}"
fi
# Build output
output="${LAVENDER}${model}${RESET}"
if [ -n "$branch" ]; then
output="${output} ${OVERLAY}│${RESET} ${MAUVE}${branch}${RESET}"
[ -n "$dirty" ] && output="${output}${PEACH}${dirty}${RESET}"
fi
output="${output} ${OVERLAY}│${RESET} ${TEAL}${folder}${RESET}"
output="${output} ${OVERLAY}│${RESET} ${context_info}"
output="${output} ${OVERLAY}│${RESET} ${cost_display}"
# Append version segment
if [ -n "$cc_version" ]; then
output="${output} ${OVERLAY}│${RESET} ${SUBTEXT}v${cc_version}${RESET}"
fi
printf '%b\n' "$output"
What it shows
- Model display name (for example Opus 4.8)
- Current git branch, or the short commit hash when detached, truncated to 19 characters plus an ellipsis when longer than 20
- A dot marker (●) after the branch when the repo has uncommitted changes
- Current folder name (the last segment of the working directory path)
- Context window usage as a 10-segment block bar
- Context usage as used and total tokens in thousands (for example 44k/200k)
- Session cost with a configurable currency symbol, defaulting to US dollars
- Claude Code version taken from running claude --version, appended only when that command succeeds (not visible in any rendered preview)
Requirements
- bash
- jq, required to parse the JSON Claude Code sends on stdin
- git, used for the branch and dirty check; the segment is skipped if it fails
- bc, used to convert the cost whenever it is nonzero
- awk, used unconditionally to parse the output of claude --version for the version segment
- date and stat, used only when CURRENCY_CODE is changed from the default USD, to check the exchange-rate cache's age (the script handles both GNU and BSD stat flags)
- curl and network access to api.frankfurter.app, but only if CURRENCY_CODE is changed from the default USD; with USD the script never touches the network
- The claude CLI on PATH for the optional version segment
- A terminal with 24-bit true color support
- No Nerd Font required; the only special characters are standard Unicode blocks, bars, and an ellipsis (▓ ░ │ ● …)
Behavior notes
- The branch segment and dirty marker come from running git in the working directory, not from the session JSON, and both disappear entirely when the directory is not a git repository, as in the scratch-dir scenario.
- The dirty dot appears only when git status reports uncommitted changes, as in the dirty feature branch scenario.
- The context bar is blue at 60 percent usage or below (22, 37, and 48 percent scenarios) and turns red above 60 percent (64 and 91 percent scenarios).
- The bar's token labels follow context_window_size, so the 1M-context scenario shows 640k/1000k instead of a 200k denominator.
- When used_percentage is null, the bar renders fully empty; because the empty field shifts the tab-separated values the script reads, the token count shows 0k and the cost prints as gray $0.00 even when the input reported a cost, as in the post-compact scenario where total_cost_usd was 0.41 but $0.00 was displayed.
- The cost is green when nonzero and dim gray $0.00 when zero or missing.
- The folder segment uses the basename of the current directory, so the worktree scenario shows the worktree folder name (feature) rather than the project directory name.
- Effort level, vim mode, rate limits, PR details, thinking state, session name, agent name, and output style vary across the scenarios but never change the output; the script ignores them.
More status lines
Dreambase Panel
150 copies
╭─ CONTEXT ────────────────────────────────────────────╮
│ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │
├─ STATS ──────────────────────────────────────────────┤
│ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │
├─ REPO ───────────────────────────────────────────────┤
│ ◆ Opus 4.8 · main · 16:21 │
╰──────────────────────────────────────────────────────╯
Single-line usage
87 copies
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0hPace Dot Meters
63 copies
Opus 4.8 [high] ~/app main
16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Activity Feed
36 copies
Opus 4.8 high ✦ │ ctx 22% │ app (main) │ PR #1287 ● │ $0.41 · 10m +128 -34
Session ● ● ● ● ● ● ● ● ○ ○ ○ 74% left Resets in 2h 6m
Weekly ● ● ● ● ● ● ● ● ● ● ○ 93% left Resets in 2d 0h
Fable ● ● ● ● ● ● ● ● ● ○ ○ 85% left Resets in 2d 0h
◐ Read:index.ts ✓ Read×1 ✓ Bash×1 ✓ Edit×1 ✓ Grep×1
◐ code-reviewer Review the change for regressions
Dreambase Flat
29 copies
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │ ◆ Opus 4.8 · main · 16:22
Color-Coded Context
25 copies
◆ Opus 4.8 │ ▪ app │ ⎇ main │ ◷ 16:20 │ ██▄░░░░░░░ 26% (44K/167K) │ 26% ↻2h6m │ 7% ⟳2d0h