statuslin.es

Dreambase Panel

python

A boxed multi-line panel with auto-sizing borders: a context section with a gradient bar and status word, a stats section (tokens, cost, duration, lines changed), and a repo section (model, git branch with file counts, clock, vim mode), in a terracotta Claude-brand palette. Source: github.com/kyleledbetter/claudecode-statusline

Preview

Clean repo
╭─ CONTEXT ────────────────────────────────────────────╮ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Opus 4.8 · main · 17:27 ╰──────────────────────────────────────────────────────╯
New session
╭─ CONTEXT ──────────────────────────────────────╮ ░░░░░░░░░░░░░░░░░░░░░░ 0% of 200K · GOOD ├─ STATS ────────────────────────────────────────┤ 0 0 $0.00 ⏱ 10m 12s ├─ REPO ─────────────────────────────────────────┤ Opus 4.8 · main · 17:27 ╰────────────────────────────────────────────────╯
Dirty branch
╭─ CONTEXT ────────────────────────────────────────────╮ ██████████▌░░░░░░░░░░░ 48% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 96.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Sonnet 4.6 · feat/auth ~1 ?1 · 17:27 I ╰──────────────────────────────────────────────────────╯
Near-full
╭─ CONTEXT ─────────────────────────────────────────────╮ ████████████████████░░ 91% of 200K · HIGH ├─ STATS ───────────────────────────────────────────────┤ 182.0K 1.4K $4.12 ⏱ 10m 12s +128/-34 ├─ REPO ────────────────────────────────────────────────┤ Opus 4.8 · main · 17:27 ╰───────────────────────────────────────────────────────╯
1M context
╭─ CONTEXT ─────────────────────────────────────────────╮ ██████████████░░░░░░░░ 64% of 1M · OK ├─ STATS ───────────────────────────────────────────────┤ 640.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ────────────────────────────────────────────────┤ Fable 5 · main · 17:27 ╰───────────────────────────────────────────────────────╯
Post-compact
╭─ CONTEXT ──────────────────────────────────────╮ ░░░░░░░░░░░░░░░░░░░░░░ 0% of 200K · GOOD ├─ STATS ────────────────────────────────────────┤ 0 0 $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ─────────────────────────────────────────┤ Haiku 4.5 · main · 17:27 N ╰────────────────────────────────────────────────╯
Worktree
╭─ CONTEXT ────────────────────────────────────────────╮ ████████▏░░░░░░░░░░░░░ 37% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 74.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Opus 4.8 · 17:27 ╰──────────────────────────────────────────────────────╯
Non-git
╭─ CONTEXT ────────────────────────────────────────────╮ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Opus 4.8 · 17:27 ╰──────────────────────────────────────────────────────╯

Source

#!/usr/bin/env python3
"""Claude Code Status Line — Dreambase Edition
   A branded mini status app with box borders and section headers.
"""

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", "")

# ── 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"

# ── Layout engine ─────────────────────────────────────────
MIN_IW = 46  # minimum inner content width


def vlen(s):
    """Visible length excluding ANSI escapes."""
    return len(re.sub(r"\033\[[^m]*m", "", s))


def make_renderer(content_rows):
    """Compute box width from widest content, return render functions."""
    iw = max(MIN_IW, max(vlen(c) for c in content_rows))
    tw = iw + 4

    def pad(s):
        return s + " " * max(0, iw - vlen(s))

    def border_top(label=""):
        if label:
            fill = tw - 5 - len(label)
            return f"{RUST}╭─ {RUST_B}{B}{label}{R}{RUST} {'─' * fill}{R}"
        return f"{RUST}{'─' * (tw - 2)}{R}"

    def border_mid(label=""):
        if label:
            fill = tw - 5 - len(label)
            return f"{RUST}├─ {RUST_B}{B}{label}{R}{RUST} {'─' * fill}{R}"
        return f"{RUST}{'─' * (tw - 2)}{R}"

    def border_bot():
        return f"{RUST}{'─' * (tw - 2)}{R}"

    def row(content):
        return f"{RUST}{R} {pad(content)} {RUST}{R}"

    return border_top, border_mid, border_bot, row


# ── 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


