Created
May 2, 2026 23:25
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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")) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
File goes under the
~/.claudefolder as~/.claude/statusline.py. To activate, add this to~/.claude/settings.json: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 Monodownloaded from here. Once the font is installed in the Windows Fonts, set it in the WSL terminal:settings