Single-line status line in a terracotta Claude-brand palette: model, effort, cwd, git branch and diff stats, MCP connection count, context bar with token usage, cache hit rate, cost, and session duration. Adapted from @kyleledbetter's Dreambase Panel.

Before you install

  • This version was reviewed before it was listed on Oct 29, 2026
  • Doesn't ask to connect to the internet
  • The (+128,-34) beside the branch counts lines Claude changed this session, not your uncommitted git diff.
  • A yellow ⚠ follows the context bar once a response passes 200k tokens, even in a 1M context window.
  • Outside a git repo the branch segment is left out, and a named session shows at the end of the line.
Not showing after you paste? Fix it

Preview

Clean repo
◆ Opus 5.5 │ Effort: high │ cwd: ~ / app │ ⎇ main (+128,-34) │ MCP: 0 │ Context: [▰▰▱▱▱▱▱▱▱▱] 44k/200k (22%) │ ↓ 44.0K ↑ 1.4K ⛁ 2.0K (4%) │ $0.41 │ ⏱ 10m 12s
New session
◆ Opus 5.5 │ Effort: high │ cwd: ~ / app │ ⎇ main (+0,-0) │ MCP: 0 │ Context: [▱▱▱▱▱▱▱▱▱▱] 0/200k (0%) │ ↓ 0 ↑ 0 │ $0.00 │ ⏱ 10m 12s
Dirty branch
◆ Sonnet 5.5 I │ Effort: medium │ cwd: ~ / app │ ⎇ feat/auth (+128,-34) │ MCP: 0 │ Context: [▰▰▰▰▱▱▱▱▱▱] 96k/200k (48%) │ ↓ 96.0K ↑ 1.4K ⛁ 2.0K (2%) │ $0.41 │ ⏱ 10m 12s
Near-full
◆ Opus 5.5 │ Effort: max │ cwd: ~ / app │ ⎇ main (+128,-34) │ MCP: 0 │ Context: [▰▰▰▰▰▰▰▰▰▱] 182k/200k (91%) ⚠ │ ↓ 182.0K ↑ 1.4K ⛁ 2.0K (1%) │ $4.12 │ ⏱ 10m 12s
1M context
◆ Fable 5.1 │ Effort: xhigh │ cwd: ~ / app │ ⎇ main (+128,-34) │ MCP: 0 │ Context: [▰▰▰▰▰▰▱▱▱▱] 640k/1M (64%) ⚠ │ ↓ 640.0K ↑ 1.4K ⛁ 2.0K (0%) │ $0.41 │ ⏱ 10m 12s
Post-compact
◆ Haiku 4.5 N │ cwd: ~ / app │ ⎇ main (+128,-34) │ MCP: 0 │ Context: [▱▱▱▱▱▱▱▱▱▱] 0/200k (0%) │ ↓ 0 ↑ 0 │ $0.41 │ ⏱ 10m 12s
Worktree
◆ Opus 5.5 │ Effort: low │ cwd: ~ / .wt / feature │ MCP: 0 │ Context: [▰▰▰▱▱▱▱▱▱▱] 74k/200k (37%) │ ↓ 74.0K ↑ 1.4K ⛁ 2.0K (2%) │ $0.41 │ ⏱ 10m 12s
Non-git
◆ Opus 5.5 │ Effort: high │ cwd: ~ / scratch │ MCP: 0 │ Context: [▰▰▱▱▱▱▱▱▱▱] 44k/200k (22%) │ ↓ 44.0K ↑ 1.4K ⛁ 2.0K (4%) │ $0.41 │ ⏱ 10m 12s │ Session: refactor-pass

In Claude Desktop

Terracotta in Claude Desktop

Recorded with statusline-anywhere.

What it does

One terracotta-colored line with your model, effort, folder, git branch, MCP server count, a context bar, token and cache stats, cost and session time.

Written in
Python
Network access
None requested
Works in
Terminal

How it works

  1. Reads the model, effort, folders, cost, line counts and context usage from the stdin JSON.
  2. Runs git branch --show-current in the project folder, caching it for 5 seconds.
  3. Counts MCP servers listed in ~/.claude.json and .mcp.json, without connecting to them.
  4. The 10-block context bar turns lime at 50%, yellow at 70%, orange at 85% and red at 95%.

