Skip to content

Instantly share code, notes, and snippets.

@paulrobello
Last active December 23, 2025 05:24
Show Gist options
  • Select an option

  • Save paulrobello/6777e2dae8945900328e18005f6032c5 to your computer and use it in GitHub Desktop.

Select an option

Save paulrobello/6777e2dae8945900328e18005f6032c5 to your computer and use it in GitHub Desktop.
Claude Code Status line uber script - customizable statusline with toggles, colors, git info, AI summary, and more
#!/bin/bash
# Statusline Script for Claude Code
# https://gist.github.com/paulrobello/6777e2dae8945900328e18005f6032c5
#
# === INSTALLATION ===
# 1. Copy this script to ~/.claude/statusline.sh:
# cp statusline.sh ~/.claude/statusline.sh
#
# 2. Make it executable:
# chmod +x ~/.claude/statusline.sh
#
# 3. Ensure jq is installed:
# # macOS
# brew install jq
# # Ubuntu/Debian
# sudo apt install jq
# # Fedora/RHEL
# sudo dnf install jq
# # Windows (via scoop)
# scoop install jq
# # Windows (manual)
# Download https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-windows-amd64.exe
# Rename to jq.exe and place in C:\Program Files\Git\usr\bin
#
# 4. Add to ~/.claude/settings.json:
# {
# "statusLine": {
# "type": "command",
# "command": "~/.claude/statusline.sh"
# }
# }
#
# Windows users use this command format instead:
# {
# "statusLine": {
# "type": "command",
# "command": "\"C:/Program Files/Git/bin/bash.exe\" ~/.claude/statusline.sh"
# }
# }
#
# Or merge with existing settings.json using jq:
# jq '. + {"statusLine": {"type": "command", "command": "~/.claude/statusline.sh"}}' \
# ~/.claude/settings.json > /tmp/settings.json && mv /tmp/settings.json ~/.claude/settings.json
#
# === END INSTALLATION ===
#
# === CLI OPTIONS ===
# --update Download and install the latest version from gist
# --debug Enable debug logging to ~/.claude/statusline.log
#
# Features:
# - Customizable display components with toggles
# - Debug logging with --debug flag
# - Full color customization support
# - Cross-platform compatible (Linux, macOS, Windows via Git Bash/MSYS/Cygwin)
# - Context window tracking with optional auto-compact buffer adjustment
# - Supports both new context_window API and legacy transcript fallback
#
# Display order:
# Line 1: πŸ• time | πŸ‘€ user@host | πŸ“ path | 🌿 git branch (βœ“/● status, ↑↓ ahead/behind)
# πŸ”– version | πŸ€– model | 🎨 style | 🧠 context % | πŸ“‹ todos | πŸ†” session_id
# βœ‰οΈ messages | ⏱️ duration | πŸ’° cost | πŸ”₯ burn rate | session name
# Line 2: β†’ Last user prompt
# Line 3: πŸ“„ AI-powered conversation summary (via Claude Haiku)
# Line 4: +lines/-lines (GitHub-style indicators)
# Force color output even when not in a TTY
export TERM=xterm-256color
# === PLATFORM DETECTION ===
# Detect if running on Windows (Git Bash, MSYS, Cygwin, WSL)
IS_WINDOWS=false
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
IS_WINDOWS=true
elif [[ -n "$WINDIR" ]] || [[ -n "$windir" ]]; then
IS_WINDOWS=true
fi
# Cross-platform hostname function (Windows doesn't support -s flag)
get_hostname() {
if [ "$IS_WINDOWS" = true ]; then
hostname | cut -d'.' -f1
else
hostname -s 2>/dev/null || hostname | cut -d'.' -f1
fi
}
# === COLOR CONFIGURATION ===
# Customize these variables to change the statusline appearance
# Format: \033[<style>;<fg>;<bg>m where style=01 is bold, fg=3X, bg=4X
# Colors: 0=black, 1=red, 2=green, 3=yellow, 4=blue, 5=magenta, 6=cyan, 7=white
# Background (40=black, 41=red, 42=green, 43=yellow, 44=blue, 45=magenta, 46=cyan, 47=white, 49=default/none)
BG="49"
# Reset code
RESET="\033[00m"
# Path color (light blue/cyan on black)
COLOR_PATH="\033[01;94;${BG}m"
# Git branch color (bold cyan on black)
COLOR_BRANCH="\033[01;36;${BG}m"
# Model badge color (bold white on black)
COLOR_MODEL="\033[01;37;${BG}m"
# Version color (bold magenta on black)
COLOR_VERSION="\033[01;35;${BG}m"
# Output style color (bold yellow on black)
COLOR_STYLE="\033[01;33;${BG}m"
# Prompt arrow color (bold yellow on black)
COLOR_ARROW="\033[01;33;${BG}m"
# Summary icon color (bold cyan on black)
COLOR_SUMMARY="\033[01;36;${BG}m"
# Lines added color (bold green on black)
COLOR_ADDED="\033[01;32;${BG}m"
# Lines removed color (bold red on black)
COLOR_REMOVED="\033[01;31;${BG}m"
# Username color (bold red on black)
COLOR_USER="\033[01;31;${BG}m"
# At sign color (bold yellow on black)
COLOR_AT="\033[01;33;${BG}m"
# Hostname color (bold green on black)
COLOR_HOST="\033[01;32;${BG}m"
# Context usage color (bold white on black)
COLOR_CONTEXT="\033[01;37;${BG}m"
# Session info color (bold cyan on black)
COLOR_SESSION="\033[01;36;${BG}m"
# === DISPLAY TOGGLES ===
# Set to "true" to show, "false" to hide
# Ordered by display position in statusline
# Line 1: Main status bar
SHOW_CURRENT_TIME="false" # πŸ• Current time (HH:MM)
SHOW_USERNAME="true" # πŸ‘€ Username display
SHOW_HOSTNAME="true" # πŸ‘€ Hostname display (combined with username as user@host)
SHOW_PATH="true" # πŸ“ Current working directory path
SHOW_GIT_BRANCH="true" # 🌿 Git branch name with status indicator (βœ“/●)
SHOW_GIT_AHEAD_BEHIND="true" # ↑↓ Git ahead/behind commit counts
SHOW_VERSION="false" # πŸ”– Claude Code version
SHOW_MODEL="true" # πŸ€– Claude model name
SHOW_OUTPUT_STYLE="false" # 🎨 Output style name
SHOW_CONTEXT_REMAINING="true" # 🧠 Context remaining percentage (CR: X%)
SHOW_TODO_COUNT="true" # πŸ“‹ Pending/in-progress todo count
SHOW_SESSION_ID="false" # πŸ†” Session ID (first 8 chars)
SHOW_MESSAGE_COUNT="false" # βœ‰οΈ Number of user messages
SHOW_SESSION_DURATION="false" # ⏱️ Session duration (min:sec)
SHOW_SESSION_COST="false" # πŸ’° Session cost in USD
SHOW_BURN_RATE="false" # πŸ”₯ Burn rate in $/hr
SHOW_SESSION_NAME="false" # Session slug/name from transcript
# Line 2: Last user prompt
SHOW_LAST_PROMPT="true" # β†’ Last user prompt text
# Line 3: AI summary
SHOW_AI_SUMMARY="false" # πŸ“„ AI-generated conversation summary
# Line 4: Line changes (optional)
SHOW_LINE_CHANGES="false" # +/- GitHub-style lines added/removed
# === CONTEXT CALCULATION SETTINGS ===
# Auto-compact buffer: tokens reserved for auto-compaction
# Computed as CLAUDE_CODE_MAX_OUTPUT_TOKENS + 13000 (default 32000 + 13000 = 45000)
# When USE_AUTO_COMPACT is true, this buffer is added to consumed tokens
AUTO_COMPACT_BUFFER=$(( ${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-32000} + 13000 ))
USE_AUTO_COMPACT="false" # Account for auto-compact reserved tokens in context calculation
# === GIST URL FOR UPDATES ===
GIST_RAW_URL="https://gist.githubusercontent.com/paulrobello/6777e2dae8945900328e18005f6032c5/raw/statusline.sh"
# Parse command line arguments
DEBUG_LOG=false
# Self-update from gist
if [ "$1" = "--update" ]; then
SCRIPT_PATH="${BASH_SOURCE[0]}"
# Resolve symlinks to get actual script location
while [ -L "$SCRIPT_PATH" ]; do
SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)"
SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
[[ $SCRIPT_PATH != /* ]] && SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_PATH"
done
SCRIPT_PATH="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)/$(basename "$SCRIPT_PATH")"
echo "Downloading latest version from gist..."
TMP_FILE=$(mktemp)
if curl -fsSL --connect-timeout 10 "$GIST_RAW_URL" -o "$TMP_FILE" 2>/dev/null; then
# Verify downloaded file is valid (has shebang)
if head -1 "$TMP_FILE" | grep -q '^#!/bin/bash'; then
cp "$TMP_FILE" "$SCRIPT_PATH"
chmod +x "$SCRIPT_PATH"
rm -f "$TMP_FILE"
echo "Updated successfully: $SCRIPT_PATH"
exit 0
else
rm -f "$TMP_FILE"
echo "Error: Downloaded file is not a valid bash script" >&2
exit 1
fi
else
rm -f "$TMP_FILE"
echo "Error: Failed to download from gist" >&2
exit 1
fi
fi
if [ "$1" = "--debug" ]; then
DEBUG_LOG=true
shift
fi
input=$(cat)
# === DEBUG LOGGING ===
# Log input for debugging if flag is set
if [ "$DEBUG_LOG" = true ]; then
LOG_FILE="$HOME/.claude/statusline.log"
echo "=== $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$LOG_FILE"
echo "INPUT JSON: $input" >> "$LOG_FILE"
fi
# Exit early if no input
if [ -z "$input" ]; then
printf "No input received"
exit 0
fi
# === EXTRACT DATA FROM JSON INPUT ===
session_id=$(echo "$input" | jq -r '.session_id // ""')
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""')
last_prompt=""
# Log extracted variables if debugging
if [ "$DEBUG_LOG" = true ]; then
echo "SESSION_ID: $session_id" >> "$LOG_FILE"
echo "PROJECT_DIR: $project_dir" >> "$LOG_FILE"
fi
# === GET TRANSCRIPT FILE PATH ===
# Use transcript_path from input if available, otherwise compute it
transcript_file=$(echo "$input" | jq -r '.transcript_path // ""')
if [ -z "$transcript_file" ] && [ -n "$session_id" ] && [ -n "$project_dir" ]; then
# Fallback: Convert project path to encoded format used by Claude (/ becomes -)
encoded_project=$(echo "$project_dir" | sed 's|/|-|g')
transcript_file="$HOME/.claude/projects/$encoded_project/$session_id.jsonl"
fi
# Log transcript file path if debugging
if [ "$DEBUG_LOG" = true ]; then
echo "TRANSCRIPT_FILE: $transcript_file" >> "$LOG_FILE"
echo "FILE_EXISTS: $([ -f "$transcript_file" ] && echo 'yes' || echo 'no')" >> "$LOG_FILE"
fi
# === CALCULATE TERMINAL WIDTH ===
# Try tput first, fall back to COLUMNS env var, then default to 80
term_width=$(tput cols 2>/dev/null || echo "${COLUMNS:-80}")
[[ ! "$term_width" =~ ^[0-9]+$ ]] && term_width=80
max_prompt_len=$((term_width - 10))
# === EXTRACT LAST USER PROMPT ===
if [ -n "$transcript_file" ] && [ -f "$transcript_file" ]; then
# Find the last user message from the JSONL file
# Handle both string and array content formats, only show first line
last_prompt=$(grep '"type":"user"' "$transcript_file" | grep -v 'tool_result' | tail -n 1 | jq -r 'if (.message.content | type) == "string" then .message.content else (.message.content // "") | tostring end' 2>/dev/null | head -n 1 | head -c "$max_prompt_len")
# Log extracted prompt if debugging
if [ "$DEBUG_LOG" = true ]; then
echo "LAST_PROMPT: $last_prompt" >> "$LOG_FILE"
fi
fi
# === DISPLAY MAIN STATUSLINE ===
# Format: πŸ• time | πŸ‘€ user@host | πŸ“ path | 🌿 branch | πŸ”– version | πŸ€– model | 🎨 style | 🧠 context | πŸ“‹ todos | βœ‰οΈ messages | ⏱️ duration | πŸ’° cost | πŸ”₯ burn rate | session name
version=$(echo "$input" | jq -r '.version // ""')
output_style=$(echo "$input" | jq -r '.output_style.name // ""')
git_branch=""
# Get git branch if available
current_dir=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""')
if [ -n "$current_dir" ] && [ -d "$current_dir" ]; then
git_branch=$(cd "$current_dir" && git rev-parse --git-dir >/dev/null 2>&1 && git branch --show-current 2>/dev/null)
if [ -n "$git_branch" ]; then
# Get git status for branch indicator
git_status=$(cd "$current_dir" && git status --porcelain 2>/dev/null)
if [ -z "$git_status" ]; then
git_status_icon="βœ“" # clean
else
git_status_icon="●" # dirty/uncommitted changes
fi
# Get ahead/behind counts
git_ahead_behind=$(cd "$current_dir" && git rev-list --left-right --count @{upstream}...HEAD 2>/dev/null)
if [ -n "$git_ahead_behind" ]; then
git_behind=$(echo "$git_ahead_behind" | cut -f1)
git_ahead=$(echo "$git_ahead_behind" | cut -f2)
else
git_behind=0
git_ahead=0
fi
fi
fi
# Track if we need separator
need_sep=""
# Current time
if [ "$SHOW_CURRENT_TIME" = "true" ]; then
printf "${RESET}πŸ• ${COLOR_CONTEXT}%s${RESET}" "$(date '+%H:%M')"
need_sep="true"
fi
# Username and/or Hostname
if [ "$SHOW_USERNAME" = "true" ] && [ "$SHOW_HOSTNAME" = "true" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${RESET}πŸ‘€ ${COLOR_USER}%s${COLOR_AT}@${COLOR_HOST}%s${RESET}" "$(whoami)" "$(get_hostname)"
need_sep="true"
elif [ "$SHOW_USERNAME" = "true" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${RESET}πŸ‘€ ${COLOR_USER}%s${RESET}" "$(whoami)"
need_sep="true"
elif [ "$SHOW_HOSTNAME" = "true" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${COLOR_AT}@${COLOR_HOST}%s${RESET}" "$(get_hostname)"
need_sep="true"
fi
# Path
if [ "$SHOW_PATH" = "true" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${RESET}πŸ“ ${COLOR_PATH}%s${RESET}" "$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "unknown"' | sed "s|^$HOME|~|g")"
need_sep="true"
fi
# Git branch
if [ "$SHOW_GIT_BRANCH" = "true" ] && [ -n "$git_branch" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${COLOR_BRANCH}🌿 %s %s${RESET}" "$git_branch" "$git_status_icon"
# Ahead/behind indicators
if [ "$SHOW_GIT_AHEAD_BEHIND" = "true" ]; then
if [ "$git_ahead" -gt 0 ]; then
printf " ${COLOR_ADDED}↑%s${RESET}" "$git_ahead"
fi
if [ "$git_behind" -gt 0 ]; then
printf " ${COLOR_REMOVED}↓%s${RESET}" "$git_behind"
fi
fi
need_sep="true"
fi
# Claude Code version
if [ "$SHOW_VERSION" = "true" ] && [ -n "$version" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${COLOR_VERSION}πŸ”– %s${RESET}" "$version"
need_sep="true"
fi
# Model name
if [ "$SHOW_MODEL" = "true" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${RESET}πŸ€– ${COLOR_MODEL}%s${RESET}" "$(echo "$input" | jq -r '.model.display_name // "Claude"')"
need_sep="true"
fi
# Output style
if [ "$SHOW_OUTPUT_STYLE" = "true" ] && [ -n "$output_style" ]; then
[ -n "$need_sep" ] && printf " | "
printf "${COLOR_STYLE}🎨 %s${RESET}" "$output_style"
need_sep="true"
fi
# Context remaining (percentage based on context_window data from input)
if [ "$SHOW_CONTEXT_REMAINING" = "true" ]; then
# Try to get context window info from input JSON (new API in latest Claude Code)
# Use current_usage for actual context window usage (not session totals)
current_input=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
cache_creation=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
context_limit=$(echo "$input" | jq -r '.context_window.context_window_size // 0')
# Log context window extraction if debugging
if [ "$DEBUG_LOG" = true ]; then
echo "CONTEXT_WINDOW_RAW: $(echo "$input" | jq -c '.context_window // "null"')" >> "$LOG_FILE"
echo "CURRENT_INPUT: $current_input" >> "$LOG_FILE"
echo "CACHE_CREATION: $cache_creation" >> "$LOG_FILE"
echo "CACHE_READ: $cache_read" >> "$LOG_FILE"
echo "CONTEXT_LIMIT: $context_limit" >> "$LOG_FILE"
fi
# Ensure numeric values (strip any whitespace and validate)
current_input=$(echo "$current_input" | tr -d '[:space:]')
cache_creation=$(echo "$cache_creation" | tr -d '[:space:]')
cache_read=$(echo "$cache_read" | tr -d '[:space:]')
context_limit=$(echo "$context_limit" | tr -d '[:space:]')
[[ ! "$current_input" =~ ^[0-9]+$ ]] && current_input=0
[[ ! "$cache_creation" =~ ^[0-9]+$ ]] && cache_creation=0
[[ ! "$cache_read" =~ ^[0-9]+$ ]] && cache_read=0
[[ ! "$context_limit" =~ ^[0-9]+$ ]] && context_limit=0
# Context usage = current input + cache tokens (not output tokens)
total_context=$((current_input + cache_creation + cache_read))
# Fallback to transcript file if context_window data not available (older Claude Code versions)
if [ "$total_context" -eq 0 ] && [ -n "$transcript_file" ] && [ -f "$transcript_file" ]; then
last_usage=$(grep '"type":"assistant"' "$transcript_file" | tail -1 | jq -r '.message.usage // empty')
if [ -n "$last_usage" ]; then
input_tokens=$(echo "$last_usage" | jq -r '.input_tokens // 0')
cache_creation=$(echo "$last_usage" | jq -r '.cache_creation_input_tokens // 0')
cache_read=$(echo "$last_usage" | jq -r '.cache_read_input_tokens // 0')
# Ensure numeric values
input_tokens=$(echo "$input_tokens" | tr -d '[:space:]')
cache_creation=$(echo "$cache_creation" | tr -d '[:space:]')
cache_read=$(echo "$cache_read" | tr -d '[:space:]')
[[ ! "$input_tokens" =~ ^[0-9]+$ ]] && input_tokens=0
[[ ! "$cache_creation" =~ ^[0-9]+$ ]] && cache_creation=0
[[ ! "$cache_read" =~ ^[0-9]+$ ]] && cache_read=0
total_context=$((input_tokens + cache_creation + cache_read))
fi
fi
# Fallback to 200k if context_window_size not available
[ "$context_limit" -eq 0 ] && context_limit=200000
# Add auto-compact buffer to consumed tokens if enabled
if [ "$USE_AUTO_COMPACT" = "true" ]; then
total_context=$((total_context + AUTO_COMPACT_BUFFER))
fi
# Log final calculation if debugging
if [ "$DEBUG_LOG" = true ]; then
echo "TOTAL_CONTEXT: $total_context" >> "$LOG_FILE"
echo "FINAL_CONTEXT_LIMIT: $context_limit" >> "$LOG_FILE"
fi
# Always display context remaining (show --% when no data available)
if [ "$total_context" -gt 0 ]; then
# Calculate remaining as percentage
context_remaining_pct=$((100 - (total_context * 100 / context_limit)))
# Clamp to valid range
[ "$context_remaining_pct" -lt 0 ] && context_remaining_pct=0
[ "$context_remaining_pct" -gt 100 ] && context_remaining_pct=100
# Color based on remaining percentage
if [ "$context_remaining_pct" -lt 10 ]; then
context_color="${COLOR_REMOVED}" # red
elif [ "$context_remaining_pct" -lt 25 ]; then
context_color="${COLOR_STYLE}" # yellow
else
context_color="${COLOR_CONTEXT}" # white
fi
printf " | ${RESET}🧠 ${context_color}CR: %s%%${RESET}" "$context_remaining_pct"
else
# No context data yet - show placeholder
printf " | ${RESET}🧠 ${COLOR_CONTEXT}CR: --%%${RESET}"
fi
need_sep="true"
fi
# Todo count (pending/in_progress from todos folder)
if [ "$SHOW_TODO_COUNT" = "true" ] && [ -n "$session_id" ]; then
todos_dir="$HOME/.claude/todos"
if [ -d "$todos_dir" ]; then
# Find most recent todo file for this session (includes agent todos)
todo_file=$(ls -t "$todos_dir/${session_id}"-*.json 2>/dev/null | head -1)
if [ -n "$todo_file" ] && [ -f "$todo_file" ]; then
todo_pending=$(jq '[.[] | select(.status == "pending")] | length' "$todo_file" 2>/dev/null || echo 0)
todo_in_progress=$(jq '[.[] | select(.status == "in_progress")] | length' "$todo_file" 2>/dev/null || echo 0)
[[ ! "$todo_pending" =~ ^[0-9]+$ ]] && todo_pending=0
[[ ! "$todo_in_progress" =~ ^[0-9]+$ ]] && todo_in_progress=0
todo_total=$((todo_pending + todo_in_progress))
if [ "$todo_total" -gt 0 ]; then
printf " | ${RESET}πŸ“‹ ${COLOR_STYLE}%s${RESET}" "$todo_total"
fi
fi
fi
fi
# Session ID (first 8 characters)
if [ "$SHOW_SESSION_ID" = "true" ] && [ -n "$session_id" ]; then
session_id_short=$(echo "$session_id" | cut -c1-8)
printf " | ${RESET}πŸ†” ${COLOR_SESSION}%s${RESET}" "$session_id_short"
fi
# Message count
if [ "$SHOW_MESSAGE_COUNT" = "true" ] && [ -n "$transcript_file" ] && [ -f "$transcript_file" ]; then
msg_count=$(grep -c '"type":"user"' "$transcript_file" 2>/dev/null || echo 0)
printf " | ${RESET}βœ‰οΈ ${COLOR_SESSION}%s${RESET}" "$msg_count"
fi
# Session duration
if [ "$SHOW_SESSION_DURATION" = "true" ]; then
duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
# Ensure numeric value
[[ ! "$duration_ms" =~ ^[0-9]+$ ]] && duration_ms=0
if [ "$duration_ms" -gt 0 ]; then
# Convert to minutes:seconds
duration_sec=$((duration_ms / 1000))
duration_min=$((duration_sec / 60))
duration_sec_rem=$((duration_sec % 60))
printf " | ${RESET}⏱️ ${COLOR_SESSION}%d:%02d${RESET}" "$duration_min" "$duration_sec_rem"
fi
fi
# Session cost
if [ "$SHOW_SESSION_COST" = "true" ]; then
session_cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
# Validate it's a number (integer or float) and greater than 0
if [[ "$session_cost" =~ ^[0-9]+\.?[0-9]*$ ]] && [ "$(awk "BEGIN {print ($session_cost > 0) ? 1 : 0}")" = "1" ]; then
printf " | ${RESET}πŸ’° ${COLOR_SESSION}\$%.2f${RESET}" "$session_cost"
fi
fi
# Burn rate (dollars per hour)
if [ "$SHOW_BURN_RATE" = "true" ]; then
session_cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
# Validate both are positive numbers
if [[ "$session_cost" =~ ^[0-9]+\.?[0-9]*$ ]] && [[ "$duration_ms" =~ ^[0-9]+$ ]] && [ "$duration_ms" -gt 0 ]; then
# Calculate dollars per hour: (cost / duration_ms) * 3600000
burn_rate=$(awk "BEGIN {printf \"%.2f\", ($session_cost / $duration_ms) * 3600000}" 2>/dev/null)
if [ -n "$burn_rate" ]; then
printf " | ${RESET}πŸ”₯ ${COLOR_REMOVED}\$%s/hr${RESET}" "$burn_rate"
fi
fi
fi
# Session name (slug)
if [ "$SHOW_SESSION_NAME" = "true" ] && [ -n "$transcript_file" ] && [ -f "$transcript_file" ]; then
session_slug=$(grep -m1 '"slug"' "$transcript_file" | jq -r '.slug // empty')
if [ -n "$session_slug" ]; then
printf " | ${COLOR_SESSION}%s${RESET}" "$session_slug"
fi
fi
# === DISPLAY LAST USER PROMPT ===
if [ "$SHOW_LAST_PROMPT" = "true" ] && [ -n "$last_prompt" ]; then
printf "\n${COLOR_ARROW}β†’${RESET} %s" "$last_prompt"
if [ ${#last_prompt} -ge "$max_prompt_len" ]; then
printf '...'
fi
fi
# === GENERATE AI-POWERED CONVERSATION SUMMARY ===
if [ "$SHOW_AI_SUMMARY" = "true" ] && [ -n "$session_id" ] && [ -n "$transcript_file" ] && [ -f "$transcript_file" ] && command -v claude >/dev/null 2>&1; then
# Get last few messages, filtering out tool results and extracting clean text
# Only get actual user prompts (strings, not tool_result arrays) and assistant text responses
context=$(tail -n 30 "$transcript_file" | jq -r '
select(.type == "user" or .type == "assistant") |
if .type == "user" then
# Only extract if content is a plain string (not tool_result array)
if (.message.content | type) == "string" then
"User: " + (.message.content | split("\n")[0] | .[0:200])
else
empty
end
else
# For assistant, get first text block, skip if it starts with tool use indicators
if (.message.content | type) == "array" then
(.message.content[] | select(.type == "text") | .text | split("\n")[0] | .[0:200]) as $text |
if ($text | test("^(Let me|I.ll|I will|```|<)")) then
empty
else
"Assistant: " + $text
end
else
empty
end
end
' 2>/dev/null | grep -v '^$' | grep -v '^Assistant: $' | tail -n 4 | tr '\n' ' ')
# Strip whitespace and check if context has meaningful content (at least 20 chars)
context_trimmed=$(echo "$context" | tr -d '[:space:]')
if [ -n "$context" ] && [ ${#context_trimmed} -gt 20 ]; then
# Use Claude Haiku for fast, cost-effective summarization
# Pass context via stdin to avoid shell escaping issues
# Limit summary to terminal width minus space for icon (use global max_prompt_len)
max_summary_len=$max_prompt_len
summary=$(printf '%s' "$context" | timeout 10s claude --model haiku -p "Output ONLY a 3-10 word summary in parentheses. No preamble, no explanation, just the summary. Example: (Fixing statusline color variables)" 2>/dev/null | head -c "$max_summary_len")
# Log summary processing if debugging
if [ "$DEBUG_LOG" = true ]; then
echo "CONTEXT: $context" >> "$LOG_FILE"
echo "SUMMARY: $summary" >> "$LOG_FILE"
echo "---" >> "$LOG_FILE"
fi
if [ -n "$summary" ]; then
printf "\n${RESET}πŸ“„ ${COLOR_SUMMARY}%s${RESET}" "$summary"
else
printf "\n${RESET}πŸ“„ ${COLOR_SUMMARY}...${RESET}"
fi
else
printf "\n${RESET}πŸ“„ ${COLOR_SUMMARY}...${RESET}"
fi
fi
# === DISPLAY LINE CHANGES ===
# Add GitHub-style line changes (independent of transcript file)
if [ "$SHOW_LINE_CHANGES" = "true" ]; then
lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
if [ "$lines_added" != "0" ] || [ "$lines_removed" != "0" ]; then
printf '\n'
if [ "$lines_added" != "0" ]; then
printf "${COLOR_ADDED}+%s${RESET}" "$lines_added"
fi
if [ "$lines_removed" != "0" ]; then
if [ "$lines_added" != "0" ]; then
printf ' '
fi
printf "${COLOR_REMOVED}-%s${RESET}" "$lines_removed"
fi
fi
fi
# Ensure output ends with newline
printf '\n'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment