Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save colegatron/b76e32edf9d294e698a7c12e8d1b3fa6 to your computer and use it in GitHub Desktop.

Select an option

Save colegatron/b76e32edf9d294e698a7c12e8d1b3fa6 to your computer and use it in GitHub Desktop.
Nice prompt for shell. Kubernetes, git, python env and nice colors
#!/bin/bash
# ============================================================================
# Check Bash Version - This script requires Bash 4.0+
# ============================================================================
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
echo "ERROR: This prompt requires Bash 4.0 or newer"
echo "Current version: ${BASH_VERSION}"
echo ""
echo "Solution: Install bash with Homebrew:"
echo " brew install bash"
echo " chsh -s /opt/homebrew/bin/bash"
echo " (Then close and reopen Terminal)"
return 1
fi
# ============================================================================
# Enhanced Bash Prompt (PS1) with Git, Kubernetes, Docker, and more
# ============================================================================
# Features:
# • Git branch & status (dirty/clean)
# • Kubernetes context (with color coding per context)
# • Docker context
# • Smart directory truncation (~/previous/current when long)
# • Exit status of last command (color-coded)
# • Python virtualenv indicator
# • Background job count
# • User/hostname (useful for SSH sessions)
# • Time (optional, default off)
# ============================================================================
# Guard: skip re-declaration if already loaded (avoids readonly errors on re-source)
[[ -n "${_BASH_PROMPT_LOADED:-}" ]] && { PROMPT_COMMAND=__build_prompt; return 0; }
readonly _BASH_PROMPT_LOADED=1
# Color definitions
readonly COLOR_RESET='\[\033[0m\]'
readonly COLOR_BOLD='\[\033[1m\]'
readonly COLOR_DIM='\[\033[2m\]'
readonly COLOR_BLINK='\[\033[5m\]'
readonly COLOR_BLINK_OFF='\[\033[25m\]'
# Powerline separator character
readonly SEPARATOR=$''
# Light grey gradient backgrounds (slightly darker left → lighter right)
readonly BG_GREY_1='\[\033[48;5;246m\]' # Light grey (darkest in scheme)
readonly BG_GREY_2='\[\033[48;5;248m\]' # Slightly lighter
readonly BG_GREY_3='\[\033[48;5;250m\]' # Lighter
readonly BG_GREY_4='\[\033[48;5;252m\]' # Very light
readonly BG_GREY_5='\[\033[48;5;253m\]' # Near white
readonly BG_GREY_6='\[\033[48;5;255m\]' # White-ish
# Foreground colors matching backgrounds (for separator transitions)
readonly FG_GREY_1='\[\033[38;5;246m\]'
readonly FG_GREY_2='\[\033[38;5;248m\]'
readonly FG_GREY_3='\[\033[38;5;250m\]'
readonly FG_GREY_4='\[\033[38;5;252m\]'
readonly FG_GREY_5='\[\033[38;5;253m\]'
readonly FG_GREY_6='\[\033[38;5;255m\]'
# Dark text colors (visible on light grey backgrounds)
readonly COLOR_DARK_RED='\[\033[38;5;88m\]'
readonly COLOR_DARK_GREEN='\[\033[38;5;28m\]'
readonly COLOR_DARK_NAVY='\[\033[38;5;17m\]'
readonly COLOR_DARK_TEAL='\[\033[38;5;23m\]'
readonly COLOR_DARK_BLUE='\[\033[38;5;18m\]'
readonly COLOR_DARK_ORANGE='\[\033[38;5;130m\]'
readonly COLOR_DARK_PURPLE='\[\033[38;5;55m\]'
readonly COLOR_DARK_GREY='\[\033[38;5;238m\]'
# Kept for compatibility (prompt_set_k8s_color etc.)
readonly COLOR_BLACK='\[\033[38;5;0m\]'
readonly COLOR_RED="${COLOR_DARK_RED}"
readonly COLOR_GREEN="${COLOR_DARK_GREEN}"
readonly COLOR_BRIGHT_GREEN="${COLOR_DARK_GREEN}"
readonly COLOR_BRIGHT_RED="${COLOR_DARK_RED}"
readonly COLOR_BRIGHT_CYAN="${COLOR_DARK_TEAL}"
readonly COLOR_BRIGHT_YELLOW="${COLOR_DARK_ORANGE}"
readonly COLOR_BRIGHT_BLUE="${COLOR_DARK_BLUE}"
readonly COLOR_BRIGHT_MAGENTA="${COLOR_DARK_PURPLE}"
readonly COLOR_BRIGHT_BLACK="${COLOR_DARK_GREY}"
# ============================================================================
# CONFIGURATION
# ============================================================================
# Set to true to show time in prompt
PROMPT_SHOW_TIME=false
# Set to true to show full paths instead of smart truncation
PROMPT_FULL_PATH=false
# K8s context color mapping (context_name=color_code)
declare -A K8S_COLORS=(
[local]="${COLOR_DARK_GREEN}"
[docker-desktop]="${COLOR_DARK_GREEN}"
[minikube]="${COLOR_DARK_TEAL}"
[staging]="${COLOR_DARK_BLUE}"
[production]="${COLOR_DARK_RED}"
[dev]="${COLOR_DARK_PURPLE}"
)
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
# Get the exit status of the last command with color
__prompt_exit_status() {
local status=$?
if [[ $status -eq 0 ]]; then
printf "%s" "${COLOR_DARK_GREEN}✓${COLOR_RESET}"
else
printf "%s" "${COLOR_DARK_RED}✗ ${status}${COLOR_RESET}"
fi
return $status
}
# Get git repo name and branch with status (raw, for conditional display)
__prompt_git_info_raw() {
local repo_name
local branch
local status
local branch_color
local display_branch
local is_dirty
# Check if we're in a git repo
if ! git rev-parse --git-dir > /dev/null 2>&1; then
return
fi
# Get repository name
repo_name=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null)
[[ -z "$repo_name" ]] && return
# Get branch name
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
[[ -z "$branch" ]] && return
# Default branch color
branch_color="${COLOR_DARK_TEAL}"
# Check if working directory is dirty
if git diff --quiet --ignore-submodules HEAD 2>/dev/null; then
status="${COLOR_DARK_GREEN}✓${COLOR_RESET}"
is_dirty=false
else
status="${COLOR_DARK_RED}*${COLOR_RESET}"
is_dirty=true
fi
# Special styling for master branch - add blink effect only if dirty
if [[ "$branch" == "master" && "$is_dirty" == "true" ]]; then
display_branch="${COLOR_BLINK}$(echo $branch | tr '[:lower:]' '[:upper:]')${COLOR_BLINK_OFF}"
else
display_branch="$branch"
fi
printf "%s" "${BG_GREY_4}${branch_color} 🔀 (${repo_name}) ${display_branch} ${COLOR_RESET}${BG_GREY_4}${status}${COLOR_RESET}"
}
# Get git repo name and branch with status
__prompt_git_info() {
__prompt_git_info_raw
}
# Get Kubernetes context (raw, for conditional display)
__prompt_k8s_context_raw() {
local context
local color
if ! command -v kubectl &> /dev/null; then
return
fi
context=$(kubectl config current-context 2>/dev/null)
[[ -z "$context" ]] && return
# Look up color for this context
color="${K8S_COLORS[$context]:-${COLOR_DARK_PURPLE}}"
# Special styling for production context - add blink effect
if [[ "$context" == "production" ]]; then
context="${COLOR_BLINK}$(echo $context | tr '[:lower:]' '[:upper:]')${COLOR_BLINK_OFF}"
fi
printf "%s" "${BG_GREY_1}${color} 🔯 ${context} ${COLOR_RESET}"
}
# Get Kubernetes context with color coding
__prompt_k8s_context() {
__prompt_k8s_context_raw
}
# Get Docker context (raw, for conditional display)
__prompt_docker_context_raw() {
local context
if ! command -v docker &> /dev/null; then
return
fi
context=$(docker context show 2>/dev/null)
[[ -z "$context" ]] && return
printf "%s" "${BG_GREY_2}${COLOR_DARK_TEAL} 🐳 ${context} ${COLOR_RESET}"
}
# Get Docker context
__prompt_docker_context() {
__prompt_docker_context_raw
}
# Get Python virtualenv info (raw, for conditional display)
__prompt_virtualenv_raw() {
if [[ -z "$VIRTUAL_ENV" ]]; then
return
fi
local venv_name
venv_name=$(basename "$VIRTUAL_ENV")
printf "%s" "${BG_GREY_5}${COLOR_DARK_GREEN} 🐍 ${venv_name} ${COLOR_RESET}"
}
# Get Python virtualenv info
__prompt_virtualenv() {
__prompt_virtualenv_raw
}
# Get background job count (raw, for conditional display)
__prompt_jobs_raw() {
local count
count=$(jobs -p | wc -l)
if [[ $count -gt 0 ]]; then
printf "%s" "${BG_GREY_5}${COLOR_DARK_PURPLE} ⚙ ${count} ${COLOR_RESET}"
fi
}
# Get background job count
__prompt_jobs() {
__prompt_jobs_raw
}
# Get time (optional)
__prompt_time() {
if [[ "$PROMPT_SHOW_TIME" == "true" ]]; then
printf "%s" " ${COLOR_DIM}[ $(date '+%H:%M:%S') ]${COLOR_RESET}"
fi
}
# Smart directory truncation
__prompt_directory() {
local pwd_path="$PWD"
local max_length=40
if [[ "$PROMPT_FULL_PATH" == "true" ]]; then
printf "%s" "${COLOR_DIM}[ ${COLOR_RESET}${COLOR_DARK_NAVY}${pwd_path}${COLOR_RESET}${COLOR_DIM} ]${COLOR_RESET}"
return
fi
# Replace home with ~
pwd_path="${pwd_path/#$HOME/~}"
# If too long, show ~/...previous/current
if [[ ${#pwd_path} -gt $max_length ]]; then
local current_dir
local parent_dir
current_dir="${pwd_path##*/}"
parent_dir="${pwd_path%/*}"
parent_dir="${parent_dir##*/}"
pwd_path="~/${COLOR_DIM}...${COLOR_RESET}${parent_dir}/${current_dir}"
fi
printf "%s" "${COLOR_DIM}[ ${COLOR_RESET}${COLOR_DARK_NAVY}${pwd_path}${COLOR_RESET}${COLOR_DIM} ]${COLOR_RESET}"
}
# Create a clickable "../" link in iTerm2 when available
__prompt_parent_link() {
if [[ "${TERM_PROGRAM-}" == "iTerm.app" ]]; then
printf "%s" $'\[\033]8;;iterm2://run?command=cd%20..\a\]../\[\033]8;;\a\]'
else
printf "%s" "../"
fi
}
# ============================================================================
# MAIN PROMPT BUILDING
# ============================================================================
__build_prompt() {
local exit_status
local k8s_info docker_info git_info venv_info jobs_info display_path
# Save exit status before anything else
exit_status=$?
# Capture optional sections
k8s_info="$(__prompt_k8s_context_raw)"
docker_info="$(__prompt_docker_context_raw)"
git_info="$(__prompt_git_info_raw)"
venv_info="$(__prompt_virtualenv_raw)"
jobs_info="$(__prompt_jobs_raw)"
# Show only ../current-folder; use clickable ../ in iTerm2 if available
local _full_path="${PWD/#$HOME/~}"
if [[ "$_full_path" == "~" || "$_full_path" == "/" ]]; then
display_path="$_full_path"
else
local _current="${_full_path##*/}"
display_path="$(__prompt_parent_link)${_current}"
fi
# Line 1: [space] → K8s → Docker → Directory → Git → Venv → Jobs → [arrow]
PS1="${BG_GREY_1} ${COLOR_RESET}${k8s_info}"
# K8s → Docker or Directory
if [[ -n "$docker_info" ]]; then
PS1+="${FG_GREY_1}${BG_GREY_2}${SEPARATOR}${COLOR_RESET}${docker_info}"
PS1+="${FG_GREY_2}${BG_GREY_3}${SEPARATOR}${COLOR_RESET}"
else
PS1+="${FG_GREY_1}${BG_GREY_3}${SEPARATOR}${COLOR_RESET}"
fi
# Directory (always shown)
PS1+="${BG_GREY_3}${COLOR_DARK_NAVY} 📁 ${display_path} ${COLOR_RESET}"
# Directory → Git → Venv → Jobs → [ending arrow]
if [[ -n "$git_info" ]]; then
PS1+="${FG_GREY_3}${BG_GREY_4}${SEPARATOR}${COLOR_RESET}${git_info}"
if [[ -n "$venv_info" ]]; then
PS1+="${FG_GREY_4}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}${venv_info}"
if [[ -n "$jobs_info" ]]; then
PS1+="${FG_GREY_5}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}${jobs_info}"
fi
PS1+="${FG_GREY_5}${SEPARATOR}${COLOR_RESET}"
elif [[ -n "$jobs_info" ]]; then
PS1+="${FG_GREY_4}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}${jobs_info}"
PS1+="${FG_GREY_5}${SEPARATOR}${COLOR_RESET}"
else
PS1+="${FG_GREY_4}${SEPARATOR}${COLOR_RESET}"
fi
elif [[ -n "$venv_info" ]]; then
PS1+="${FG_GREY_3}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}${venv_info}"
if [[ -n "$jobs_info" ]]; then
PS1+="${FG_GREY_5}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}${jobs_info}"
fi
PS1+="${FG_GREY_5}${SEPARATOR}${COLOR_RESET}"
elif [[ -n "$jobs_info" ]]; then
PS1+="${FG_GREY_3}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}${jobs_info}"
PS1+="${FG_GREY_5}${SEPARATOR}${COLOR_RESET}"
else
PS1+="${FG_GREY_3}${SEPARATOR}${COLOR_RESET}"
fi
PS1+="\n"
# Line 2: user@host → exit status → prompt symbol
PS1+="${BG_GREY_4} ${COLOR_RESET}${BG_GREY_4}${COLOR_DARK_GREEN}\u${COLOR_RESET}${BG_GREY_4}@${COLOR_RESET}${BG_GREY_4}${COLOR_DARK_ORANGE}\h${COLOR_RESET}${BG_GREY_4} ${COLOR_RESET}"
PS1+="${FG_GREY_4}${BG_GREY_5}${SEPARATOR}${COLOR_RESET}"
PS1+="${BG_GREY_5} ${COLOR_RESET}${BG_GREY_5}"
PS1+="$(__prompt_exit_status)"
PS1+="${COLOR_RESET}${BG_GREY_5} ${COLOR_RESET}"
PS1+="${FG_GREY_5}${BG_GREY_6}${SEPARATOR}${COLOR_RESET}"
if [[ $EUID -eq 0 ]]; then
PS1+="${BG_GREY_6} ${COLOR_RESET}${BG_GREY_6}${COLOR_DARK_RED}#${COLOR_RESET}${BG_GREY_6} ${COLOR_RESET}${FG_GREY_6}${SEPARATOR}${COLOR_RESET}"
else
PS1+="${BG_GREY_6} ${COLOR_RESET}${BG_GREY_6}${COLOR_DARK_GREEN}\$${COLOR_RESET}${BG_GREY_6} ${COLOR_RESET}${FG_GREY_6}${SEPARATOR}${COLOR_RESET}"
fi
PS1+=" "
return $exit_status
}
# Set the prompt command
PROMPT_COMMAND=__build_prompt
# Optionally set secondary prompt (for multi-line commands)
PS2="${COLOR_DARK_GREY}→${COLOR_RESET} "
# ============================================================================
# CONFIGURATION FUNCTIONS
# ============================================================================
# Function to toggle time display
prompt_toggle_time() {
if [[ "$PROMPT_SHOW_TIME" == "true" ]]; then
PROMPT_SHOW_TIME=false
echo "Time display: OFF"
else
PROMPT_SHOW_TIME=true
echo "Time display: ON"
fi
}
# Function to toggle full path
prompt_toggle_fullpath() {
if [[ "$PROMPT_FULL_PATH" == "true" ]]; then
PROMPT_FULL_PATH=false
echo "Full path display: OFF"
else
PROMPT_FULL_PATH=true
echo "Full path display: ON"
fi
}
# Function to set K8s context color
prompt_set_k8s_color() {
local context=$1
local color=$2
if [[ -z "$context" ]] || [[ -z "$color" ]]; then
echo "Usage: prompt_set_k8s_color <context> <color_code>"
echo "Example: prompt_set_k8s_color production '\033[91m'"
return 1
fi
K8S_COLORS[$context]=$color
echo "Set ${context} → $color"
}
# Function to show current config
prompt_show_config() {
echo "=== Prompt Configuration ==="
echo "Time display: $PROMPT_SHOW_TIME"
echo "Full path: $PROMPT_FULL_PATH"
echo ""
echo "K8s context colors:"
for context in "${!K8S_COLORS[@]}"; do
echo " $context → ${K8S_COLORS[$context]}"
done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment