statuslin.es

Catppuccin Frappé

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

Shows:GitTokensCostThemedBashNetwork Access

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 ╰──────────────────────────────────────────────────────╯
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0h
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
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 Opus 4.8 · main · 16:22
◆ Opus 4.8 │ ▪ app │ ⎇ main │ ◷ 16:20 │ 26% (44K/167K) │ 26% ↻2h6m7% ⟳2d0h