Skip to content

Instantly share code, notes, and snippets.

@JohnRDOrazio
Created May 2, 2026 23:25
Show Gist options
  • Select an option

  • Save JohnRDOrazio/acb88c05e7ab6f81929aaa81d12da812 to your computer and use it in GitHub Desktop.

Select an option

Save JohnRDOrazio/acb88c05e7ab6f81929aaa81d12da812 to your computer and use it in GitHub Desktop.
Claude Code multiline status: current folder, [github org/repo + branch (open PR)] | model, context consumption, hourly/weekly limits, time
#!/usr/bin/env python3
import json, sys, subprocess, re, os, time
data = json.load(sys.stdin)
model = data["model"]["display_name"]
directory = data["workspace"]["current_dir"]
dir_name = os.path.basename(directory)
pct = int(data.get("context_window", {}).get("used_percentage", 0) or 0)
duration_ms = int(data.get("cost", {}).get("total_duration_ms", 0) or 0)
# Rate limits (subscription plan)
rate = data.get("rate_limits", {})
five_h = rate.get("five_hour", {}).get("used_percentage")
week = rate.get("seven_day", {}).get("used_percentage")
# Colors
CYAN, GREEN, YELLOW, RED, RESET = "\033[36m", "\033[32m", "\033[33m", "\033[31m", "\033[0m"
MAGENTA = "\033[35m"
DIM = "\033[2m"
# OSC 8 clickable link: \033]8;;URL\a TEXT \033]8;;\a
def link(url, text):
if url:
return f"\033]8;;{url}\a{text}\033]8;;\a"
return text
# ── Cached git info (expensive operations) ───────────────
import hashlib
dir_hash = hashlib.md5(directory.encode()).hexdigest()[:8]
CACHE_FILE = f"/tmp/claude-statusline-git-{dir_hash}"
CACHE_MAX_AGE = 10 # seconds
def git(*args):
return subprocess.check_output(
["git", "-C", directory] + list(args),
stderr=subprocess.DEVNULL, text=True
).strip()
def refresh_cache():
info = {"repo": "", "owner": "", "remote_url": "", "upstream_owner": "", "upstream_url": "", "branch": "", "ahead": 0, "behind": 0, "pr": "", "pr_url": ""}
try:
git("rev-parse", "--git-dir")
except Exception:
return info
try:
raw = git("remote", "get-url", "origin")
url = re.sub(r"^git@github\.com:", "https://github.com/", raw)
info["remote_url"] = re.sub(r"\.git$", "", url)
parts = info["remote_url"].rstrip("/").split("/")
if len(parts) >= 2:
info["owner"] = parts[-2]
info["repo"] = parts[-1]
except Exception:
pass
try:
info["branch"] = git("branch", "--show-current")
except Exception:
pass
# Ahead/behind tracking branch
try:
status = git("status", "--branch", "--porcelain=v2")
for line in status.splitlines():
if line.startswith("# branch.ab"):
parts = line.split()
info["ahead"] = int(parts[2].lstrip("+"))
info["behind"] = int(parts[3].lstrip("-"))
break
except Exception:
pass
# Pass --repo explicitly since gh may default to upstream instead of origin
gh_repo = f"{info['owner']}/{info['repo']}" if info["owner"] and info["repo"] else ""
# Fork parent (authoritative via gh, independent of remote naming)
if gh_repo:
try:
parent_json = subprocess.check_output(
["gh", "repo", "view", gh_repo, "--json", "parent"],
stderr=subprocess.DEVNULL, text=True, cwd=directory, timeout=5
).strip()
parent = json.loads(parent_json).get("parent") if parent_json else None
if parent:
parent_owner = parent.get("owner", {}).get("login", "")
parent_name = parent.get("name", "")
if parent_owner and parent_name:
info["upstream_owner"] = parent_owner
info["upstream_url"] = f"https://github.com/{parent_owner}/{parent_name}"
except Exception:
pass
# Open PR for current branch (gh cli)
if info["branch"] and gh_repo:
try:
pr_json = subprocess.check_output(
["gh", "pr", "view", info["branch"], "--repo", gh_repo, "--json", "number,url"],
stderr=subprocess.DEVNULL, text=True, cwd=directory, timeout=5
).strip()
if pr_json:
pr_data = json.loads(pr_json)
info["pr"] = str(pr_data.get("number", ""))
info["pr_url"] = pr_data.get("url", "")
except Exception:
pass
return info
def load_cache():
try:
if os.path.exists(CACHE_FILE):
age = time.time() - os.path.getmtime(CACHE_FILE)
if age <= CACHE_MAX_AGE:
with open(CACHE_FILE) as f:
return json.load(f)
except Exception:
pass
return None
def save_cache(info):
try:
with open(CACHE_FILE, "w") as f:
json.dump(info, f)
except Exception:
pass
cached = load_cache()
if cached is None:
cached = refresh_cache()
save_cache(cached)
repo_name = cached["repo"]
owner = cached.get("owner", "")
remote_url = cached.get("remote_url", "")
upstream_owner = cached.get("upstream_owner", "")
upstream_url = cached.get("upstream_url", "")
branch = cached["branch"]
ahead = cached["ahead"]
behind = cached["behind"]
pr = cached["pr"]
pr_url = cached.get("pr_url", "")
# ── Line 1: Model | Context bar | Rate limits | Duration ──
bar_color = RED if pct >= 90 else YELLOW if pct >= 70 else GREEN
filled = pct // 10
bar = "█" * filled + "░" * (10 - filled)
limits_parts = []
if five_h is not None:
limits_parts.append(f"5h: {five_h:.0f}%")
if week is not None:
limits_parts.append(f"7d: {week:.0f}%")
mins, secs = duration_ms // 60000, (duration_ms % 60000) // 1000
line1 = f"{CYAN}[{model}]{RESET}"
line1 += f" | {bar_color}{bar}{RESET} {pct}%"
if limits_parts:
line1 += f" | {YELLOW}{' '.join(limits_parts)}{RESET}"
line1 += f" | ⏱️ {mins}m {secs}s"
# ── Line 2: Dir | [owner/repo branch ahead/behind] | PR ──
line2 = f"{YELLOW}📁 {dir_name}{RESET}"
if repo_name or branch:
git_parts = []
if repo_name:
full_repo = f"{owner}/{repo_name}" if owner else repo_name
repo_text = f" {full_repo}"
repo_piece = f"{MAGENTA}{link(remote_url, repo_text)}{RESET}"
if upstream_owner and upstream_owner != owner:
fork_text = f" {upstream_owner}"
repo_piece += f" {DIM}{link(upstream_url, fork_text)}{RESET}"
git_parts.append(repo_piece)
if branch:
branch_text = f" {branch}"
branch_url = f"{remote_url}/tree/{branch}" if remote_url else ""
branch_linked = f"{GREEN}{link(branch_url, branch_text)}{RESET}"
# Ahead/behind indicators
indicators = []
if ahead:
indicators.append(f"↑{ahead}")
if behind:
indicators.append(f"↓{behind}")
if indicators:
branch_linked += f" {DIM}{' '.join(indicators)}{RESET}"
git_parts.append(branch_linked)
line2 += f" [{' '.join(git_parts)}]"
if pr:
pr_text = f" #{pr}"
line2 += f" {CYAN}{link(pr_url, pr_text)}{RESET}"
# Write raw bytes to stdout to preserve escape sequences
output = f"{line2}\n{line1}\n"
sys.stdout.buffer.write(output.encode("utf-8"))
@JohnRDOrazio
Copy link
Copy Markdown
Author

JohnRDOrazio commented May 2, 2026

File goes under the ~/.claude folder as ~/.claude/statusline.py. To activate, add this to ~/.claude/settings.json:

  "statusLine": {
    "type": "command",
    "command": "python3 ~/.claude/statusline.py"
  },

or just ask claude code to activate it as the status line, and claude will modify the settings accordingly.

Results are cached, so don't show in real time, they are only updated after a "turn" in the conversation: when claude replies, the status line is updated. So if you change directories, it might not show until the next turn in which claude responds.

The status line uses a few glyphs to represent git branches / forks / octocat which are only available in nerd fonts, I'm currently using CaskaydiaCove Nerd Font Mono downloaded from here. Once the font is installed in the Windows Fonts, set it in the WSL terminal:

  1. right-click on the terminal window bar and choose settings
  2. select your terminal profile (i.e. Ubuntu 24.04.1 LTS, or similar)
  3. scroll down to Additional settings and select Appearance
  4. Font face -> select "CaskaydiaCove Nerd Font Mono" (or similar nerd font)
  5. Save, and close settings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment