Skip to content

Instantly share code, notes, and snippets.

@intellectronica
Last active November 6, 2025 19:03
Show Gist options
  • Save intellectronica/dc7405a5635b2b60c50b92ed7f29510f to your computer and use it in GitHub Desktop.
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)
#!/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()
@intellectronica
Copy link
Author

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

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

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