# ── Progress bar ──────────────────────────────────────────
BAR_W = 22
BLOCKS = " ▏▎▍▌▋▊▉█"

if pct >= 95:
    bar_clr = RED
    status_label = f"{RED}{B}CRITICAL{R}"
elif pct >= 85:
    bar_clr = ORG
    status_label = f"{ORG}HIGH{R}"
elif pct >= 70:
    bar_clr = YEL
    status_label = f"{YEL}MODERATE{R}"
elif pct >= 50:
    bar_clr = LIME
    status_label = f"{LIME}OK{R}"
else:
    bar_clr = GRN
    status_label = f"{GRN}GOOD{R}"

eighths = pct * BAR_W * 8 // 100
full = eighths // 8
partial = eighths % 8
empty = BAR_W - full - (1 if partial else 0)

bar = "█" * full
if partial:
    bar += BLOCKS[partial]
bar += "░" * empty

ctx_label = "1M" if ctx_size >= 1_000_000 else "200K"
warn = f" {RED}{B}{R}" if exceeds_200k else ""

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


def git_info():
    try:
        if time.time() - os.path.getmtime(CACHE) < 5:
            with open(CACHE) as f:
                p = f.read().strip().split("|")
                if len(p) == 4:
                    return p
    except (OSError, ValueError):
        pass
    try:
        br = subprocess.run(
            ["git", "-C", project_dir, "branch", "--show-current"],
            capture_output=True, text=True, timeout=2,
        ).stdout.strip()
        if not br:
            return ["", "0", "0", "0"]
        st = len(subprocess.run(
            ["git", "-C", project_dir, "diff", "--cached", "--numstat"],
            capture_output=True, text=True, timeout=2,
        ).stdout.strip().splitlines())
        mo = len(subprocess.run(
            ["git", "-C", project_dir, "diff", "--numstat"],
            capture_output=True, text=True, timeout=2,
        ).stdout.strip().splitlines())
        ut = len(subprocess.run(
            ["git", "-C", project_dir, "ls-files", "--others", "--exclude-standard"],
            capture_output=True, text=True, timeout=2,
        ).stdout.strip().splitlines())
        result = [br, str(st), str(mo), str(ut)]
        with open(CACHE, "w") as f:
            f.write("|".join(result))
        return result
    except Exception:
        return ["", "0", "0", "0"]


branch, staged, modified, untracked = git_info()
staged, modified, untracked = int(staged), int(modified), int(untracked)

# ── 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)
clock = time.strftime("%H:%M")

# Git indicators
git_ind = ""
if staged:
    git_ind += f" {GRN}+{staged}{R}"
if modified:
    git_ind += f" {YEL}~{modified}{R}"
if untracked:
    git_ind += f" {GRY}?{untracked}{R}"

# Lines changed
lines_s = ""
if lines_added or lines_removed:
    lines_s = f"{GRN}+{lines_added}{R}{D}/{R}{RED}-{lines_removed}{R}"

# 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 ─────────────────────────────────
ctx_content = (
    f"{bar_clr}{bar}{R}  {WHT}{B}{pct}%{R}{warn}"
    f" {D}of{R} {RUST}{ctx_label}{R}  {D}·{R}  {status_label}"
)

stats_content = (
    f"{BLU}{R} {in_t}  {PUR}{R} {out_t}"
    f"  {RUST_D}{R}  {YEL}{cost_s}{R}"
    f"  {RUST_D}{R}  {GRY}{dur_s}{R}"
    + (f"  {RUST_D}{R}  {lines_s}" if lines_s else "")
)

repo_content = f"{m_icon} {RUST_B}{B}{model}{R}"
if branch:
    repo_content += f"  {RUST_D}·{R}  {PUR}{truncate(branch)}{R}{git_ind}"
repo_content += f"  {RUST_D}·{R}  {GRY}{clock}{R}"
repo_content += vim_s

# ── Render (box auto-sizes to widest row) ─────────────────
border_top, border_mid, border_bot, row = make_renderer(
    [ctx_content, stats_content, repo_content]
)

print(border_top("CONTEXT"))
print(row(ctx_content))
print(border_mid("STATS"))
print(row(stats_content))
print(border_mid("REPO"))
print(row(repo_content))
print(border_bot())