Source

#!/usr/bin/env python3
"""Claude Code Status Line — Dreambase Flat
   Single-line adaptation of @kyleledbetter's Dreambase Panel: the same
   context bar, stats, and repo sections in a terracotta Claude-brand
   palette, rendered on one line instead of a boxed three-row panel.
"""

import json
import os
import re
import subprocess
import sys
import time

data = json.load(sys.stdin)

# ── Extract data ──────────────────────────────────────────
model = data.get("model", {}).get("display_name", "—")
model_id = data.get("model", {}).get("id", "")
project_dir = data.get("workspace", {}).get("project_dir", ".")
current_dir = data.get("workspace", {}).get("current_dir", ".")
pct = int(float(data.get("context_window", {}).get("used_percentage") or 0))
ctx_size = int(data.get("context_window", {}).get("context_window_size") or 200000)
input_tokens = int(data.get("context_window", {}).get("total_input_tokens") or 0)
output_tokens = int(data.get("context_window", {}).get("total_output_tokens") or 0)
cost_usd = float(data.get("cost", {}).get("total_cost_usd") or 0)
duration_ms = int(data.get("cost", {}).get("total_duration_ms") or 0)
lines_added = int(data.get("cost", {}).get("total_lines_added") or 0)
lines_removed = int(data.get("cost", {}).get("total_lines_removed") or 0)
exceeds_200k = data.get("exceeds_200k_tokens", False)
vim_mode = data.get("vim", {}).get("mode", "")
effort_level = (
    data.get("thinking", {}).get("level")
    or data.get("effort", {}).get("level")
    or os.environ.get("CLAUDE_EFFORT", "")
)
session_id = data.get("session_id", "")
current = data.get("context_window", {}).get("current_usage") or {}
cache_read = int(current.get("cache_read_input_tokens") or 0)
cache_write = int(current.get("cache_creation_input_tokens") or 0)
fresh_input = int(current.get("input_tokens") or 0)

# ── ANSI Colors ───────────────────────────────────────────
R = "\033[0m"
B = "\033[1m"
D = "\033[2m"

# Claude brand rust / terracotta
RUST = "\033[38;5;173m"
RUST_B = "\033[38;5;209m"
RUST_D = "\033[38;5;131m"
RUST_BG = "\033[48;5;52m"

# Functional
GRN = "\033[38;5;34m"
LIME = "\033[38;5;118m"
YEL = "\033[38;5;220m"
ORG = "\033[38;5;208m"
RED = "\033[38;5;196m"
CYN = "\033[38;5;81m"
BLU = "\033[38;5;69m"
PUR = "\033[38;5;141m"
WHT = "\033[38;5;255m"
GRY = "\033[38;5;243m"

# ── Formatters ────────────────────────────────────────────
def fmt_tok(n):
    if n >= 1_000_000:
        return f"{n / 1_000_000:.1f}M"
    if n >= 1_000:
        return f"{n / 1_000:.1f}K"
    return str(n)


def fmt_dur(ms):
    s = ms // 1000
    h, rem = divmod(s, 3600)
    m, sec = divmod(rem, 60)
    if h:
        return f"{h}h {m}m"
    if m:
        return f"{m}m {sec}s"
    return f"{sec}s"


def truncate(s, maxlen=18):
    return s[: maxlen - 1] + "…" if len(s) > maxlen else s


def fmt_cwd(path, maxlen=40):
    home = os.path.expanduser("~")
    if path == home or path.startswith(home + os.sep):
        path = "~" + path[len(home):]
    root = path.startswith("/")
    segs = [p for p in path.split("/") if p]

    def width(s):
        return len(" / ".join(s)) + (1 if root else 0)

    # Fish-style: shorten leading segments to their first char until it fits; last segment stays full
    for i in range(len(segs) - 1):
        if width(segs) <= maxlen:
            break
        segs[i] = segs[i][:1]
    return ("/" if root else ""), segs


# ── Context bar ───────────────────────────────────────────
BAR_W = 10

if pct >= 95:
    bar_clr = RED
elif pct >= 85:
    bar_clr = ORG
elif pct >= 70:
    bar_clr = YEL
elif pct >= 50:
    bar_clr = LIME
else:
    bar_clr = GRN

filled = pct * BAR_W // 100
bar = "▰" * filled + "▱" * (BAR_W - filled)

# Tokens currently in context: fresh input + cache read + cache write (falls back to the percentage)
cache_total = cache_read + cache_write + fresh_input
used_tokens = cache_total if cache_total else pct * ctx_size // 100


def fmt_ctx(n):
    if n >= 1_000_000:
        return f"{n / 1_000_000:g}M"
    if n >= 1_000:
        return f"{round(n / 1_000)}k"
    return str(n)


ctx_label = fmt_ctx(ctx_size)
warn = f" {YEL}{B}⚠{R}" if exceeds_200k else ""

# ── MCP servers (user + project config) ───────────────────
def mcp_count():
    names = set()
    try:
        with open(os.path.expanduser("~/.claude.json")) as f:
            claude_json = json.load(f)
        names.update((claude_json.get("mcpServers") or {}).keys())
        project = claude_json.get("projects", {}).get(project_dir, {})
        names.update((project.get("mcpServers") or {}).keys())
    except (OSError, ValueError):
        pass
    try:
        with open(os.path.join(project_dir, ".mcp.json")) as f:
            names.update((json.load(f).get("mcpServers") or {}).keys())
    except (OSError, ValueError):
        pass
    return len(names)


# ── Session name (from running-session registry) ──────────
def session_name():
    if data.get("session_name"):
        return data["session_name"]
    sessions_dir = os.path.expanduser("~/.claude/sessions")
    try:
        for fname in os.listdir(sessions_dir):
            if not fname.endswith(".json"):
                continue
            try:
                with open(os.path.join(sessions_dir, fname)) as f:
                    entry = json.load(f)
            except (OSError, ValueError):
                continue
            if session_id and entry.get("sessionId") == session_id:
                return entry.get("name", "")
    except OSError:
        pass
    return ""


# ── Git branch (cached for perf) ──────────────────────────
CACHE = "/tmp/claude-sl-git"


def git_branch():
    try:
        if time.time() - os.path.getmtime(CACHE) < 5:
            with open(CACHE) as f:
                cached_dir, cached_branch = f.read().split("\n", 1)
                if cached_dir == project_dir:
                    return cached_branch.strip()
    except (OSError, ValueError):
        pass
    try:
        br = subprocess.run(
            ["git", "-C", project_dir, "branch", "--show-current"],
            capture_output=True, text=True, timeout=2,
        ).stdout.strip()
        with open(CACHE, "w") as f:
            f.write(f"{project_dir}\n{br}")
        return br
    except Exception:
        return ""


branch = git_branch()

# ── Derived values ────────────────────────────────────────
in_t = fmt_tok(input_tokens)
out_t = fmt_tok(output_tokens)
cost_s = f"${cost_usd:.2f}"
dur_s = fmt_dur(duration_ms)

# Model diamond icon
if "opus" in model_id:
    m_icon = f"{RUST_B}◆{R}"
elif "sonnet" in model_id:
    m_icon = f"{BLU}◆{R}"
elif "haiku" in model_id:
    m_icon = f"{GRN}◆{R}"
else:
    m_icon = f"{GRY}◆{R}"

# Vim mode
vim_s = ""
if vim_mode == "NORMAL":
    vim_s = f" {RUST_BG}{WHT}{B} N {R}"
elif vim_mode == "INSERT":
    vim_s = f" {RUST_BG}{WHT}{B} I {R}"

# ── Build content strings ─────────────────────────────────
model_content = f"{m_icon} {RUST_B}{B}{model}{R}{vim_s}"

effort_content = ""
if effort_level:
    effort_content = f"{GRY}Effort:{R} {PUR}{effort_level}{R}"

session_content = ""
name = session_name()
if name:
    session_content = f"{GRY}Session:{R} {PUR}{name}{R}"

CWD_SEP = f"{R} {GRY}/{R} {PUR}"
cwd_prefix, cwd_segs = fmt_cwd(current_dir)
cwd_content = f"{GRY}cwd:{R} {PUR}{cwd_prefix}{CWD_SEP.join(cwd_segs)}{R}"

branch_content = ""
if branch:
    branch_content = (
        f"{GRY}⎇{R} {PUR}{truncate(branch)}{R}"
        f" {D}({R}{GRN}+{lines_added}{R}{D},{R}{RED}-{lines_removed}{R}{D}){R}"
    )

ctx_content = (
    f"{GRY}Context:{R} [{bar_clr}{bar}{R}] {WHT}{B}{fmt_ctx(used_tokens)}{R}"
    f"{GRY}/{R}{RUST}{ctx_label}{R} {D}({pct}%){R}{warn}"
)

cache_s = ""
if cache_total:
    hit_pct = cache_read * 100 // cache_total
    cache_s = f"  {CYN}⛁{R} {fmt_tok(cache_read)} {GRY}({hit_pct}%){R}"

stats_content = (
    f"{BLU}↓{R} {in_t}  {PUR}↑{R} {out_t}{cache_s}"
    f"  {RUST_D}│{R}  {YEL}{cost_s}{R}"
    f"  {RUST_D}│{R}  {GRY}⏱ {dur_s}{R}"
)

# ── Render (single line) ──────────────────────────────────
sep = f"  {RUST}│{R}  "
segments = [model_content]
if effort_content:
    segments.append(effort_content)
segments.append(cwd_content)
if branch_content:
    segments.append(branch_content)
mcp_content = f"{GRY}MCP:{R} {PUR}{mcp_count()}{R}"
segments += [mcp_content, ctx_content, stats_content]
left = sep.join(segments)

# Right-align session name: pad with spaces to terminal width (visible chars only)
ANSI_RE = re.compile(r"\033\[[0-9;]*m")


def visible_len(s):
    return len(ANSI_RE.sub("", s))


def term_width():
    for stream in (sys.stderr, sys.stdin, sys.stdout):
        try:
            return os.get_terminal_size(stream.fileno()).columns
        except (OSError, ValueError):
            pass
    return int(os.environ.get("COLUMNS", 120))


if session_content:
    gap = term_width() - visible_len(left) - visible_len(session_content) - 2
    if gap >= 2:
        print(f"{left}{' ' * gap}{session_content}")
    else:
        print(f"{left}{sep}{session_content}")
else:
    print(left)

More status lines

  • 233 installs
╭─ CONTEXT ────────────────────────────────────────────╮ │ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │ ├─ STATS ──────────────────────────────────────────────┤ │ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │ ├─ REPO ───────────────────────────────────────────────┤ │ ◆ Opus 4.8 · main · 16:21 │ ╰──────────────────────────────────────────────────────╯
  • 81 installs
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │ 5h 26% ↻2h06m │ 7d 7% ↻2d0h │ ◆ Opus 5.5 · main · 03:53
  • 3 installs
◆ Opus 4.8 ▏ ctx ▓▓░░░░░░░░ 22% ▏ high ▏ 10m main ▏ week ▓░░░░░░░░░ 7% ▏ 2d0h ▏ $0.41 ▏ +128/-34
Opus 4.8 | user | ~/app | main | ██░░░░░░░░ 22% | 37000↑ 1400↓ 2000c | $0.4100
  • 101 installs
Opus 4.8 [high] ~/app main 16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Opus 4.8 │ main │ app │ ▓▓░░░░░░░░ 44k/200k │ $0.41

Related mods

Token Weather Usage in Claude Desktop

One line above the prompt: context tokens with their weather, a bar per recent prompt, your 5-hour and 7-day limits against the time elapsed, the prompt cache with its time left, the session cost with the last prompt's share in dollars and in points of the 5-hour limit, and the subagents running. Labels in English, French or Russian. After Token Weather (Anthropic, Apache-2.0) and the ideas of usage-meter (HolyGrail) and prompt-cache-control (Daniel San).

Deck in Claude Desktop

A cockpit for Claude Code: plan bars and subagent strips, context, usage limits with reset countdowns and burn rates, spend, the model route and a pet. Six bar styles, three collapsed looks. CLI and Desktop.

Flightdeck in Claude Desktop

Flightdeck: a live agent dashboard for Claude Code. Main model vitals, an on-call architect, every permission check, subagent cards and swimlanes, a turn receipt and a session log, all from real session events