Last active
November 6, 2025 19:03
-
-
Save intellectronica/dc7405a5635b2b60c50b92ed7f29510f to your computer and use it in GitHub Desktop.
My Claude Code custom status line (directory ・ git ・ mcp servers ・ cost (if API key) ・ model (and thinking)・ context window ・ version)
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 -S uv run | |
| # /// script | |
| # requires-python = ">=3.12" | |
| # dependencies = [] | |
| # /// | |
| """ | |
| Claude Code Status Line - Eleanor's Custom Edition | |
| """ | |
| import json | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import Any | |
| DEBUG = True | |
| class Colours: | |
| AMBER = "\033[38;2;255;159;0m" | |
| ORANGE = "\033[38;2;255;119;0m" | |
| PURPLE = "\033[38;2;168;85;247m" | |
| BLUE = "\033[38;2;59;130;246m" | |
| CYAN = "\033[38;2;34;211;238m" | |
| GREEN = "\033[38;2;34;197;94m" | |
| RED = "\033[38;2;239;68;68m" | |
| YELLOW = "\033[38;2;234;179;8m" | |
| GRAY = "\033[38;2;156;163;175m" | |
| DARK_GRAY = "\033[38;2;107;114;128m" | |
| WHITE = "\033[38;2;248;250;252m" | |
| BOLD = "\033[1m" | |
| DIM = "\033[2m" | |
| RESET = "\033[0m" | |
| class Icons: | |
| THINKING = "✳︎" | |
| GIT_CLEAN = "✓" | |
| GIT_DIRTY = "±" | |
| GIT_AHEAD = "↑" | |
| GIT_BEHIND = "↓" | |
| COST = "$" | |
| def get_git_info(cwd: str) -> dict[str, Any]: | |
| """Get git repository information.""" | |
| try: | |
| subprocess.run( | |
| ["git", "rev-parse", "--git-dir"], | |
| cwd=cwd, | |
| capture_output=True, | |
| check=True, | |
| timeout=1, | |
| ) | |
| branch = subprocess.run( | |
| ["git", "rev-parse", "--abbrev-ref", "HEAD"], | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| timeout=1, | |
| ).stdout.strip() | |
| status_output = subprocess.run( | |
| ["git", "status", "--porcelain", "--branch"], | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| timeout=1, | |
| ).stdout | |
| is_clean = len([line for line in status_output.split("\n") if line and not line.startswith("##")]) == 0 | |
| ahead, behind = 0, 0 | |
| branch_line = [line for line in status_output.split("\n") if line.startswith("##")] | |
| if branch_line: | |
| line = branch_line[0] | |
| if "ahead" in line: | |
| ahead = int(line.split("ahead ")[1].split("]")[0].split(",")[0]) | |
| if "behind" in line: | |
| behind = int(line.split("behind ")[1].split("]")[0].split(",")[0]) | |
| return { | |
| "branch": branch, | |
| "clean": is_clean, | |
| "ahead": ahead, | |
| "behind": behind, | |
| } | |
| except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError): | |
| return None | |
| def format_git_status(git_info: dict[str, Any]) -> str: | |
| """Format git status with colours and icons.""" | |
| if not git_info: | |
| return "" | |
| parts = [] | |
| branch_colour = Colours.CYAN if git_info["clean"] else Colours.YELLOW | |
| parts.append(f"{branch_colour}{git_info['branch']}{Colours.RESET}") | |
| if git_info["clean"]: | |
| parts.append(f"{Colours.GREEN}{Icons.GIT_CLEAN}{Colours.RESET}") | |
| else: | |
| parts.append(f"{Colours.YELLOW}{Icons.GIT_DIRTY}{Colours.RESET}") | |
| if git_info["ahead"]: | |
| parts.append(f"{Colours.GREEN}{Icons.GIT_AHEAD}{git_info['ahead']}{Colours.RESET}") | |
| if git_info["behind"]: | |
| parts.append(f"{Colours.RED}{Icons.GIT_BEHIND}{git_info['behind']}{Colours.RESET}") | |
| return " ".join(parts) | |
| def get_context_info(data: dict[str, Any]) -> str | None: | |
| """Get context window usage from transcript.""" | |
| try: | |
| transcript_path = data.get("transcript_path") | |
| if not transcript_path or not Path(transcript_path).exists(): | |
| return None | |
| # JSONL format: each line is a separate JSON object | |
| latest_usage = None | |
| with open(transcript_path, 'r') as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| entry = json.loads(line) # Parse each line individually | |
| # Skip non-message entries (summaries, metadata) | |
| if entry.get("type") not in ("assistant", "user"): | |
| continue | |
| # Usage is nested: entry -> message -> usage | |
| message = entry.get("message", {}) | |
| usage = message.get("usage", {}) | |
| if usage and usage.get("input_tokens"): | |
| latest_usage = usage | |
| except json.JSONDecodeError: | |
| continue | |
| if not latest_usage: | |
| return None | |
| # Total includes cache tokens | |
| total_tokens = ( | |
| latest_usage.get("input_tokens", 0) + | |
| latest_usage.get("cache_creation_input_tokens", 0) + | |
| latest_usage.get("cache_read_input_tokens", 0) | |
| ) | |
| # Claude Code context is ~200k tokens with auto-compact at ~160k | |
| max_context = 200_000 | |
| percentage = (total_tokens / max_context) * 100 | |
| if percentage < 30: | |
| colour = Colours.GREEN | |
| elif percentage < 60: | |
| colour = Colours.YELLOW | |
| else: | |
| colour = Colours.RED | |
| return f"{Colours.DIM}{colour}{percentage:.0f}%{Colours.RESET}" | |
| except (FileNotFoundError, IOError): | |
| return None | |
| def get_thinking_mode(data: dict[str, Any]) -> str | None: | |
| """Get the current thinking mode.""" | |
| try: | |
| settings_path = Path.home() / ".claude" / "settings.json" | |
| if not settings_path.exists(): | |
| return None | |
| with open(settings_path) as f: | |
| settings = json.load(f) | |
| always_thinking = settings.get("alwaysThinkingEnabled", False) | |
| if always_thinking: | |
| return f"{Icons.THINKING}" | |
| return None | |
| except (json.JSONDecodeError, FileNotFoundError, KeyError): | |
| return None | |
| def get_mcp_servers(data: dict[str, Any]) -> list[str]: | |
| """ | |
| Get list of configured MCP servers from all locations. | |
| Returns list of server names. | |
| """ | |
| mcp_servers = [] | |
| # 1. Check ~/.claude.json for user-level MCP servers | |
| try: | |
| claude_config = Path.home() / ".claude.json" | |
| if claude_config.exists(): | |
| with open(claude_config) as f: | |
| config = json.load(f) | |
| servers = config.get("mcpServers", {}) | |
| mcp_servers.extend(servers.keys()) | |
| except (json.JSONDecodeError, FileNotFoundError, KeyError): | |
| pass | |
| # 2. Check project-level MCP configurations | |
| cwd = data.get("workspace", {}).get("current_dir") or data.get("cwd", "") | |
| if cwd: | |
| # Check .mcp.json in project root | |
| project_mcp = Path(cwd) / ".mcp.json" | |
| if project_mcp.exists(): | |
| try: | |
| with open(project_mcp) as f: | |
| mcp_config = json.load(f) | |
| servers = mcp_config.get("mcpServers", {}) | |
| mcp_servers.extend(servers.keys()) | |
| except (json.JSONDecodeError, FileNotFoundError, KeyError): | |
| pass | |
| # Check .claude/.mcp.json | |
| claude_mcp = Path(cwd) / ".claude" / ".mcp.json" | |
| if claude_mcp.exists(): | |
| try: | |
| with open(claude_mcp) as f: | |
| mcp_config = json.load(f) | |
| servers = mcp_config.get("mcpServers", {}) | |
| mcp_servers.extend(servers.keys()) | |
| except (json.JSONDecodeError, FileNotFoundError, KeyError): | |
| pass | |
| # Remove duplicates while preserving order | |
| seen = set() | |
| unique = [] | |
| for server in mcp_servers: | |
| if server not in seen: | |
| seen.add(server) | |
| unique.append(server) | |
| return unique | |
| def format_mcp_servers(server_names: list[str]) -> str | None: | |
| """Format MCP server list for display.""" | |
| if not server_names: | |
| return None | |
| display = " ".join(server_names).strip() | |
| return f"{Colours.PURPLE}{Colours.DIM}{display}{Colours.RESET}" | |
| def is_using_api_key() -> bool: | |
| """ | |
| Determine if Claude Code is using API key authentication. | |
| Returns True if using API key, False if using subscription. | |
| """ | |
| claude_config = Path.home() / ".claude.json" | |
| if not claude_config.exists(): | |
| return True | |
| try: | |
| with open(claude_config) as f: | |
| config = json.load(f) | |
| # If oauthAccount exists with valid data, using subscription | |
| oauth_account = config.get("oauthAccount", {}) | |
| if oauth_account and oauth_account.get("accountUuid"): | |
| return False | |
| return True | |
| except (json.JSONDecodeError, KeyError): | |
| return True | |
| def get_cost_info(data: dict[str, Any]) -> str | None: | |
| """Get session cost information.""" | |
| if not is_using_api_key(): | |
| return None | |
| cost_data = data.get("cost", {}) | |
| if cost_data and cost_data.get("total_cost_usd"): | |
| cost = cost_data["total_cost_usd"] | |
| colour = Colours.GREEN if cost < 1.0 else Colours.YELLOW if cost < 5.0 else Colours.RED | |
| return f"{colour}{Icons.COST} ${cost:.3f}{Colours.RESET}" | |
| return None | |
| def main(): | |
| """Main status line generator.""" | |
| try: | |
| input_data = json.loads(sys.stdin.read()) | |
| except json.JSONDecodeError: | |
| print("Error: Invalid JSON input", file=sys.stderr) | |
| sys.exit(2) | |
| if DEBUG: | |
| try: | |
| log_path = Path("/tmp/claude-statusline.jsonl") | |
| with open(log_path, "a") as f: | |
| f.write(json.dumps(input_data, indent=2)) | |
| f.write("\n") | |
| except (IOError, OSError) as e: | |
| print(f"Warning: Could not write log file: {e}", file=sys.stderr) | |
| parts = [] | |
| cwd = input_data.get("workspace", {}).get("current_dir", "") | |
| if cwd: | |
| dir_name = Path(cwd).name or "/" | |
| parts.append(f"{Colours.BLUE}{dir_name}{Colours.RESET}") | |
| if cwd: | |
| git_info = get_git_info(cwd) | |
| if git_info: | |
| git_status = format_git_status(git_info) | |
| parts.append(git_status) | |
| server_names = get_mcp_servers(input_data) | |
| if server_names: | |
| mcp = format_mcp_servers(server_names) | |
| if mcp: | |
| parts.append(mcp) | |
| cost = get_cost_info(input_data) | |
| if cost: | |
| parts.append(cost) | |
| model = input_data.get("model", {}) | |
| model_name = model.get("display_name", "Unknown") | |
| model_name = "".join(c for c in model_name if c.isalpha()).lower() | |
| thinking = get_thinking_mode(input_data) | |
| if thinking: | |
| model_name += " " + thinking | |
| parts.append(f"{Colours.AMBER}{model_name}{Colours.RESET}") | |
| context = get_context_info(input_data) | |
| if context: | |
| parts.append(context) | |
| version = input_data.get("version") | |
| if version: | |
| parts.append(f"{Colours.GRAY}{Colours.DIM}v{version}{Colours.RESET}") | |
| print(f"{Colours.DARK_GRAY} ・ {Colours.RESET}".join(parts)) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

To install, add this to
~/.claude/settings.json: