|
| 1 | +#!/bin/bash |
| 2 | + # Claude Code Status Line - Multi-line with git status, context, cost, duration, and clickable links |
| 3 | + |
| 4 | + # Read JSON input from stdin |
| 5 | + input=$(cat) |
| 6 | + |
| 7 | + # Extract values from JSON using jq |
| 8 | + model_name=$(echo "$input" | jq -r '.model.display_name // "Unknown"') |
| 9 | + cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "."') |
| 10 | + context_pct=$(echo "$input" | jq -r '(.context_window.used_percentage // 0) | if . <= 1 and . > 0 then . * 100 else . end | floor') |
| 11 | + cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // 0') |
| 12 | + duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0') |
| 13 | + |
| 14 | + # Colors |
| 15 | + WHITE='\033[37m' |
| 16 | + GREEN='\033[32m' |
| 17 | + YELLOW='\033[33m' |
| 18 | + RED='\033[31m' |
| 19 | + BLUE='\033[34m' |
| 20 | + CYAN='\033[36m' |
| 21 | + MAGENTA='\033[35m' |
| 22 | + GRAY='\033[90m' |
| 23 | + BOLD_GRAY='\033[1;90m' |
| 24 | + RESET='\033[0m' |
| 25 | + |
| 26 | + # Format cost (2 decimal places) |
| 27 | + cost_formatted=$(printf '$%.2f' "$cost_usd") |
| 28 | + |
| 29 | + # Format duration |
| 30 | + duration_sec=$((duration_ms / 1000)) |
| 31 | + duration_min=$((duration_sec / 60)) |
| 32 | + duration_sec_rem=$((duration_sec % 60)) |
| 33 | + duration_formatted="${duration_min}m ${duration_sec_rem}s" |
| 34 | + |
| 35 | + # Git information |
| 36 | + git_branch="" |
| 37 | + staged_count=0 |
| 38 | + modified_count=0 |
| 39 | + remote_url="" |
| 40 | + repo_name="" |
| 41 | + |
| 42 | + if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then |
| 43 | + git_branch=$(git -C "$cwd" branch --show-current 2>/dev/null || echo "detached") |
| 44 | + staged_count=$(git -C "$cwd" diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ') |
| 45 | + modified_count=$(git -C "$cwd" diff --numstat 2>/dev/null | wc -l | tr -d ' ') |
| 46 | + remote_url=$(git -C "$cwd" config --get remote.origin.url 2>/dev/null) |
| 47 | + repo_name=$(basename "$cwd") |
| 48 | + fi |
| 49 | + |
| 50 | + # Convert remote URL to GitHub HTTPS URL |
| 51 | + github_url="" |
| 52 | + if [[ "$remote_url" =~ ^git@github\.com:(.+)\.git$ ]]; then |
| 53 | + github_url="https://github.com/${BASH_REMATCH[1]}" |
| 54 | + elif [[ "$remote_url" =~ ^git@github\.com:(.+)$ ]]; then |
| 55 | + github_url="https://github.com/${BASH_REMATCH[1]}" |
| 56 | + elif [[ "$remote_url" =~ ^https://github\.com/(.+)\.git$ ]]; then |
| 57 | + github_url="https://github.com/${BASH_REMATCH[1]}" |
| 58 | + elif [[ "$remote_url" =~ ^https://github\.com/(.+)$ ]]; then |
| 59 | + github_url="${remote_url}" |
| 60 | + fi |
| 61 | + |
| 62 | + # Create clickable repo name using OSC 8 escape sequences |
| 63 | + if [ -n "$github_url" ] && [ -n "$repo_name" ]; then |
| 64 | + # OSC 8 format: ESC ] 8 ; ; URL BEL text ESC ] 8 ; ; BEL |
| 65 | + clickable_repo=$(printf '\033]8;;%s\a%s\033]8;;\a' "$github_url" "$repo_name") |
| 66 | + else |
| 67 | + clickable_repo="$repo_name" |
| 68 | + fi |
| 69 | + |
| 70 | + # Build git status with colors |
| 71 | + git_status="" |
| 72 | + if [ "$staged_count" -gt 0 ]; then |
| 73 | + git_status="${git_status}${GREEN}+${staged_count}${RESET}" |
| 74 | + fi |
| 75 | + if [ "$modified_count" -gt 0 ]; then |
| 76 | + [ -n "$git_status" ] && git_status="${git_status} " |
| 77 | + git_status="${git_status}${YELLOW}~${modified_count}${RESET}" |
| 78 | + fi |
| 79 | + |
| 80 | + # Build progress bar for context window (15 chars) |
| 81 | + bar_width=15 |
| 82 | + filled=$((context_pct * bar_width / 100)) |
| 83 | + [ "$filled" -gt "$bar_width" ] && filled=$bar_width |
| 84 | + empty=$((bar_width - filled)) |
| 85 | + |
| 86 | + # Choose bar color based on usage |
| 87 | + if [ "$context_pct" -lt 50 ]; then |
| 88 | + bar_color="$GREEN" |
| 89 | + elif [ "$context_pct" -lt 80 ]; then |
| 90 | + bar_color="$YELLOW" |
| 91 | + else |
| 92 | + bar_color="$RED" |
| 93 | + fi |
| 94 | + |
| 95 | + # Build the progress bar with checkered/diagonal pattern |
| 96 | + bar="" |
| 97 | + [ "$filled" -gt 0 ] && bar=$(printf "%${filled}s" | sed 's/ /▓/g') |
| 98 | + [ "$empty" -gt 0 ] && bar="${bar}$(printf "%${empty}s" | sed 's/ /░/g')" |
| 99 | + |
| 100 | + # Short directory name |
| 101 | + dir_name="${cwd##*/}" |
| 102 | + |
| 103 | + # === LINE 1: Model, folder icon, directory, branch icon, git branch with colored status === |
| 104 | + line1="${BOLD_GRAY}[${model_name}]${RESET} ${GRAY}${clickable_repo}${RESET}" |
| 105 | + if [ -n "$git_branch" ]; then |
| 106 | + line1="${line1} ${GRAY}|${RESET} ${GRAY}${git_branch}${RESET}" |
| 107 | + if [ -n "$git_status" ]; then |
| 108 | + line1="${line1} ${git_status}" |
| 109 | + fi |
| 110 | + fi |
| 111 | + |
| 112 | + # === LINE 2: Context bar, percentage, cost, clock icon, duration === |
| 113 | + line2="${bar_color}${bar}${RESET} ${context_pct}% ${GRAY}|${RESET} ${GRAY}${cost_formatted}${RESET} ${GRAY}|${RESET} ${GRAY}${duration_formatted}${RESET}" |
| 114 | + |
| 115 | + # Output both lines |
| 116 | + printf '%b\n' "$line1" |
| 117 | + printf '%b\n' "$line2" |
| 118 | + |
0 commit comments