diff --git a/bin/dotfiles-compile.sh b/bin/dotfiles-compile.sh index 690444b..d253cfb 100755 --- a/bin/dotfiles-compile.sh +++ b/bin/dotfiles-compile.sh @@ -2,63 +2,38 @@ # ============================================================================ # Dotfiles Compile - Pre-compile zsh files for faster loading # ============================================================================ -# Compiles .zsh and .zshrc files to .zwc bytecode format -# This can speed up shell startup by 20-50ms -# -# Usage: -# dotfiles-compile.sh # Compile all -# dotfiles-compile.sh --clean # Remove compiled files -# -# Aliases: dfc-compile -# ============================================================================ set -e DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" -# Colors -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' +# Source shared colors +source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} # ============================================================================ # MOTD-style header # ============================================================================ -_M_WIDTH=66 - print_header() { - local user="${USER:-root}" - local hostname="${HOST:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-compile" - local datetime=$(date '+%a %b %d %H:%M') - - # Colors - local _M_RESET=$'\033[0m' - local _M_BOLD=$'\033[1m' - local _M_DIM=$'\033[2m' - local _M_BLUE=$'\033[38;5;39m' - local _M_GREY=$'\033[38;5;242m' - - # Build horizontal line - local hline="" - for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done - local inner=$((_M_WIDTH - 2)) - - # Header content - local h_left="✦ ${user}@${hostname}" - local h_center="${script_name}" - local h_right="${datetime}" - local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) - local h_spaces="" - for ((i=0; i/dev/null; then + df_print_header "dotfiles-compile" + else + local user="${USER:-root}" + local hostname="${HOST:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + local hline="" && for ((i=0; i/dev/null && \ - echo -e "${GREEN}✓${NC} Compiled: ${file##*/}" || \ - echo -e "${YELLOW}⚠${NC} Skipped: ${file##*/}" + echo -e "${DF_GREEN}✓${DF_NC} Compiled: ${file##*/}" || \ + echo -e "${DF_YELLOW}⚠${DF_NC} Skipped: ${file##*/}" else - echo -e "${CYAN}○${NC} Current: ${file##*/}" + echo -e "${DF_CYAN}○${DF_NC} Current: ${file##*/}" fi fi } @@ -79,57 +54,46 @@ clean_compiled() { local count=0 - # Dotfiles for zwc in "$DOTFILES_DIR"/**/*.zwc(N); do rm -f "$zwc" ((count++)) done - # Home zsh files rm -f ~/.zshrc.zwc ~/.zshenv.zwc ~/.zprofile.zwc 2>/dev/null - # Oh-my-zsh (optional) - # rm -f ~/.oh-my-zsh/**/*.zwc(N) 2>/dev/null - - echo -e "${GREEN}✓${NC} Removed $count compiled files" + echo -e "${DF_GREEN}✓${DF_NC} Removed $count compiled files" } compile_all() { - echo -e "${CYAN}Compiling zsh files for faster startup...${NC}" + echo -e "${DF_CYAN}Compiling zsh files for faster startup...${DF_NC}" echo - # Core files echo "Core files:" compile_file ~/.zshrc compile_file ~/.zshenv compile_file ~/.zprofile echo - # Dotfiles zsh files echo "Dotfiles:" compile_file "$DOTFILES_DIR/zsh/.zshrc" compile_file "$DOTFILES_DIR/zsh/aliases.zsh" - # Function files for file in "$DOTFILES_DIR/zsh/functions"/*.zsh(N); do compile_file "$file" done - # Theme for file in "$DOTFILES_DIR/zsh/themes"/*.zsh-theme(N); do compile_file "$file" done echo - # Oh-my-zsh core (optional, can save ~10ms) if [[ -d ~/.oh-my-zsh ]]; then echo "Oh-My-Zsh (optional):" compile_file ~/.oh-my-zsh/oh-my-zsh.sh - # compile_file ~/.oh-my-zsh/lib/*.zsh # Uncomment for more speed echo fi - echo -e "${GREEN}✓${NC} Compilation complete" + echo -e "${DF_GREEN}✓${DF_NC} Compilation complete" echo echo "To measure startup time:" echo " time zsh -i -c exit" @@ -145,9 +109,6 @@ show_help() { echo " (none) Compile all zsh files" echo " --clean Remove all compiled (.zwc) files" echo " --help Show this help" - echo - echo "The compiled files (.zwc) are automatically used by zsh" - echo "and can speed up shell startup by 20-50ms." } # ============================================================================ @@ -157,13 +118,7 @@ show_help() { print_header case "${1:-}" in - --clean|-c) - clean_compiled - ;; - --help|-h) - show_help - ;; - *) - compile_all - ;; + --clean|-c) clean_compiled ;; + --help|-h) show_help ;; + *) compile_all ;; esac diff --git a/bin/dotfiles-doctor.sh b/bin/dotfiles-doctor.sh index d3cc728..e3e28be 100755 --- a/bin/dotfiles-doctor.sh +++ b/bin/dotfiles-doctor.sh @@ -5,16 +5,17 @@ set -e -readonly DOTFILES_HOME="${DOTFILES_HOME:-.}" +readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" readonly DOTFILES_VERSION="3.0.0" -# Color codes -readonly RED='\033[0;31m' -readonly GREEN='\033[0;32m' -readonly YELLOW='\033[1;33m' -readonly BLUE='\033[0;34m' -readonly CYAN='\033[0;36m' -readonly NC='\033[0m' +# Source shared colors +source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { + # Fallback if colors.zsh not found + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} # Track results TOTAL_CHECKS=0 @@ -26,39 +27,22 @@ WARNING_CHECKS=0 # MOTD-style header # ============================================================================ -_M_WIDTH=66 - print_header() { - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-doctor" - local datetime=$(date '+%a %b %d %H:%M') - - # Colors - local _M_RESET=$'\033[0m' - local _M_BOLD=$'\033[1m' - local _M_DIM=$'\033[2m' - local _M_BLUE=$'\033[38;5;39m' - local _M_GREY=$'\033[38;5;242m' - - # Build horizontal line - local hline="" - for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done - local inner=$((_M_WIDTH - 2)) - - # Header content - local h_left="✦ ${user}@${hostname}" - local h_center="${script_name}" - local h_right="${datetime}" - local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) - local h_spaces="" - for ((i=0; i/dev/null; then + df_print_header "dotfiles-doctor" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + local hline="" && for ((i=0; i/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_MAGENTA=$'\033[0;35m' + DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} # ============================================================================ # MOTD-style header # ============================================================================ -_M_WIDTH=66 - print_header() { - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-stats" - local datetime=$(date '+%a %b %d %H:%M') - - # Colors - local _M_RESET=$'\033[0m' - local _M_BOLD=$'\033[1m' - local _M_DIM=$'\033[2m' - local _M_BLUE=$'\033[38;5;39m' - local _M_GREY=$'\033[38;5;242m' - - # Build horizontal line - local hline="" - for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done - local inner=$((_M_WIDTH - 2)) - - # Header content - local h_left="✦ ${user}@${hostname}" - local h_center="${script_name}" - local h_right="${datetime}" - local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) - local h_spaces="" - for ((i=0; i/dev/null; then + df_print_header "dotfiles-stats" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + local hline="" && for ((i=0; i/dev/null | sort | uniq -c | sort -k2n | while read count hour; do local bar_length=$((count / 5)) local bar=$(printf '█%.0s' $(seq 1 $bar_length)) - printf " ${CYAN}%02d:00${NC} ${MAGENTA}%5d${NC} ${GREEN}${bar}${NC}\n" "$hour" "$count" + printf " ${DF_CYAN}%02d:00${DF_NC} ${DF_MAGENTA}%5d${DF_NC} ${DF_GREEN}${bar}${DF_NC}\n" "$hour" "$count" done else - echo " ${YELLOW}⚠${NC} Zsh history file required for hourly breakdown" + echo " ${DF_YELLOW}⚠${DF_NC} Zsh history file required for hourly breakdown" fi echo "" @@ -182,10 +165,10 @@ show_dirs() { if [[ -f "$HOME/.zsh_history" ]]; then grep "cd " "$HOME/.zsh_history" | awk '{print $NF}' | sort | uniq -c | \ sort -rn | head -15 | while read count dir; do - printf " ${CYAN}%4d${NC} ${YELLOW}%s${NC}\n" "$count" "$dir" + printf " ${DF_CYAN}%4d${DF_NC} ${DF_YELLOW}%s${DF_NC}\n" "$count" "$dir" done else - echo " ${YELLOW}⚠${NC} Zsh history file required" + echo " ${DF_YELLOW}⚠${DF_NC} Zsh history file required" fi echo "" @@ -198,14 +181,14 @@ show_git_breakdown() { local total=$(get_history | grep "^git" | wc -l) if [[ $total -eq 0 ]]; then - echo " ${YELLOW}No git commands found${NC}" + echo " ${DF_YELLOW}No git commands found${DF_NC}" return fi get_history | grep "^git " | awk '{print $2}' | sort | uniq -c | sort -rn | \ head -10 | while read count subcmd; do local percent=$((count * 100 / total)) - printf " ${YELLOW}git %-15s${NC} ${CYAN}%4d${NC} (${MAGENTA}%3d%%${NC})\n" \ + printf " ${DF_YELLOW}git %-15s${DF_NC} ${DF_CYAN}%4d${DF_NC} (${DF_MAGENTA}%3d%%${DF_NC})\n" \ "$subcmd" "$count" "$percent" done @@ -242,7 +225,6 @@ main() { show_git_breakdown ;; export) - # Export as JSON echo "{" echo " \"total_commands\": $(get_history | wc -l)," echo " \"unique_commands\": $(get_history | sort | uniq | wc -l)," diff --git a/bin/dotfiles-update.sh b/bin/dotfiles-update.sh index 4f8fb21..fee778a 100755 --- a/bin/dotfiles-update.sh +++ b/bin/dotfiles-update.sh @@ -2,57 +2,31 @@ # ============================================================================ # Update Dotfiles Script # ============================================================================ -# Updates dotfiles from the git repository and relinks files -# -# Usage: -# dotfiles-update.sh # Pull and re-run install -# dotfiles-update.sh --skip-deps # Pull and re-run install without deps -# dotfiles-update.sh --pull-only # Only git pull, don't re-run install -# -# Aliases: dfu, dfupdate -# ============================================================================ set -e -# ============================================================================ -# Options -# ============================================================================ - -SKIP_DEPS=true # Default to skipping deps on updates +SKIP_DEPS=true PULL_ONLY=false for arg in "$@"; do case "$arg" in - --skip-deps) - SKIP_DEPS=true - ;; - --with-deps) - SKIP_DEPS=false - ;; - --pull-only) - PULL_ONLY=true - ;; + --skip-deps) SKIP_DEPS=true ;; + --with-deps) SKIP_DEPS=false ;; + --pull-only) PULL_ONLY=true ;; --help|-h) echo "Usage: dotfiles-update.sh [OPTIONS]" - echo + echo "" echo "Options:" echo " --skip-deps Skip dependency check (default for updates)" echo " --with-deps Run full dependency check" echo " --pull-only Only git pull, don't re-run install script" echo " --help Show this help message" - echo - echo "Aliases:" - echo " dfu, dfupdate Update dotfiles" - echo exit 0 ;; esac done -# ============================================================================ # Load Configuration -# ============================================================================ - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf" [[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="$HOME/.dotfiles/dotfiles.conf" @@ -60,78 +34,55 @@ DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf" if [[ -f "$DOTFILES_CONF" ]]; then source "$DOTFILES_CONF" else - # Fallback defaults DOTFILES_DIR="$HOME/.dotfiles" DOTFILES_BRANCH="main" - DOTFILES_GITHUB_USER="adlee-was-taken" - DOTFILES_REPO_NAME="dotfiles" - DOTFILES_RAW_URL="https://raw.githubusercontent.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}/${DOTFILES_BRANCH}" + DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main" fi -# ============================================================================ -# Colors -# ============================================================================ - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' +# Source shared colors +source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} # ============================================================================ # MOTD-style header # ============================================================================ -_M_WIDTH=66 - print_header() { - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-update" - local datetime=$(date '+%a %b %d %H:%M') - - # Colors - local _M_RESET=$'\033[0m' - local _M_BOLD=$'\033[1m' - local _M_DIM=$'\033[2m' - local _M_BLUE=$'\033[38;5;39m' - local _M_GREY=$'\033[38;5;242m' - - # Build horizontal line - local hline="" - for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done - local inner=$((_M_WIDTH - 2)) - - # Header content - local h_left="✦ ${user}@${hostname}" - local h_center="${script_name}" - local h_right="${datetime}" - local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) - local h_spaces="" - for ((i=0; i/dev/null; then + df_print_header "dotfiles-update" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + local hline="" && for ((i=0; i${NC} $1" + echo -e "${DF_GREEN}==>${DF_NC} $1" } # ============================================================================ @@ -178,7 +129,7 @@ if [ $? -eq 0 ]; then echo print_success "Update complete!" - echo -e "Reload your shell: ${CYAN}reload${NC} or ${CYAN}source ~/.zshrc${NC}" + echo -e "Reload your shell: ${DF_CYAN}reload${DF_NC} or ${DF_CYAN}source ~/.zshrc${DF_NC}" else print_error "Failed to update dotfiles" exit 1 diff --git a/bin/dotfiles-vault.sh b/bin/dotfiles-vault.sh index 7808675..3355e01 100755 --- a/bin/dotfiles-vault.sh +++ b/bin/dotfiles-vault.sh @@ -5,54 +5,38 @@ set -e +readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" readonly VAULT_DIR="${HOME}/.dotfiles/vault" readonly VAULT_FILE="${VAULT_DIR}/secrets.enc" -# Color codes -readonly RED='\033[0;31m' -readonly GREEN='\033[0;32m' -readonly YELLOW='\033[1;33m' -readonly BLUE='\033[0;34m' -readonly CYAN='\033[0;36m' -readonly NC='\033[0m' +# Source shared colors +source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} # ============================================================================ # MOTD-style header # ============================================================================ -_M_WIDTH=66 - print_header() { - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-vault" - local datetime=$(date '+%a %b %d %H:%M') - - # Colors - local _M_RESET=$'\033[0m' - local _M_BOLD=$'\033[1m' - local _M_DIM=$'\033[2m' - local _M_BLUE=$'\033[38;5;39m' - local _M_GREY=$'\033[38;5;242m' - - # Build horizontal line - local hline="" - for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done - local inner=$((_M_WIDTH - 2)) - - # Header content - local h_left="✦ ${user}@${hostname}" - local h_center="${script_name}" - local h_right="${datetime}" - local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) - local h_spaces="" - for ((i=0; i/dev/null; then + df_print_header "dotfiles-vault" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + local hline="" && for ((i=0; i&2 + echo -e "${DF_RED}✗${DF_NC} $1" >&2 } print_section() { echo "" - echo -e "${BLUE}▶${NC} $1" + echo -e "${DF_BLUE}▶${DF_NC} $1" } # ============================================================================ @@ -94,7 +78,6 @@ init_vault() { chmod 700 "$VAULT_DIR" if [[ ! -f "$VAULT_FILE" ]]; then - # Create empty encrypted file echo "{}" | $(get_cipher) > "$VAULT_FILE" print_success "Vault initialized" else @@ -147,24 +130,19 @@ vault_set() { exit 1 fi - # Get value from stdin if not provided if [[ -z "$value" ]]; then read -s -p "Enter value for $key: " value echo "" fi - # Decrypt current vault local current=$(decrypt_vault) - # Add new key-value pair (using jq if available, otherwise simple replacement) if command -v jq &> /dev/null; then local updated=$(echo "$current" | jq --arg k "$key" --arg v "$value" '.[$k] = $v') else - # Simple fallback without jq local updated="{\"$key\": \"$value\"}" fi - # Encrypt and save encrypt_vault "$updated" print_success "Secret stored: $key" } @@ -182,7 +160,6 @@ vault_get() { if command -v jq &> /dev/null; then echo "$vault" | jq -r ".\"$key\" // \"\"" | grep -v "^$" else - # Simple grep fallback echo "$vault" | grep "\"$key\"" | cut -d'"' -f4 fi } @@ -194,12 +171,11 @@ vault_list() { if command -v jq &> /dev/null; then echo "$vault" | jq -r 'keys[]' | while read key; do - echo -e " ${CYAN}•${NC} $key" + echo -e " ${DF_CYAN}•${DF_NC} $key" done else - # Simple fallback echo "$vault" | grep -o '"[^"]*":' | sed 's/"//g' | sed 's/:$//' | while read key; do - echo -e " ${CYAN}•${NC} $key" + echo -e " ${DF_CYAN}•${DF_NC} $key" done fi @@ -280,23 +256,23 @@ vault_status() { print_section "Vault Status" if [[ ! -d "$VAULT_DIR" ]]; then - echo -e " ${YELLOW}⚠${NC} Vault not initialized" + echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized" return fi if [[ ! -f "$VAULT_FILE" ]]; then - echo -e " ${YELLOW}⚠${NC} Vault file not found" + echo -e " ${DF_YELLOW}⚠${DF_NC} Vault file not found" return fi local size=$(du -h "$VAULT_FILE" | cut -f1) local modified=$(stat -c %y "$VAULT_FILE" 2>/dev/null | cut -d' ' -f1 || stat -f '%Sm' "$VAULT_FILE" 2>/dev/null) - echo -e " ${CYAN}Location:${NC} $VAULT_FILE" - echo -e " ${CYAN}Size:${NC} $size" - echo -e " ${CYAN}Modified:${NC} $modified" - echo -e " ${CYAN}Encryption:${NC} $(get_cipher)" - echo -e " ${CYAN}Permissions:${NC} $(stat -c '%a' $VAULT_FILE 2>/dev/null || stat -f '%a' "$VAULT_FILE")" + echo -e " ${DF_CYAN}Location:${DF_NC} $VAULT_FILE" + echo -e " ${DF_CYAN}Size:${DF_NC} $size" + echo -e " ${DF_CYAN}Modified:${DF_NC} $modified" + echo -e " ${DF_CYAN}Encryption:${DF_NC} $(get_cipher)" + echo -e " ${DF_CYAN}Permissions:${DF_NC} $(stat -c '%a' $VAULT_FILE 2>/dev/null || stat -f '%a' "$VAULT_FILE")" echo "" } @@ -308,7 +284,6 @@ vault_status() { main() { print_header - # Initialize vault if not exists if [[ ! -d "$VAULT_DIR" ]]; then init_vault fi diff --git a/bin/dotfiles-version.sh b/bin/dotfiles-version.sh index 011d487..8f9631e 100755 --- a/bin/dotfiles-version.sh +++ b/bin/dotfiles-version.sh @@ -2,17 +2,8 @@ # ============================================================================ # Dotfiles Version Checker # ============================================================================ -# Shows current and remote version info -# -# Usage: -# dotfiles-version.sh # Show version info -# dotfiles-version.sh --check # Check for updates (exit 1 if behind) -# ============================================================================ -# ============================================================================ # Load Configuration -# ============================================================================ - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf" [[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="${SCRIPT_DIR}/dotfiles.conf" @@ -27,82 +18,51 @@ else DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main" fi -# ============================================================================ -# Colors -# ============================================================================ - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' - -# ============================================================================ -# MOTD-style header -# ============================================================================ - -_M_WIDTH=66 - -print_header() { - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local script_name="dotfiles-version" - local datetime=$(date '+%a %b %d %H:%M') - - # Colors - local _M_RESET=$'\033[0m' - local _M_BOLD=$'\033[1m' - local _M_DIM=$'\033[2m' - local _M_BLUE=$'\033[38;5;39m' - local _M_GREY=$'\033[38;5;242m' - - # Build horizontal line - local hline="" - for ((i=0; i<_M_WIDTH; i++)); do hline+="═"; done - local inner=$((_M_WIDTH - 2)) - - # Header content - local h_left="✦ ${user}@${hostname}" - local h_center="${script_name}" - local h_right="${datetime}" - local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) - local h_spaces="" - for ((i=0; i/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' } -# ============================================================================ -# Options -# ============================================================================ - CHECK_ONLY=false for arg in "$@"; do case "$arg" in - --check|-c) - CHECK_ONLY=true - ;; + --check|-c) CHECK_ONLY=true ;; --help|-h) echo "Usage: dotfiles-version.sh [OPTIONS]" - echo + echo "" echo "Options:" echo " --check Only check for updates (exit 1 if behind)" echo " --help Show this help message" - echo - echo "Aliases:" - echo " dfv, dfversion Show version info" - echo exit 0 ;; esac done +# ============================================================================ +# MOTD-style header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-version" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + local hline="" && for ((i=0; i/dev/null) if [[ -n "$remote_conf" ]]; then echo "$remote_conf" | grep -oP 'DOTFILES_VERSION="\K[^"]+' || echo "unknown" @@ -176,7 +135,6 @@ compare_versions() { if [[ "$local_v" == "$remote_v" ]]; then echo "current" else - # Simple semver comparison local local_parts=(${local_v//./ }) local remote_parts=(${remote_v//./ }) @@ -220,44 +178,44 @@ main() { print_header - echo -e "${CYAN}Local:${NC}" - echo -e " Version: ${GREEN}${local_version}${NC}" + echo -e "${DF_CYAN}Local:${DF_NC}" + echo -e " Version: ${DF_GREEN}${local_version}${DF_NC}" echo -e " Commit: ${local_commit}" echo -e " Date: ${local_date}" echo -e " Path: ${DOTFILES_DIR}" echo - echo -e "${CYAN}Remote:${NC}" + echo -e "${DF_CYAN}Remote:${DF_NC}" echo -e " Version: ${remote_version}" echo -e " Commit: ${remote_commit}" echo -e " Branch: ${DOTFILES_BRANCH}" echo - echo -e "${CYAN}Status:${NC}" + echo -e "${DF_CYAN}Status:${DF_NC}" case "$version_status" in current) - echo -e " Version: ${GREEN}✓ Up to date${NC}" + echo -e " Version: ${DF_GREEN}✓ Up to date${DF_NC}" ;; behind) - echo -e " Version: ${YELLOW}⚠ New version available: ${remote_version}${NC}" + echo -e " Version: ${DF_YELLOW}⚠ New version available: ${remote_version}${DF_NC}" ;; ahead) - echo -e " Version: ${CYAN}ℹ Local is ahead of remote${NC}" + echo -e " Version: ${DF_CYAN}ℹ Local is ahead of remote${DF_NC}" ;; *) - echo -e " Version: ${YELLOW}? Cannot determine${NC}" + echo -e " Version: ${DF_YELLOW}? Cannot determine${DF_NC}" ;; esac if [[ "$commits_behind" -gt 0 ]]; then - echo -e " Commits: ${YELLOW}⚠ ${commits_behind} commit(s) behind${NC}" + echo -e " Commits: ${DF_YELLOW}⚠ ${commits_behind} commit(s) behind${DF_NC}" echo - echo -e "${YELLOW}To update:${NC}" + echo -e "${DF_YELLOW}To update:${DF_NC}" echo " dfu # Alias" echo " dotfiles-update.sh # Full command" elif [[ "$commits_behind" == "0" ]]; then - echo -e " Commits: ${GREEN}✓ Up to date${NC}" + echo -e " Commits: ${DF_GREEN}✓ Up to date${DF_NC}" fi echo diff --git a/zsh/functions/motd.zsh b/zsh/functions/motd.zsh index 13f347a..58164c3 100644 --- a/zsh/functions/motd.zsh +++ b/zsh/functions/motd.zsh @@ -12,24 +12,20 @@ # Only run in interactive shells [[ -o interactive ]] || return 0 -# ============================================================================ -# MOTD Width, adjust if needed. -# ============================================================================ - -_M_WIDTH=66 +# Source shared colors (with fallback) +source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_RESET=$'\033[0m' DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' + typeset -g DF_BLUE=$'\033[38;5;39m' DF_CYAN=$'\033[38;5;51m' + typeset -g DF_GREEN=$'\033[38;5;82m' DF_YELLOW=$'\033[38;5;220m' + typeset -g DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m' +} # ============================================================================ -# Colors (ANSI escape codes) +# MOTD Width # ============================================================================ -_M_RESET=$'\033[0m' -_M_BOLD=$'\033[1m' -_M_DIM=$'\033[2m' -_M_BLUE=$'\033[38;5;39m' -_M_CYAN=$'\033[38;5;51m' -_M_GREEN=$'\033[38;5;82m' -_M_YELLOW=$'\033[38;5;220m' -_M_GREY=$'\033[38;5;242m' +typeset -g _M_WIDTH=66 # ============================================================================ # Info Gathering @@ -64,7 +60,6 @@ _motd_disk() { # Box Drawing - Fixed Width # ============================================================================ - _motd_line() { local char="$1" local i @@ -76,7 +71,6 @@ _motd_line() { } _motd_pad() { - # Pad a plain string to exact width local str="$1" local width="$2" local len=${#str} @@ -101,14 +95,14 @@ show_motd() { local load=$(_motd_load) local mem=$(_motd_mem) local disk=$(_motd_disk) - local local_ip=$(hostname -i | awk -F" " '{print $1}') + local local_ip=$(hostname -i 2>/dev/null | awk -F" " '{print $1}' || echo "N/A") local hline=$(_motd_line '═') local inner=$((_M_WIDTH - 2)) echo "" # Top border - echo "${_M_GREY}╒${hline}╕${_M_RESET}" + echo "${DF_GREY}╒${hline}╕${DF_NC}" # Header: hostname + datetime local h_left="✦ ${hostname}" @@ -117,21 +111,17 @@ show_motd() { local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2 )) local h_spaces="" for ((i=0; i # Get OTP/TOTP code # pw search # Search items # pw copy # Copy password to clipboard -# -# Supported: LastPass (lpass) -# -# Add to .zshrc: -# source ~/.dotfiles/zsh/functions/password-manager.zsh # ============================================================================ -# ============================================================================ -# Configuration -# ============================================================================ - -# Colors -typeset -g PW_GREEN=$'\033[0;32m' -typeset -g PW_BLUE=$'\033[0;34m' -typeset -g PW_YELLOW=$'\033[1;33m' -typeset -g PW_CYAN=$'\033[0;36m' -typeset -g PW_RED=$'\033[0;31m' -typeset -g PW_NC=$'\033[0m' +# Source shared colors (with fallback) +source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_GREEN=$'\033[0;32m' DF_BLUE=$'\033[0;34m' + typeset -g DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + typeset -g DF_RED=$'\033[0;31m' DF_NC=$'\033[0m' +} # ============================================================================ # LastPass Functions @@ -78,9 +69,8 @@ pw() { local cmd="${1:-help}" shift - # Check if lastpass is installed if ! command -v lpass &>/dev/null; then - echo -e "${PW_RED}✗${PW_NC} LastPass CLI (lpass) not installed" + echo -e "${DF_RED}✗${DF_NC} LastPass CLI (lpass) not installed" echo "Install with: yay -S lastpass-cli" return 1 fi @@ -133,7 +123,7 @@ pw() { ;; help|--help|-h|*) - echo -e "${PW_BLUE}Password Manager CLI (LastPass)${PW_NC}" + echo -e "${DF_BLUE}Password Manager CLI (LastPass)${DF_NC}" echo echo "Usage: pw [args]" echo @@ -174,7 +164,6 @@ alias pws='pw search' # ============================================================================ if command -v fzf &>/dev/null; then - # Interactive password selection and copy pwf() { if ! command -v lpass &>/dev/null; then echo "LastPass CLI not installed" @@ -188,7 +177,6 @@ if command -v fzf &>/dev/null; then fi } - # Interactive OTP selection and copy pwof() { if ! command -v lpass &>/dev/null; then echo "LastPass CLI not installed" diff --git a/zsh/functions/smart-suggest.zsh b/zsh/functions/smart-suggest.zsh index c302b29..70803db 100644 --- a/zsh/functions/smart-suggest.zsh +++ b/zsh/functions/smart-suggest.zsh @@ -8,164 +8,73 @@ # - Suggests existing aliases for frequently typed commands # - "Did you mean?" for unknown commands # - Package installation suggestions for missing commands -# -# Add to .zshrc: -# source ~/.dotfiles/zsh/functions/smart-suggest.zsh # ============================================================================ +# Source shared colors (with fallback) +source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_CYAN=$'\033[0;36m' DF_YELLOW=$'\033[1;33m' + typeset -g DF_GREEN=$'\033[0;32m' DF_RED=$'\033[0;31m' + typeset -g DF_DIM=$'\033[2m' DF_NC=$'\033[0m' +} + # ============================================================================ # Configuration # ============================================================================ -# Enable/disable features typeset -g SMART_SUGGEST_ENABLED=true typeset -g SMART_SUGGEST_TYPOS=true typeset -g SMART_SUGGEST_ALIASES=true typeset -g SMART_SUGGEST_PACKAGES=true typeset -g SMART_SUGGEST_HISTORY=true - -# Tracking file for alias suggestions typeset -g SMART_SUGGEST_TRACK_FILE="$HOME/.cache/smart-suggest-track" -# Colors -typeset -g SS_CYAN=$'\033[0;36m' -typeset -g SS_YELLOW=$'\033[1;33m' -typeset -g SS_GREEN=$'\033[0;32m' -typeset -g SS_RED=$'\033[0;31m' -typeset -g SS_DIM=$'\033[2m' -typeset -g SS_NC=$'\033[0m' - # ============================================================================ # Common Typo Database # ============================================================================ typeset -gA TYPO_CORRECTIONS=( # Git typos - [gti]="git" - [gitt]="git" - [got]="git" - [gut]="git" - [gi]="git" - [giit]="git" - [ggit]="git" - [gitst]="git st" - [gits]="git s" - [gitl]="git l" - [gitd]="git d" - [gitp]="git p" - [psuh]="push" - [psull]="pull" - [pul]="pull" - [puhs]="push" - [stauts]="status" - [statis]="status" - [statuus]="status" - [comit]="commit" - [commti]="commit" - [commt]="commit" - [chekcout]="checkout" - [chekout]="checkout" - [checkou]="checkout" - [branhc]="branch" - [barnch]="branch" - [bracnh]="branch" - [marge]="merge" - [merg]="merge" - [stsh]="stash" - [stahs]="stash" + [gti]="git" [gitt]="git" [got]="git" [gut]="git" [gi]="git" + [giit]="git" [ggit]="git" [gitst]="git st" [gits]="git s" + [gitl]="git l" [gitd]="git d" [gitp]="git p" + [psuh]="push" [psull]="pull" [pul]="pull" [puhs]="push" + [stauts]="status" [statis]="status" [statuus]="status" + [comit]="commit" [commti]="commit" [commt]="commit" + [chekcout]="checkout" [chekout]="checkout" [checkou]="checkout" + [branhc]="branch" [barnch]="branch" [bracnh]="branch" + [marge]="merge" [merg]="merge" [stsh]="stash" [stahs]="stash" # Docker typos - [dokcer]="docker" - [doker]="docker" - [docekr]="docker" - [dcoker]="docker" - [dockr]="docker" - [docke]="docker" - [docker-compoes]="docker-compose" - [docker-compsoe]="docker-compose" + [dokcer]="docker" [doker]="docker" [docekr]="docker" + [dcoker]="docker" [dockr]="docker" [docke]="docker" + [docker-compoes]="docker-compose" [docker-compsoe]="docker-compose" [dokcer-compose]="docker-compose" # Common command typos - [sl]="ls" - [l]="ls" - [sls]="ls" - [lss]="ls" - [cta]="cat" - [catt]="cat" - [caat]="cat" - [grpe]="grep" - [gerp]="grep" - [gre]="grep" - [grepp]="grep" - [mkdri]="mkdir" - [mkdr]="mkdir" - [mdkir]="mkdir" - [mdir]="mkdir" - [rn]="rm" - [rmm]="rm" - [chmdo]="chmod" - [chomd]="chmod" - [chonw]="chown" - [cown]="chown" - [tarr]="tar" - [tart]="tar" - [wegt]="wget" - [wgte]="wget" - [weget]="wget" - [crul]="curl" - [crul]="curl" - [curll]="curl" - [pytohn]="python" - [pyhton]="python" - [pythn]="python" - [pyton]="python" - [pthon]="python" - [pytho]="python" - [ndoe]="node" - [noed]="node" - [noode]="node" - [npn]="npm" - [nmpm]="npm" - [nppm]="npm" - [yran]="yarn" - [yaarn]="yarn" - [yanr]="yarn" - [suod]="sudo" - [sudi]="sudo" - [sduo]="sudo" - [sudoo]="sudo" - [sssh]="ssh" - [shh]="ssh" - [sssh]="ssh" - [scpp]="scp" - [spcp]="scp" - [vmi]="vim" - [imv]="vim" - [viim]="vim" - [cde]="code" - [cdoe]="code" - [cod]="code" - [clera]="clear" - [cler]="clear" - [claer]="clear" - [ecoh]="echo" - [ehco]="echo" - [echoo]="echo" - [exti]="exit" - [ext]="exit" - [exitt]="exit" - [eixt]="exit" - [histroy]="history" - [hisotry]="history" - [hsitory]="history" - [histrory]="history" - [maek]="make" - [mkae]="make" - [amke]="make" - [makee]="make" - [ccd]="cd" - [cdd]="cd" - [ccd]="cd" + [sl]="ls" [l]="ls" [sls]="ls" [lss]="ls" + [cta]="cat" [catt]="cat" [caat]="cat" + [grpe]="grep" [gerp]="grep" [gre]="grep" [grepp]="grep" + [mkdri]="mkdir" [mkdr]="mkdir" [mdkir]="mkdir" [mdir]="mkdir" + [rn]="rm" [rmm]="rm" [chmdo]="chmod" [chomd]="chmod" + [chonw]="chown" [cown]="chown" [tarr]="tar" [tart]="tar" + [wegt]="wget" [wgte]="wget" [weget]="wget" + [crul]="curl" [curll]="curl" + [pytohn]="python" [pyhton]="python" [pythn]="python" + [pyton]="python" [pthon]="python" [pytho]="python" + [ndoe]="node" [noed]="node" [noode]="node" + [npn]="npm" [nmpm]="npm" [nppm]="npm" + [yran]="yarn" [yaarn]="yarn" [yanr]="yarn" + [suod]="sudo" [sudi]="sudo" [sduo]="sudo" [sudoo]="sudo" + [sssh]="ssh" [shh]="ssh" [scpp]="scp" [spcp]="scp" + [vmi]="vim" [imv]="vim" [viim]="vim" + [cde]="code" [cdoe]="code" [cod]="code" + [clera]="clear" [cler]="clear" [claer]="clear" + [ecoh]="echo" [ehco]="echo" [echoo]="echo" + [exti]="exit" [ext]="exit" [exitt]="exit" [eixt]="exit" + [histroy]="history" [hisotry]="history" [hsitory]="history" + [histrory]="history" [maek]="make" [mkae]="make" + [amke]="make" [makee]="make" [ccd]="cd" [cdd]="cd" ) # ============================================================================ @@ -173,46 +82,25 @@ typeset -gA TYPO_CORRECTIONS=( # ============================================================================ _ss_get_package_manager() { - if command -v apt-get &>/dev/null; then - echo "apt" - elif command -v dnf &>/dev/null; then - echo "dnf" - elif command -v pacman &>/dev/null; then - echo "pacman" - elif command -v brew &>/dev/null; then - echo "brew" - else - echo "" + if command -v apt-get &>/dev/null; then echo "apt" + elif command -v dnf &>/dev/null; then echo "dnf" + elif command -v pacman &>/dev/null; then echo "pacman" + elif command -v brew &>/dev/null; then echo "brew" + else echo "" fi } -# Common commands and their packages typeset -gA COMMAND_PACKAGES=( - [htop]="htop" - [tree]="tree" - [jq]="jq" - [fd]="fd-find:apt fd:pacman fd:brew" - [rg]="ripgrep" - [bat]="bat" - [eza]="eza" - [exa]="exa" - [fzf]="fzf" - [tldr]="tldr" - [ncdu]="ncdu" - [duf]="duf" - [dust]="dust" - [procs]="procs" - [bottom]="bottom" - [btm]="bottom" - [lazygit]="lazygit" - [lazydocker]="lazydocker" - [neofetch]="neofetch" - [fastfetch]="fastfetch" - [httpie]="httpie" - [http]="httpie" + [htop]="htop" [tree]="tree" [jq]="jq" + [fd]="fd-find:apt fd:pacman fd:brew" [rg]="ripgrep" + [bat]="bat" [eza]="eza" [exa]="exa" [fzf]="fzf" + [tldr]="tldr" [ncdu]="ncdu" [duf]="duf" [dust]="dust" + [procs]="procs" [bottom]="bottom" [btm]="bottom" + [lazygit]="lazygit" [lazydocker]="lazydocker" + [neofetch]="neofetch" [fastfetch]="fastfetch" + [httpie]="httpie" [http]="httpie" [delta]="git-delta:apt delta:pacman git-delta:brew" - [glow]="glow" - [navi]="navi" + [glow]="glow" [navi]="navi" ) _ss_suggest_package() { @@ -226,9 +114,7 @@ _ss_suggest_package() { local pkg="" - # Check for PM-specific package name if [[ "$pkg_info" == *":"* ]]; then - # Format: "pkg1:pm1 pkg2:pm2" for entry in ${(s: :)pkg_info}; do local p="${entry%%:*}" local m="${entry##*:}" @@ -237,7 +123,6 @@ _ss_suggest_package() { break fi done - # Fallback to first package [[ -z "$pkg" ]] && pkg="${${(s: :)pkg_info}[1]%%:*}" else pkg="$pkg_info" @@ -264,12 +149,11 @@ _ss_track_command() { [[ "$SMART_SUGGEST_ALIASES" != true ]] && return local cmd="$1" - [[ ${#cmd} -lt 8 ]] && return # Skip short commands + [[ ${#cmd} -lt 8 ]] && return mkdir -p "$(dirname "$SMART_SUGGEST_TRACK_FILE")" echo "$cmd" >> "$SMART_SUGGEST_TRACK_FILE" - # Periodically check for alias suggestions local count=$(grep -Fc "$cmd" "$SMART_SUGGEST_TRACK_FILE" 2>/dev/null || echo 0) if [[ $count -ge 10 && $((count % 10)) -eq 0 ]]; then @@ -281,23 +165,21 @@ _ss_suggest_alias_for() { local cmd="$1" local count="$2" - # Check if an alias already exists for this command local existing=$(alias | grep -F "='$cmd'" | head -1 | cut -d= -f1) if [[ -n "$existing" ]]; then echo - echo -e "${SS_CYAN}💡 Tip:${SS_NC} You've typed '${SS_YELLOW}$cmd${SS_NC}' $count times" - echo -e " You already have an alias: ${SS_GREEN}$existing${SS_NC}" + echo -e "${DF_CYAN}💡 Tip:${DF_NC} You've typed '${DF_YELLOW}$cmd${DF_NC}' $count times" + echo -e " You already have an alias: ${DF_GREEN}$existing${DF_NC}" else - # Generate suggested alias name local suggested=$(echo "$cmd" | awk '{ for(i=1; i<=NF && i<=3; i++) printf substr($i,1,1) }') echo - echo -e "${SS_CYAN}💡 Tip:${SS_NC} You've typed '${SS_YELLOW}$cmd${SS_NC}' $count times" - echo -e " Consider adding: ${SS_GREEN}alias $suggested='$cmd'${SS_NC}" + echo -e "${DF_CYAN}💡 Tip:${DF_NC} You've typed '${DF_YELLOW}$cmd${DF_NC}' $count times" + echo -e " Consider adding: ${DF_GREEN}alias $suggested='$cmd'${DF_NC}" fi } @@ -315,34 +197,31 @@ command_not_found_handler() { return 127 } - echo -e "${SS_RED}✗${SS_NC} Command not found: ${SS_YELLOW}$cmd${SS_NC}" + echo -e "${DF_RED}✗${DF_NC} Command not found: ${DF_YELLOW}$cmd${DF_NC}" local suggestion_made=false - # Check for typo if [[ "$SMART_SUGGEST_TYPOS" == true ]]; then local correction="${TYPO_CORRECTIONS[$cmd]}" if [[ -n "$correction" ]]; then - echo -e "${SS_CYAN}→${SS_NC} Did you mean: ${SS_GREEN}$correction${SS_NC}?" - echo -e " ${SS_DIM}Run: $correction $args${SS_NC}" + echo -e "${DF_CYAN}→${DF_NC} Did you mean: ${DF_GREEN}$correction${DF_NC}?" + echo -e " ${DF_DIM}Run: $correction $args${DF_NC}" suggestion_made=true fi fi - # Check for similar commands if [[ "$suggestion_made" != true ]]; then local similar=$(compgen -c 2>/dev/null | grep -i "^${cmd:0:3}" | head -3 | tr '\n' ', ' | sed 's/,$//') if [[ -n "$similar" ]]; then - echo -e "${SS_CYAN}→${SS_NC} Similar commands: ${SS_GREEN}$similar${SS_NC}" + echo -e "${DF_CYAN}→${DF_NC} Similar commands: ${DF_GREEN}$similar${DF_NC}" suggestion_made=true fi fi - # Suggest package installation if [[ "$SMART_SUGGEST_PACKAGES" == true ]]; then local install_cmd=$(_ss_suggest_package "$cmd") if [[ -n "$install_cmd" ]]; then - echo -e "${SS_CYAN}→${SS_NC} To install: ${SS_GREEN}$install_cmd${SS_NC}" + echo -e "${DF_CYAN}→${DF_NC} To install: ${DF_GREEN}$install_cmd${DF_NC}" suggestion_made=true fi fi @@ -351,42 +230,30 @@ command_not_found_handler() { } # ============================================================================ -# Pre-exec Hook for Tracking +# Hooks # ============================================================================ _ss_preexec_hook() { local cmd="$1" - - # Extract just the command (first word) local first_word="${cmd%% *}" - - # Track full commands for alias suggestions _ss_track_command "$cmd" } -# ============================================================================ -# Post-command Hook for Suggestions -# ============================================================================ - _ss_precmd_hook() { local exit_code=$? - - # Only suggest if last command failed [[ $exit_code -eq 0 ]] && return - # Get last command local last_cmd=$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//') [[ -z "$last_cmd" ]] && return local first_word="${last_cmd%% *}" - # Check if it's a git command with typo if [[ "$first_word" == "git" && $exit_code -ne 0 ]]; then local git_subcmd=$(echo "$last_cmd" | awk '{print $2}') local correction="${TYPO_CORRECTIONS[$git_subcmd]}" if [[ -n "$correction" ]]; then - echo -e "${SS_CYAN}→${SS_NC} Did you mean: ${SS_GREEN}git $correction${SS_NC}?" + echo -e "${DF_CYAN}→${DF_NC} Did you mean: ${DF_GREEN}git $correction${DF_NC}?" fi fi } @@ -395,18 +262,15 @@ _ss_precmd_hook() { # Quick Fix Function # ============================================================================ -# Run the suggested correction -# Usage: !! or just press up and edit fuck() { local last_cmd=$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//') local first_word="${last_cmd%% *}" - # Check for typo correction local correction="${TYPO_CORRECTIONS[$first_word]}" if [[ -n "$correction" ]]; then local fixed_cmd="${last_cmd/$first_word/$correction}" - echo -e "${SS_GREEN}Running:${SS_NC} $fixed_cmd" + echo -e "${DF_GREEN}Running:${DF_NC} $fixed_cmd" eval "$fixed_cmd" else echo "No automatic fix available" @@ -419,28 +283,9 @@ fuck() { # ============================================================================ _ss_setup() { - # Add preexec hook autoload -Uz add-zsh-hook add-zsh-hook preexec _ss_preexec_hook add-zsh-hook precmd _ss_precmd_hook } -# Initialize [[ "$SMART_SUGGEST_ENABLED" == true ]] && _ss_setup - -# ============================================================================ -# Usage Examples (commented) -# ============================================================================ - -# $ gti status -# ✗ Command not found: gti -# → Did you mean: git? -# Run: git status - -# $ dokcer ps -# ✗ Command not found: dokcer -# → Did you mean: docker? - -# After typing "docker-compose up -d" 10 times: -# 💡 Tip: You've typed 'docker-compose up -d' 10 times -# Consider adding: alias dcu='docker-compose up -d' diff --git a/zsh/functions/snapper.zsh b/zsh/functions/snapper.zsh index 8a22a9a..4992f6d 100644 --- a/zsh/functions/snapper.zsh +++ b/zsh/functions/snapper.zsh @@ -1,14 +1,13 @@ # ============================================================================ # Snapper Snapshot Functions for CachyOS/Arch with limine-snapper-sync # ============================================================================ -# Add these functions to your ~/.zshrc or ~/.dotfiles/zsh/.zshrc -# Colors for output -SNAP_GREEN='\033[0;32m' -SNAP_YELLOW='\033[1;33m' -SNAP_RED='\033[0;31m' -SNAP_BLUE='\033[0;34m' -SNAP_NC='\033[0m' # No Color +# Source shared colors (with fallback) +source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + typeset -g DF_RED=$'\033[0;31m' DF_BLUE=$'\033[0;34m' DF_NC=$'\033[0m' +} # ============================================================================ # Main Snapshot Function with Limine Validation @@ -19,110 +18,81 @@ snap-create() { local snap_config="root" local limine_conf="/boot/limine.conf" - # Print header - echo -e "\n${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}" - echo -e "${SNAP_BLUE}║${SNAP_NC} Snapper Snapshot Creation & Validation ${SNAP_BLUE}║${SNAP_NC}" - echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n" + echo -e "\n${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} Snapper Snapshot Creation & Validation ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}\n" - # Check if description was provided if [[ -z "$description" ]]; then - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} No description provided" + echo -e "${DF_YELLOW}⚠${DF_NC} No description provided" echo -n "Enter snapshot description: " read description - - if [[ -z "$description" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} Description required. Aborting." - return 1 - fi + [[ -z "$description" ]] && { echo -e "${DF_RED}✗${DF_NC} Description required. Aborting."; return 1; } fi - # Check if limine.conf exists - if [[ ! -f "$limine_conf" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} Limine config not found: $limine_conf" - return 1 - fi + [[ ! -f "$limine_conf" ]] && { echo -e "${DF_RED}✗${DF_NC} Limine config not found: $limine_conf"; return 1; } - # Get limine.conf state before snapshot - echo -e "${SNAP_BLUE}==>${SNAP_NC} Checking limine.conf state before snapshot" + echo -e "${DF_BLUE}==>${DF_NC} Checking limine.conf state before snapshot" local before_checksum=$(sudo md5sum "$limine_conf" | awk '{print $1}') local before_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0") - echo -e "${SNAP_GREEN}✓${SNAP_NC} Before: $before_entries snapshot entries" - echo -e "${SNAP_GREEN}✓${SNAP_NC} Before checksum: $before_checksum" + echo -e "${DF_GREEN}✓${DF_NC} Before: $before_entries snapshot entries" + echo -e "${DF_GREEN}✓${DF_NC} Before checksum: $before_checksum" - # Create the snapshot - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Creating snapshot: \"$description\"" + echo -e "\n${DF_BLUE}==>${DF_NC} Creating snapshot: \"$description\"" - local snapshot_num=$(sudo snapper -c "$snap_config" create \ - --description "$description" \ - --print-number) + local snapshot_num=$(sudo snapper -c "$snap_config" create --description "$description" --print-number) - if [[ -z "$snapshot_num" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} Failed to create snapshot" - return 1 - fi + [[ -z "$snapshot_num" ]] && { echo -e "${DF_RED}✗${DF_NC} Failed to create snapshot"; return 1; } - echo -e "${SNAP_GREEN}✓${SNAP_NC} Snapshot created: #$snapshot_num" + echo -e "${DF_GREEN}✓${DF_NC} Snapshot created: #$snapshot_num" - # Trigger limine-snapper-sync service - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Triggering limine-snapper-sync service..." + echo -e "\n${DF_BLUE}==>${DF_NC} Triggering limine-snapper-sync service..." if sudo systemctl start limine-snapper-sync.service; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Service triggered successfully" + echo -e "${DF_GREEN}✓${DF_NC} Service triggered successfully" else - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} Failed to trigger service (may run automatically)" + echo -e "${DF_YELLOW}⚠${DF_NC} Failed to trigger service (may run automatically)" fi - # Wait a moment for the service to complete - echo -e "${SNAP_BLUE}==>${SNAP_NC} Waiting for limine-snapper-sync to update limine.conf..." + echo -e "${DF_BLUE}==>${DF_NC} Waiting for limine-snapper-sync to update limine.conf..." sleep 2 - # Get limine.conf state after snapshot - echo -e "${SNAP_BLUE}==>${SNAP_NC} Validating limine.conf update" + echo -e "${DF_BLUE}==>${DF_NC} Validating limine.conf update" local after_checksum=$(sudo md5sum "$limine_conf" | awk '{print $1}') local after_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0") - # Validate the update local validation_passed=true if [[ "$before_checksum" == "$after_checksum" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} limine.conf was NOT updated (checksum unchanged)" + echo -e "${DF_RED}✗${DF_NC} limine.conf was NOT updated (checksum unchanged)" validation_passed=false else - echo -e "${SNAP_GREEN}✓${SNAP_NC} limine.conf was updated" + echo -e "${DF_GREEN}✓${DF_NC} limine.conf was updated" fi if [[ "$after_entries" -le "$before_entries" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} No new snapshot entry added to limine.conf" + echo -e "${DF_RED}✗${DF_NC} No new snapshot entry added to limine.conf" validation_passed=false else local new_entries=$((after_entries - before_entries)) - echo -e "${SNAP_GREEN}✓${SNAP_NC} Added $new_entries new snapshot entry/entries" + echo -e "${DF_GREEN}✓${DF_NC} Added $new_entries new snapshot entry/entries" fi - # Check for the specific snapshot in limine.conf - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Searching for snapshot #$snapshot_num in limine.conf" + echo -e "\n${DF_BLUE}==>${DF_NC} Searching for snapshot #$snapshot_num in limine.conf" - # Format in limine.conf is: ///[number] │ [date] if sudo grep -qP "^\\s*///$snapshot_num\\s*│" "$limine_conf"; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Found snapshot #$snapshot_num in limine.conf" - - # Show the entry with description - echo -e "\n${SNAP_BLUE}Snapshot entry:${SNAP_NC}" + echo -e "${DF_GREEN}✓${DF_NC} Found snapshot #$snapshot_num in limine.conf" + echo -e "\n${DF_BLUE}Snapshot entry:${DF_NC}" local entry_line=$(sudo grep -nP "^\\s*///$snapshot_num\\s*│" "$limine_conf" | head -n 1 | cut -d: -f1) - if [[ -n "$entry_line" ]]; then - # Show the snapshot header and description - sudo sed -n "${entry_line}p; $((entry_line+1))p" "$limine_conf" | sed 's/^/ /' - fi + [[ -n "$entry_line" ]] && sudo sed -n "${entry_line}p; $((entry_line+1))p" "$limine_conf" | sed 's/^/ /' else - echo -e "${SNAP_RED}✗${SNAP_NC} Snapshot #$snapshot_num NOT found in limine.conf" + echo -e "${DF_RED}✗${DF_NC} Snapshot #$snapshot_num NOT found in limine.conf" validation_passed=false fi - # Print summary - echo -e "\n${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}" - echo -e "${SNAP_BLUE}║${SNAP_NC} Summary ${SNAP_BLUE}║${SNAP_NC}" - echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}" + echo -e "\n${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} Summary ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}" echo -e "Snapshot Number: #$snapshot_num" echo -e "Description: \"$description\"" echo -e "Config: $snap_config" @@ -130,14 +100,14 @@ snap-create() { echo -e "After entries: $after_entries" if [[ "$validation_passed" == true ]]; then - echo -e "Status: ${SNAP_GREEN}✓ VALIDATED${SNAP_NC}" - echo -e "\n${SNAP_GREEN}✓${SNAP_NC} Snapshot created and limine.conf successfully updated!" + echo -e "Status: ${DF_GREEN}✓ VALIDATED${DF_NC}" + echo -e "\n${DF_GREEN}✓${DF_NC} Snapshot created and limine.conf successfully updated!" return 0 else - echo -e "Status: ${SNAP_RED}✗ VALIDATION FAILED${SNAP_NC}" - echo -e "\n${SNAP_RED}✗${SNAP_NC} Snapshot created but limine.conf validation failed!" - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} Check if limine-snapper-sync service is running properly" - echo -e "${SNAP_YELLOW}Run:${SNAP_NC} sudo systemctl status limine-snapper-sync.service" + echo -e "Status: ${DF_RED}✗ VALIDATION FAILED${DF_NC}" + echo -e "\n${DF_RED}✗${DF_NC} Snapshot created but limine.conf validation failed!" + echo -e "${DF_YELLOW}⚠${DF_NC} Check if limine-snapper-sync service is running properly" + echo -e "${DF_YELLOW}Run:${DF_NC} sudo systemctl status limine-snapper-sync.service" return 1 fi } @@ -146,343 +116,135 @@ snap-create() { # Helper Functions # ============================================================================ -# List recent snapshots snap-list() { local count="${1:-10}" - echo -e "${SNAP_BLUE}Recent $count snapshots:${SNAP_NC}\n" + echo -e "${DF_BLUE}Recent $count snapshots:${DF_NC}\n" sudo snapper -c root list | tail -n "$((count + 1))" } -# Show snapshot details snap-show() { - if [[ -z "$1" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} Usage: snap-show " - return 1 - fi + [[ -z "$1" ]] && { echo -e "${DF_RED}✗${DF_NC} Usage: snap-show "; return 1; } - echo -e "${SNAP_BLUE}Snapshot #$1 details:${SNAP_NC}\n" + echo -e "${DF_BLUE}Snapshot #$1 details:${DF_NC}\n" sudo snapper -c root list | grep "^\s*$1\s" - echo -e "\n${SNAP_BLUE}In limine.conf:${SNAP_NC}" + echo -e "\n${DF_BLUE}In limine.conf:${DF_NC}" if sudo grep -qP "^\\s*///$1\\s*│" /boot/limine.conf; then local entry_line=$(sudo grep -nP "^\\s*///$1\\s*│" /boot/limine.conf | head -n 1 | cut -d: -f1) - if [[ -n "$entry_line" ]]; then - sudo sed -n "${entry_line}p; $((entry_line+1))p" /boot/limine.conf - fi + [[ -n "$entry_line" ]] && sudo sed -n "${entry_line}p; $((entry_line+1))p" /boot/limine.conf else - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} Not found in limine.conf" + echo -e "${DF_YELLOW}⚠${DF_NC} Not found in limine.conf" fi } -# Delete snapshot with limine validation snap-delete() { - if [[ -z "$1" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} Usage: snap-delete " - return 1 - fi + [[ -z "$1" ]] && { echo -e "${DF_RED}✗${DF_NC} Usage: snap-delete "; return 1; } local snapshot_num="$1" local limine_conf="/boot/limine.conf" - echo -e "${SNAP_BLUE}==>${SNAP_NC} Deleting snapshot #$snapshot_num" + echo -e "${DF_BLUE}==>${DF_NC} Deleting snapshot #$snapshot_num" - # Check before deletion (count snapshot entries using correct format) local before_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0") - # Delete the snapshot sudo snapper -c root delete "$snapshot_num" if [[ $? -eq 0 ]]; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Snapshot #$snapshot_num deleted" + echo -e "${DF_GREEN}✓${DF_NC} Snapshot #$snapshot_num deleted" - # Trigger sync service - echo -e "${SNAP_BLUE}==>${SNAP_NC} Triggering limine-snapper-sync..." + echo -e "${DF_BLUE}==>${DF_NC} Triggering limine-snapper-sync..." sudo systemctl start limine-snapper-sync.service - - # Wait for service to complete sleep 2 - # Check after deletion (use correct format) local after_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0") if [[ "$after_entries" -lt "$before_entries" ]]; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} limine.conf updated (removed entry)" + echo -e "${DF_GREEN}✓${DF_NC} limine.conf updated (removed entry)" else - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} limine.conf may not have been updated" + echo -e "${DF_YELLOW}⚠${DF_NC} limine.conf may not have been updated" fi - # Verify snapshot is gone from limine.conf (use correct format) if ! sudo grep -qP "^\\s*///$snapshot_num\\s*│" "$limine_conf"; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Snapshot #$snapshot_num removed from limine.conf" + echo -e "${DF_GREEN}✓${DF_NC} Snapshot #$snapshot_num removed from limine.conf" else - echo -e "${SNAP_RED}✗${SNAP_NC} Snapshot #$snapshot_num still in limine.conf!" + echo -e "${DF_RED}✗${DF_NC} Snapshot #$snapshot_num still in limine.conf!" fi else - echo -e "${SNAP_RED}✗${SNAP_NC} Failed to delete snapshot #$snapshot_num" + echo -e "${DF_RED}✗${DF_NC} Failed to delete snapshot #$snapshot_num" return 1 fi } -# Check limine.conf for all snapshots snap-check-limine() { local limine_conf="/boot/limine.conf" - echo -e "${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}" - echo -e "${SNAP_BLUE}║${SNAP_NC} Limine Snapshot Entries ${SNAP_BLUE}║${SNAP_NC}" - echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n" + echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} Limine Snapshot Entries ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}\n" - if [[ ! -f "$limine_conf" ]]; then - echo -e "${SNAP_RED}✗${SNAP_NC} Limine config not found: $limine_conf" - return 1 - fi + [[ ! -f "$limine_conf" ]] && { echo -e "${DF_RED}✗${DF_NC} Limine config not found: $limine_conf"; return 1; } - # Get latest snapshot number (excluding snapshot 0) local latest_snapshot=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | tail -n 1 | awk '{print $1}') + [[ -z "$latest_snapshot" ]] && { echo -e "${DF_YELLOW}⚠${DF_NC} No snapshots found in snapper"; return 1; } - if [[ -z "$latest_snapshot" ]]; then - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} No snapshots found in snapper" - return 1 - fi + echo -e "${DF_BLUE}Latest snapshot:${DF_NC} #$latest_snapshot" - echo -e "${SNAP_BLUE}Latest snapshot:${SNAP_NC} #$latest_snapshot" - - # Check if latest snapshot is in limine.conf - # Format in limine.conf is: ///49 │ 2025-12-14 01:15:33 - echo -e "${SNAP_BLUE}==>${SNAP_NC} Checking if latest snapshot is in limine.conf" + echo -e "${DF_BLUE}==>${DF_NC} Checking if latest snapshot is in limine.conf" if sudo grep -qP "^\\s*///$latest_snapshot\s*│" "$limine_conf"; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Latest snapshot #$latest_snapshot is present in limine.conf" - - # Show the entry with description - echo -e "\n${SNAP_BLUE}Latest snapshot entry:${SNAP_NC}" - local entry_line=$(sudo grep -nP "^\\s*///$latest_snapshot\s*│" "$limine_conf" | head -n 1 | cut -d: -f1) - if [[ -n "$entry_line" ]]; then - # Show the snapshot header and description (next line) - sudo sed -n "${entry_line}p; $((entry_line+1))p" "$limine_conf" | sed 's/^/ /' - fi + echo -e "${DF_GREEN}✓${DF_NC} Latest snapshot #$latest_snapshot is present in limine.conf" else - echo -e "${SNAP_RED}✗${SNAP_NC} Latest snapshot #$latest_snapshot is NOT in limine.conf" - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} This may indicate the sync service hasn't run yet" + echo -e "${DF_RED}✗${DF_NC} Latest snapshot #$latest_snapshot is NOT in limine.conf" fi - # Count snapshot entries (lines matching ///[number] │) - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Counting snapshot entries" + echo -e "\n${DF_BLUE}==>${DF_NC} Counting snapshot entries" local entry_count=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine_conf" || echo "0") - echo -e "${SNAP_BLUE}Total snapshot entries:${SNAP_NC} $entry_count\n" - - # Show all snapshot entries - if [[ "$entry_count" -gt 0 ]]; then - echo -e "${SNAP_BLUE}Snapshot boot entries:${SNAP_NC}\n" - - # Extract snapshot entries with their descriptions - local snap_nums=($(sudo grep -oP "^\\s*///\\K\\d+(?=\\s*│)" "$limine_conf" | sort -n)) - - local i=1 - for snap_num in "${snap_nums[@]}"; do - local snap_line=$(sudo grep -nP "^\\s*///$snap_num\s*│" "$limine_conf" | head -n 1) - local line_num=$(echo "$snap_line" | cut -d: -f1) - local date_time=$(echo "$snap_line" | cut -d: -f2- | grep -oP "│\\s*\\K.*" || echo "") - - # Get description from next line (starts with "comment:") - local desc="" - if [[ -n "$line_num" ]]; then - desc=$(sudo sed -n "$((line_num+1))p" "$limine_conf" | grep -oP "comment:\\s*\\K.*" || echo "") - fi - - printf "%2d. Snapshot #%-3s %s %s\n" "$i" "$snap_num" "$date_time" "${desc:-(no description)}" - ((i++)) - done - - # Show snapshot range - if [[ ${#snap_nums[@]} -gt 0 ]]; then - echo -e "\n${SNAP_BLUE}Snapshot range in boot menu:${SNAP_NC}" - echo -e " Oldest: #${snap_nums[1]}" - echo -e " Newest: #${snap_nums[-1]}" - fi - else - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} No snapshot entries found in limine.conf" - fi - - # Compare with actual snapshots - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Comparing with snapper list" - echo "" - - # Count all snapshots except snapshot 0 (current system) - local snapper_count=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | wc -l) - - # Get oldest and newest snapshots - local oldest_snapshot=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | head -n 1 | awk '{print $1}') - - echo -e "Snapshots in snapper: $snapper_count (range: #$oldest_snapshot to #$latest_snapshot)" - echo -e "Entries in limine.conf: $entry_count" - - if [[ "$snapper_count" -eq "$entry_count" ]]; then - echo -e "Status: ${SNAP_GREEN}✓ FULLY SYNCED${SNAP_NC}" - else - local diff=$((snapper_count - entry_count)) - echo -e "Status: ${SNAP_YELLOW}⚠ PARTIALLY SYNCED${SNAP_NC}" - echo -e "Missing from boot menu: $diff snapshot(s)" - - # Check if latest is present (most important) - if sudo grep -qP "^\\s*///$latest_snapshot\s*│" "$limine_conf"; then - echo -e "\n${SNAP_GREEN}✓${SNAP_NC} Latest snapshot IS available for boot (this is what matters most)" - else - echo -e "\n${SNAP_RED}✗${SNAP_NC} Latest snapshot NOT available for boot (run: snap-sync)" - fi - - echo -e "\n${SNAP_BLUE}Note:${SNAP_NC} limine-snapper-sync typically limits boot entries to recent snapshots" - echo -e " This is normal and prevents boot menu clutter" - - # Check the limit from comment line - local limit_comment=$(sudo grep -oP "comment:\\s*\\K\\d+\\s*/\\s*\\d+" "$limine_conf" | head -n 1) - if [[ -n "$limit_comment" ]]; then - echo -e " Current limit: $limit_comment snapshots" - fi - - # Show which snapshots are missing if there aren't too many - if [[ $diff -le 20 ]]; then - echo -e "\n${SNAP_BLUE}Snapshots NOT in boot menu:${SNAP_NC}" - - local all_snapshots=($(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | awk '{print $1}')) - local limine_snapshots=($(sudo grep -oP "^\\s*///\\K\\d+(?=\\s*│)" "$limine_conf")) - local missing_count=0 - - for snap_num in "${all_snapshots[@]}"; do - # Check if this snapshot is in limine.conf - local found=false - for limine_snap in "${limine_snapshots[@]}"; do - if [[ "$snap_num" == "$limine_snap" ]]; then - found=true - break - fi - done - - if [[ "$found" == false ]]; then - if [[ $missing_count -lt 10 ]]; then - local snap_info=$(sudo snapper -c root list | grep "^\s*$snap_num\s") - local snap_type=$(echo "$snap_info" | awk '{print $2}') - local snap_desc=$(echo "$snap_info" | awk -F'|' '{print $5}' | xargs) - echo -e " #$snap_num ($snap_type) - $snap_desc" - fi - ((missing_count++)) - fi - done - - if [[ $missing_count -gt 10 ]]; then - echo -e " ... and $((missing_count - 10)) more" - fi - fi - fi + echo -e "${DF_BLUE}Total snapshot entries:${DF_NC} $entry_count\n" } -# Show detailed snapshot information with types -snap-info() { - echo -e "${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}" - echo -e "${SNAP_BLUE}║${SNAP_NC} Detailed Snapshot Information ${SNAP_BLUE}║${SNAP_NC}" - echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n" - - echo -e "${SNAP_BLUE}All snapshots with types:${SNAP_NC}\n" - sudo snapper -c root list - - echo -e "\n${SNAP_BLUE}Breakdown by type:${SNAP_NC}\n" - - local single_count=$(sudo snapper -c root list | grep -c "single" || echo "0") - local pre_count=$(sudo snapper -c root list | grep -c "pre" || echo "0") - local post_count=$(sudo snapper -c root list | grep -c "post" || echo "0") - local total=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | wc -l) - - echo -e "Single snapshots: $single_count" - echo -e "Pre snapshots: $pre_count" - echo -e "Post snapshots: $post_count" - echo -e "Total snapshots: $total" - - echo -e "\n${SNAP_BLUE}Snapshot types explained:${SNAP_NC}" - echo -e " ${SNAP_GREEN}single${SNAP_NC} - Manual snapshots (created by you)" - echo -e " ${SNAP_GREEN}pre${SNAP_NC} - Before system changes (e.g., package updates)" - echo -e " ${SNAP_GREEN}post${SNAP_NC} - After system changes" -} - -# Manually trigger sync service snap-sync() { - echo -e "${SNAP_BLUE}==>${SNAP_NC} Manually triggering limine-snapper-sync..." + echo -e "${DF_BLUE}==>${DF_NC} Manually triggering limine-snapper-sync..." if sudo systemctl start limine-snapper-sync.service; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Service triggered successfully" - - # Wait for completion + echo -e "${DF_GREEN}✓${DF_NC} Service triggered successfully" sleep 2 - - # Show status - echo -e "\n${SNAP_BLUE}Service status:${SNAP_NC}" + echo -e "\n${DF_BLUE}Service status:${DF_NC}" sudo systemctl status limine-snapper-sync.service --no-pager -l | tail -n 10 else - echo -e "${SNAP_RED}✗${SNAP_NC} Failed to trigger service" + echo -e "${DF_RED}✗${DF_NC} Failed to trigger service" return 1 fi } -# Validate limine-snapper-sync service is working snap-validate-service() { - echo -e "${SNAP_BLUE}╔════════════════════════════════════════════════════════════╗${SNAP_NC}" - echo -e "${SNAP_BLUE}║${SNAP_NC} Limine-Snapper-Sync Service Validation ${SNAP_BLUE}║${SNAP_NC}" - echo -e "${SNAP_BLUE}╚════════════════════════════════════════════════════════════╝${SNAP_NC}\n" + echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} Limine-Snapper-Sync Service Validation ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}\n" - # Check if service unit exists - echo -e "${SNAP_BLUE}==>${SNAP_NC} Checking service unit" + echo -e "${DF_BLUE}==>${DF_NC} Checking service unit" if systemctl list-unit-files | grep -q "limine-snapper-sync.service"; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} limine-snapper-sync.service unit exists" + echo -e "${DF_GREEN}✓${DF_NC} limine-snapper-sync.service unit exists" else - echo -e "${SNAP_RED}✗${SNAP_NC} limine-snapper-sync.service unit NOT found" - echo -e "\n${SNAP_YELLOW}Install with:${SNAP_NC} paru -S limine-snapper-sync" + echo -e "${DF_RED}✗${DF_NC} limine-snapper-sync.service unit NOT found" + echo -e "\n${DF_YELLOW}Install with:${DF_NC} paru -S limine-snapper-sync" return 1 fi - # Check if service is enabled - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking if service is enabled" + echo -e "\n${DF_BLUE}==>${DF_NC} Checking if service is enabled" if systemctl is-enabled limine-snapper-sync.service &>/dev/null; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Service is enabled" + echo -e "${DF_GREEN}✓${DF_NC} Service is enabled" else - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} Service is NOT enabled" - echo -e "${SNAP_YELLOW}Enable with:${SNAP_NC} sudo systemctl enable limine-snapper-sync.service" + echo -e "${DF_YELLOW}⚠${DF_NC} Service is NOT enabled" + echo -e "${DF_YELLOW}Enable with:${DF_NC} sudo systemctl enable limine-snapper-sync.service" fi - # Check service status - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking service status" - - if systemctl is-active limine-snapper-sync.service &>/dev/null; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Service is active" - else - echo -e "${SNAP_YELLOW}⚠${SNAP_NC} Service is inactive (this is normal for oneshot services)" - fi - - # Show recent service logs - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Recent service logs (last 10 lines)" + echo -e "\n${DF_BLUE}==>${DF_NC} Recent service logs (last 10 lines)" echo "" sudo journalctl -u limine-snapper-sync.service -n 10 --no-pager | sed 's/^/ /' - # Check snapper config - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking snapper configuration" - - if [[ -f "/etc/snapper/configs/root" ]]; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} Snapper root config exists" - else - echo -e "${SNAP_RED}✗${SNAP_NC} Snapper root config not found" - fi - - # Check limine.conf - echo -e "\n${SNAP_BLUE}==>${SNAP_NC} Checking limine.conf" - - if [[ -f "/boot/limine.conf" ]]; then - echo -e "${SNAP_GREEN}✓${SNAP_NC} limine.conf exists" - local snap_entries=$(sudo grep -cP "^\\s*///\\d+\\s*│" /boot/limine.conf || echo "0") - echo -e " Snapshot entries: $snap_entries" - else - echo -e "${SNAP_RED}✗${SNAP_NC} limine.conf not found" - fi - - echo -e "\n${SNAP_GREEN}✓${SNAP_NC} Validation complete" + echo -e "\n${DF_GREEN}✓${DF_NC} Validation complete" } # Quick snapshot aliases @@ -492,16 +254,3 @@ alias snaprm='snap-delete' alias snapshow='snap-show' alias snapcheck='snap-check-limine' alias snapsync='snap-sync' -alias snapinfo='snap-info' - -# ============================================================================ -# Usage Examples (commented out - uncomment to see examples) -# ============================================================================ - -# snap-create "Before system update" -# snap-list 20 -# snap-show 42 -# snap-delete 42 -# snap-check-limine -# snap-sync -# snap-validate-service diff --git a/zsh/functions/ssh-manager.zsh b/zsh/functions/ssh-manager.zsh index 4c3f536..cb6e7c1 100644 --- a/zsh/functions/ssh-manager.zsh +++ b/zsh/functions/ssh-manager.zsh @@ -2,26 +2,16 @@ # SSH Session Manager with Tmux Integration # ============================================================================ # Manage SSH connections with automatic tmux session handling -# -# Usage: -# ssh-save # Save SSH connection -# ssh-connect # Connect and attach/create tmux session -# ssh-list # List all saved connections -# ssh-delete # Delete saved connection -# ssh-edit # Edit connection details -# sshf # Fuzzy search and connect -# -# Features: -# - Automatic tmux session attach/create on remote host -# - Named sessions per connection -# - Connection profiles with SSH options -# - Auto-reconnect support -# - Dotfiles sync to remote (optional) -# -# Add to .zshrc: -# source ~/.dotfiles/zsh/functions/ssh-manager.zsh # ============================================================================ +# Source shared colors (with fallback) +source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_GREEN=$'\033[0;32m' DF_BLUE=$'\033[0;34m' + typeset -g DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + typeset -g DF_RED=$'\033[0;31m' DF_NC=$'\033[0m' +} + # ============================================================================ # Configuration # ============================================================================ @@ -31,33 +21,14 @@ typeset -g SSH_AUTO_TMUX="${SSH_AUTO_TMUX:-true}" typeset -g SSH_TMUX_SESSION_PREFIX="${SSH_TMUX_SESSION_PREFIX:-ssh}" typeset -g SSH_SYNC_DOTFILES="${SSH_SYNC_DOTFILES:-ask}" -# Colors -typeset -g SSH_GREEN=$'\033[0;32m' -typeset -g SSH_BLUE=$'\033[0;34m' -typeset -g SSH_YELLOW=$'\033[1;33m' -typeset -g SSH_CYAN=$'\033[0;36m' -typeset -g SSH_RED=$'\033[0;31m' -typeset -g SSH_NC=$'\033[0m' - # ============================================================================ # Helper Functions # ============================================================================ -_ssh_print_step() { - echo -e "${SSH_BLUE}==>${SSH_NC} $1" -} - -_ssh_print_success() { - echo -e "${SSH_GREEN}✓${SSH_NC} $1" -} - -_ssh_print_error() { - echo -e "${SSH_RED}✗${SSH_NC} $1" -} - -_ssh_print_info() { - echo -e "${SSH_CYAN}ℹ${SSH_NC} $1" -} +_ssh_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; } +_ssh_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +_ssh_print_error() { echo -e "${DF_RED}✗${DF_NC} $1"; } +_ssh_print_info() { echo -e "${DF_CYAN}ℹ${DF_NC} $1"; } _ssh_init_profiles() { if [[ ! -f "$SSH_PROFILES_FILE" ]]; then @@ -65,10 +36,6 @@ _ssh_init_profiles() { cat > "$SSH_PROFILES_FILE" << 'EOF' # SSH Connection Profiles # Format: name|user@host|port|key_file|options|description -# -# Example: -# prod|user@prod.example.com|22|~/.ssh/prod_key|-L 8080:localhost:80|Production server -# dev|user@dev.example.com|2222||ForwardAgent=yes|Development server EOF _ssh_print_success "Created SSH profiles file: $SSH_PROFILES_FILE" fi @@ -77,14 +44,8 @@ EOF _ssh_parse_profile() { local name="$1" local line=$(grep "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null | head -1) - - if [[ -z "$line" ]]; then - return 1 - fi - - # Parse: name|connection|port|key|options|description + [[ -z "$line" ]] && return 1 IFS='|' read -r profile_name connection port key_file ssh_opts description <<< "$line" - echo "$connection|$port|$key_file|$ssh_opts|$description" } @@ -93,88 +54,62 @@ _ssh_parse_profile() { # ============================================================================ ssh-save() { - local name="$1" - local connection="$2" - local port="${3:-22}" - local key_file="${4:-}" - local options="${5:-}" - local description="${6:-}" + local name="$1" connection="$2" port="${3:-22}" key_file="${4:-}" options="${5:-}" description="${6:-}" _ssh_init_profiles - if [[ -z "$name" || -z "$connection" ]]; then + [[ -z "$name" || -z "$connection" ]] && { echo "Usage: ssh-save [port] [key_file] [options] [description]" - echo - echo "Examples:" - echo " ssh-save prod user@prod.com" - echo " ssh-save dev user@dev.com 2222 ~/.ssh/dev_key" - echo " ssh-save vpn user@vpn.com 22 '' '-D 9090' 'VPN server'" return 1 - fi + } - # Check if profile exists if grep -q "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null; then - echo -e "${SSH_YELLOW}⚠${SSH_NC} Profile '$name' already exists" - read -q "REPLY?Overwrite? [y/N]: " - echo + echo -e "${DF_YELLOW}⚠${DF_NC} Profile '$name' already exists" + read -q "REPLY?Overwrite? [y/N]: "; echo [[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1 - - # Remove old entry grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" fi - # Save new profile echo "${name}|${connection}|${port}|${key_file}|${options}|${description}" >> "$SSH_PROFILES_FILE" _ssh_print_success "Saved SSH profile: $name" echo " Connection: $connection" [[ "$port" != "22" ]] && echo " Port: $port" [[ -n "$key_file" ]] && echo " Key: $key_file" - [[ -n "$options" ]] && echo " Options: $options" - [[ -n "$description" ]] && echo " Description: $description" } ssh-list() { _ssh_init_profiles - echo -e "${SSH_BLUE}╔════════════════════════════════════════════════════════════╗${SSH_NC}" - echo -e "${SSH_BLUE}║${SSH_NC} SSH Connection Profiles ${SSH_BLUE}║${SSH_NC}" - echo -e "${SSH_BLUE}╚════════════════════════════════════════════════════════════╝${SSH_NC}" + echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} SSH Connection Profiles ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}" echo local has_profiles=false while IFS='|' read -r name connection port key options description; do - # Skip comments and empty lines [[ "$name" =~ ^# ]] && continue [[ -z "$name" ]] && continue - has_profiles=true - echo -e "${SSH_GREEN}●${SSH_NC} ${SSH_CYAN}$name${SSH_NC}" + echo -e "${DF_GREEN}●${DF_NC} ${DF_CYAN}$name${DF_NC}" echo " Connection: $connection" [[ "$port" != "22" && -n "$port" ]] && echo " Port: $port" [[ -n "$key" ]] && echo " Key: $key" - [[ -n "$options" ]] && echo " Options: $options" [[ -n "$description" ]] && echo " Description: $description" echo done < "$SSH_PROFILES_FILE" - if [[ "$has_profiles" != true ]]; then + [[ "$has_profiles" != true ]] && { _ssh_print_info "No profiles saved yet" - echo - echo "Create a profile with:" - echo " ssh-save myserver user@example.com" - fi + echo "Create a profile with: ssh-save myserver user@example.com" + } } ssh-delete() { local name="$1" - - if [[ -z "$name" ]]; then - echo "Usage: ssh-delete " - return 1 - fi + [[ -z "$name" ]] && { echo "Usage: ssh-delete "; return 1; } _ssh_init_profiles @@ -183,182 +118,74 @@ ssh-delete() { return 1 fi - # Remove profile grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" - _ssh_print_success "Deleted profile: $name" } -ssh-edit() { - local name="$1" - - if [[ -z "$name" ]]; then - # Edit entire file - ${EDITOR:-vim} "$SSH_PROFILES_FILE" - return - fi - - _ssh_init_profiles - - local profile_data=$(_ssh_parse_profile "$name") - if [[ -z "$profile_data" ]]; then - _ssh_print_error "Profile '$name' not found" - return 1 - fi - - IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data" - - echo -e "${SSH_CYAN}Editing profile: $name${SSH_NC}" - echo - - read "new_connection?Connection [$connection]: " - new_connection="${new_connection:-$connection}" - - read "new_port?Port [$port]: " - new_port="${new_port:-$port}" - - read "new_key?Key file [$key_file]: " - new_key="${new_key:-$key_file}" - - read "new_opts?SSH options [$ssh_opts]: " - new_opts="${new_opts:-$ssh_opts}" - - read "new_desc?Description [$description]: " - new_desc="${new_desc:-$description}" - - # Remove old and add new - grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" - echo "${name}|${new_connection}|${new_port}|${new_key}|${new_opts}|${new_desc}" >> "${SSH_PROFILES_FILE}.tmp" - mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" - - _ssh_print_success "Updated profile: $name" -} - -# ============================================================================ -# SSH Connection with Tmux Integration -# ============================================================================ - ssh-connect() { local name="$1" local session_name="${2:-${SSH_TMUX_SESSION_PREFIX}-${name}}" - if [[ -z "$name" ]]; then - echo "Usage: ssh-connect [tmux_session_name]" - echo - echo "Saved profiles:" - ssh-list - return 1 - fi + [[ -z "$name" ]] && { echo "Usage: ssh-connect "; ssh-list; return 1; } _ssh_init_profiles - # Parse profile local profile_data=$(_ssh_parse_profile "$name") - if [[ -z "$profile_data" ]]; then - _ssh_print_error "Profile '$name' not found" - echo "Use 'ssh-save $name user@host' to create it" - return 1 - fi + [[ -z "$profile_data" ]] && { _ssh_print_error "Profile '$name' not found"; return 1; } IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data" _ssh_print_step "Connecting to: $name" [[ -n "$description" ]] && echo " $description" - # Build SSH command local ssh_cmd="ssh" - - # Add port [[ -n "$port" && "$port" != "22" ]] && ssh_cmd="$ssh_cmd -p $port" - - # Add key file [[ -n "$key_file" ]] && ssh_cmd="$ssh_cmd -i $key_file" - - # Add custom options [[ -n "$ssh_opts" ]] && ssh_cmd="$ssh_cmd $ssh_opts" - - # Add connection ssh_cmd="$ssh_cmd $connection" - # Tmux integration if [[ "$SSH_AUTO_TMUX" == "true" ]]; then _ssh_print_info "Attaching to tmux session: $session_name" - - # SSH with tmux attach or create local tmux_cmd="tmux attach-session -t $session_name 2>/dev/null || tmux new-session -s $session_name" - - # Execute eval "$ssh_cmd -t '$tmux_cmd'" else - # Direct SSH without tmux eval "$ssh_cmd" fi } -# ============================================================================ -# Fuzzy Search Integration (requires fzf) -# ============================================================================ - sshf() { if ! command -v fzf &>/dev/null; then _ssh_print_error "fzf not installed" - echo "Install: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && ~/.fzf/install" return 1 fi _ssh_init_profiles - # Build selection list local profiles=() while IFS='|' read -r name connection port key options description; do [[ "$name" =~ ^# ]] && continue [[ -z "$name" ]] && continue - local display="$name → $connection" [[ -n "$description" ]] && display="$display ($description)" - profiles+=("$name|$display") done < "$SSH_PROFILES_FILE" - if [[ ${#profiles[@]} -eq 0 ]]; then - _ssh_print_info "No profiles saved" - return 1 - fi + [[ ${#profiles[@]} -eq 0 ]] && { _ssh_print_info "No profiles saved"; return 1; } - # Fuzzy select local selection=$(printf '%s\n' "${profiles[@]}" | \ - fzf --height=50% \ - --layout=reverse \ - --border=rounded \ - --prompt='SSH > ' \ - --preview='echo {}' \ - --preview-window=hidden \ - --delimiter='|' \ - --with-nth=2) + fzf --height=50% --layout=reverse --border=rounded --prompt='SSH > ' \ + --delimiter='|' --with-nth=2) - if [[ -n "$selection" ]]; then - local profile_name="${selection%%|*}" - ssh-connect "$profile_name" - fi + [[ -n "$selection" ]] && ssh-connect "${selection%%|*}" } -# ============================================================================ -# Quick Reconnect -# ============================================================================ - ssh-reconnect() { local name="${1:-last}" if [[ "$name" == "last" ]]; then - # Get last connected profile from history local last_profile=$(grep "ssh-connect" "$HISTFILE" 2>/dev/null | tail -1 | awk '{print $2}') - - if [[ -z "$last_profile" ]]; then - _ssh_print_error "No previous connection found" - return 1 - fi - + [[ -z "$last_profile" ]] && { _ssh_print_error "No previous connection found"; return 1; } name="$last_profile" fi @@ -366,58 +193,29 @@ ssh-reconnect() { ssh-connect "$name" } -# ============================================================================ -# Dotfiles Sync to Remote -# ============================================================================ - ssh-sync-dotfiles() { local name="$1" - - if [[ -z "$name" ]]; then - echo "Usage: ssh-sync-dotfiles " - return 1 - fi + [[ -z "$name" ]] && { echo "Usage: ssh-sync-dotfiles "; return 1; } local profile_data=$(_ssh_parse_profile "$name") - if [[ -z "$profile_data" ]]; then - _ssh_print_error "Profile '$name' not found" - return 1 - fi + [[ -z "$profile_data" ]] && { _ssh_print_error "Profile '$name' not found"; return 1; } IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data" local dotfiles_dir="${DOTFILES_DIR:-$HOME/.dotfiles}" - - if [[ ! -d "$dotfiles_dir" ]]; then - _ssh_print_error "Dotfiles directory not found: $dotfiles_dir" - return 1 - fi + [[ ! -d "$dotfiles_dir" ]] && { _ssh_print_error "Dotfiles directory not found"; return 1; } _ssh_print_step "Syncing dotfiles to: $connection" - # Build rsync command local rsync_cmd="rsync -avz --exclude='.git' --exclude='*.local'" - [[ -n "$port" && "$port" != "22" ]] && rsync_cmd="$rsync_cmd -e 'ssh -p $port'" [[ -n "$key_file" ]] && rsync_cmd="$rsync_cmd -e 'ssh -i $key_file'" - rsync_cmd="$rsync_cmd $dotfiles_dir/ $connection:.dotfiles/" _ssh_print_info "Running: $rsync_cmd" if eval "$rsync_cmd"; then _ssh_print_success "Dotfiles synced successfully" - - # Optionally run install script on remote - read -q "REPLY?Run install script on remote? [y/N]: " - echo - if [[ "$REPLY" =~ ^[Yy]$ ]]; then - local ssh_cmd="ssh" - [[ -n "$port" && "$port" != "22" ]] && ssh_cmd="$ssh_cmd -p $port" - [[ -n "$key_file" ]] && ssh_cmd="$ssh_cmd -i $key_file" - - eval "$ssh_cmd $connection 'cd .dotfiles && ./install.sh --skip-deps'" - fi else _ssh_print_error "Failed to sync dotfiles" return 1 @@ -435,26 +233,6 @@ alias sshd='ssh-delete' alias sshr='ssh-reconnect' alias sshsync='ssh-sync-dotfiles' -# ============================================================================ -# Completion Helper -# ============================================================================ - -_ssh_manager_profiles() { - local profiles=() - while IFS='|' read -r name rest; do - [[ "$name" =~ ^# ]] && continue - [[ -z "$name" ]] && continue - profiles+=("$name") - done < "$SSH_PROFILES_FILE" 2>/dev/null - - echo "${profiles[@]}" -} - -# ZSH completion (if you want to add it) -# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-connect -# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-delete -# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-edit - # ============================================================================ # Initialization # ============================================================================ diff --git a/zsh/functions/tmux-workspaces.zsh b/zsh/functions/tmux-workspaces.zsh index 9ccaf24..72819b6 100644 --- a/zsh/functions/tmux-workspaces.zsh +++ b/zsh/functions/tmux-workspaces.zsh @@ -2,26 +2,16 @@ # Tmux Workspace Manager - Project Templates & Layouts # ============================================================================ # Quick project workspace setup with pre-configured tmux layouts -# -# Usage: -# tw-create [template] # Create workspace from template -# tw-attach # Attach to workspace -# tw-list # List all workspaces -# tw-delete # Delete workspace -# tw-save # Save current layout as template -# tw # Quick attach (or create if not exists) -# -# Templates: -# dev - Vim (50%) + terminal (25%) + logs (25%) -# ops - 4 panes: htop, logs, shell, monitoring -# ssh-multi - 4 panes for managing multiple servers -# debug - 2 panes: main (70%) + helper (30%) -# full - Just one full pane -# -# Add to .zshrc: -# source ~/.dotfiles/zsh/functions/tmux-workspaces.zsh # ============================================================================ +# Source shared colors (with fallback) +source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_GREEN=$'\033[0;32m' DF_BLUE=$'\033[0;34m' + typeset -g DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + typeset -g DF_RED=$'\033[0;31m' DF_NC=$'\033[0m' +} + # ============================================================================ # Configuration # ============================================================================ @@ -30,33 +20,14 @@ typeset -g TW_TEMPLATES_DIR="${TW_TEMPLATES_DIR:-$HOME/.dotfiles/.tmux-templates typeset -g TW_SESSION_PREFIX="${TW_SESSION_PREFIX:-work}" typeset -g TW_DEFAULT_TEMPLATE="${TW_DEFAULT_TEMPLATE:-dev}" -# Colors -typeset -g TW_GREEN=$'\033[0;32m' -typeset -g TW_BLUE=$'\033[0;34m' -typeset -g TW_YELLOW=$'\033[1;33m' -typeset -g TW_CYAN=$'\033[0;36m' -typeset -g TW_RED=$'\033[0;31m' -typeset -g TW_NC=$'\033[0m' - # ============================================================================ # Helper Functions # ============================================================================ -_tw_print_step() { - echo -e "${TW_BLUE}==>${TW_NC} $1" -} - -_tw_print_success() { - echo -e "${TW_GREEN}✓${TW_NC} $1" -} - -_tw_print_error() { - echo -e "${TW_RED}✗${TW_NC} $1" -} - -_tw_print_info() { - echo -e "${TW_CYAN}ℹ${TW_NC} $1" -} +_tw_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; } +_tw_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +_tw_print_error() { echo -e "${DF_RED}✗${DF_NC} $1"; } +_tw_print_info() { echo -e "${DF_CYAN}ℹ${DF_NC} $1"; } _tw_check_tmux() { if ! command -v tmux &>/dev/null; then @@ -68,11 +39,7 @@ _tw_check_tmux() { _tw_init_templates() { mkdir -p "$TW_TEMPLATES_DIR" - - # Create default templates if they don't exist - if [[ ! -f "$TW_TEMPLATES_DIR/dev.tmux" ]]; then - _tw_create_default_templates - fi + [[ ! -f "$TW_TEMPLATES_DIR/dev.tmux" ]] && _tw_create_default_templates } # ============================================================================ @@ -82,89 +49,44 @@ _tw_init_templates() { _tw_create_default_templates() { _tw_print_step "Creating default templates..." - # Development template - vim + terminal + logs cat > "$TW_TEMPLATES_DIR/dev.tmux" << 'EOF' # Development workspace -# Usage: tw-create myproject dev - -# Split vertically (vim on left 50%, rest on right) split-window -h -p 50 - -# Split right pane horizontally (terminal top, logs bottom) split-window -v -p 50 - -# Select the first pane (vim) select-pane -t 0 - -# Optional: Start vim in first pane -# send-keys -t 0 'vim' C-m - -# Optional: Set pane titles -# select-pane -t 0 -T "Editor" -# select-pane -t 1 -T "Terminal" -# select-pane -t 2 -T "Logs" EOF - # Operations template - 4 panes for monitoring cat > "$TW_TEMPLATES_DIR/ops.tmux" << 'EOF' -# Operations workspace -# 4-pane layout for system monitoring - -# Create 2x2 grid +# Operations workspace - 4 panes split-window -h -p 50 split-window -v -p 50 select-pane -t 0 split-window -v -p 50 - -# Optional: Auto-start monitoring tools -# send-keys -t 0 'htop' C-m -# send-keys -t 1 'docker ps' C-m -# send-keys -t 2 '' C-m -# send-keys -t 3 'tail -f /var/log/syslog' C-m - select-pane -t 0 EOF - # SSH multi-server template cat > "$TW_TEMPLATES_DIR/ssh-multi.tmux" << 'EOF' # Multi-server SSH workspace -# 4 panes for managing multiple servers - -# Create 2x2 grid split-window -h -p 50 split-window -v -p 50 select-pane -t 0 split-window -v -p 50 - -# Enable pane synchronization (optional - uncomment to enable) -# set-window-option synchronize-panes on - select-pane -t 0 EOF - # Debug template - main + helper pane cat > "$TW_TEMPLATES_DIR/debug.tmux" << 'EOF' # Debug workspace -# Main pane (70%) + helper pane (30%) - split-window -h -p 30 - select-pane -t 0 EOF - # Full template - single pane cat > "$TW_TEMPLATES_DIR/full.tmux" << 'EOF' -# Full workspace -# Single full-screen pane (default tmux behavior) +# Full workspace - single pane EOF - # Code review template - side-by-side comparison cat > "$TW_TEMPLATES_DIR/review.tmux" << 'EOF' # Code Review workspace -# Two equal panes side-by-side for comparison - split-window -h -p 50 - select-pane -t 0 EOF @@ -178,42 +100,29 @@ EOF tw-templates() { _tw_init_templates - echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}" - echo -e "${TW_BLUE}║${TW_NC} Available Tmux Templates ${TW_BLUE}║${TW_NC}" - echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}" + echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} Available Tmux Templates ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}" echo for template in "$TW_TEMPLATES_DIR"/*.tmux; do [[ ! -f "$template" ]] && continue - local name=$(basename "$template" .tmux) local description=$(grep "^#" "$template" | head -2 | tail -1 | sed 's/^# *//') - - echo -e "${TW_GREEN}●${TW_NC} ${TW_CYAN}$name${TW_NC}" + echo -e "${DF_GREEN}●${DF_NC} ${DF_CYAN}$name${DF_NC}" [[ -n "$description" ]] && echo " $description" done echo - echo "Create workspace: ${TW_CYAN}tw-create myproject dev${TW_NC}" - echo "Quick attach: ${TW_CYAN}tw myproject${TW_NC}" + echo "Create workspace: ${DF_CYAN}tw-create myproject dev${DF_NC}" + echo "Quick attach: ${DF_CYAN}tw myproject${DF_NC}" } tw-template-edit() { local template_name="$1" - - if [[ -z "$template_name" ]]; then - echo "Usage: tw-template-edit " - echo - tw-templates - return 1 - fi - + [[ -z "$template_name" ]] && { echo "Usage: tw-template-edit "; tw-templates; return 1; } _tw_init_templates - - local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux" - - ${EDITOR:-vim} "$template_file" - + ${EDITOR:-vim} "$TW_TEMPLATES_DIR/${template_name}.tmux" _tw_print_success "Template edited: $template_name" } @@ -225,26 +134,19 @@ tw-create() { local workspace_name="$1" local template="${2:-$TW_DEFAULT_TEMPLATE}" - if [[ -z "$workspace_name" ]]; then - echo "Usage: tw-create [template]" - echo - tw-templates - return 1 - fi + [[ -z "$workspace_name" ]] && { echo "Usage: tw-create [template]"; tw-templates; return 1; } _tw_check_tmux || return 1 _tw_init_templates local session_name="${TW_SESSION_PREFIX}-${workspace_name}" - # Check if session already exists if tmux has-session -t "$session_name" 2>/dev/null; then _tw_print_error "Workspace '$workspace_name' already exists" - echo "Use: ${TW_CYAN}tw $workspace_name${TW_NC} to attach" + echo "Use: ${DF_CYAN}tw $workspace_name${DF_NC} to attach" return 1 fi - # Check if template exists local template_file="$TW_TEMPLATES_DIR/${template}.tmux" if [[ ! -f "$template_file" ]]; then _tw_print_error "Template '$template' not found" @@ -254,14 +156,10 @@ tw-create() { _tw_print_step "Creating workspace: $workspace_name (template: $template)" - # Create new tmux session (detached) tmux new-session -d -s "$session_name" - - # Apply template _tw_print_step "Applying template: $template" tmux source-file "$template_file" -t "$session_name" - # Set working directory if we're in a git repo or specific directory if git rev-parse --git-dir &>/dev/null 2>&1; then local git_root=$(git rev-parse --show-toplevel) _tw_print_info "Setting workspace directory to: $git_root" @@ -270,24 +168,17 @@ tw-create() { _tw_print_success "Workspace created: $workspace_name" - # Attach if not already in tmux if [[ -z "$TMUX" ]]; then _tw_print_step "Attaching to workspace..." tmux attach-session -t "$session_name" else - _tw_print_info "Switch with: ${TW_CYAN}tmux switch-client -t $session_name${TW_NC}" + _tw_print_info "Switch with: ${DF_CYAN}tmux switch-client -t $session_name${DF_NC}" fi } tw-attach() { local workspace_name="$1" - - if [[ -z "$workspace_name" ]]; then - echo "Usage: tw-attach " - echo - tw-list - return 1 - fi + [[ -z "$workspace_name" ]] && { echo "Usage: tw-attach "; tw-list; return 1; } _tw_check_tmux || return 1 @@ -295,12 +186,10 @@ tw-attach() { if ! tmux has-session -t "$session_name" 2>/dev/null; then _tw_print_error "Workspace '$workspace_name' not found" - echo - echo "Create it with: ${TW_CYAN}tw-create $workspace_name${TW_NC}" + echo "Create it with: ${DF_CYAN}tw-create $workspace_name${DF_NC}" return 1 fi - # Attach or switch if [[ -z "$TMUX" ]]; then tmux attach-session -t "$session_name" else @@ -311,48 +200,38 @@ tw-attach() { tw-list() { _tw_check_tmux || return 1 - echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}" - echo -e "${TW_BLUE}║${TW_NC} Active Tmux Workspaces ${TW_BLUE}║${TW_NC}" - echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}" + echo -e "${DF_BLUE}╔════════════════════════════════════════════════════════════╗${DF_NC}" + echo -e "${DF_BLUE}║${DF_NC} Active Tmux Workspaces ${DF_BLUE}║${DF_NC}" + echo -e "${DF_BLUE}╚════════════════════════════════════════════════════════════╝${DF_NC}" echo local has_workspaces=false - # List all tmux sessions tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do - # Only show sessions with our prefix if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then has_workspaces=true local workspace_name="${session_full#${TW_SESSION_PREFIX}-}" local attached="" - # Check if currently attached if [[ -n "$TMUX" ]]; then local current_session=$(tmux display-message -p '#S') - [[ "$current_session" == "$session_full" ]] && attached=" ${TW_GREEN}(current)${TW_NC}" + [[ "$current_session" == "$session_full" ]] && attached=" ${DF_GREEN}(current)${DF_NC}" fi - echo -e "${TW_GREEN}●${TW_NC} ${TW_CYAN}$workspace_name${TW_NC}$attached" + echo -e "${DF_GREEN}●${DF_NC} ${DF_CYAN}$workspace_name${DF_NC}$attached" echo " Session: $session_full" fi done if [[ "$has_workspaces" != true ]]; then _tw_print_info "No active workspaces" - echo - echo "Create one with: ${TW_CYAN}tw-create myproject${TW_NC}" + echo "Create one with: ${DF_CYAN}tw-create myproject${DF_NC}" fi } tw-delete() { local workspace_name="$1" - - if [[ -z "$workspace_name" ]]; then - echo "Usage: tw-delete " - echo - tw-list - return 1 - fi + [[ -z "$workspace_name" ]] && { echo "Usage: tw-delete "; tw-list; return 1; } _tw_check_tmux || return 1 @@ -363,75 +242,45 @@ tw-delete() { return 1 fi - # Kill session tmux kill-session -t "$session_name" - _tw_print_success "Deleted workspace: $workspace_name" } -# ============================================================================ -# Save Current Layout as Template -# ============================================================================ - tw-save() { local template_name="$1" - - if [[ -z "$template_name" ]]; then - echo "Usage: tw-save " - echo - echo "Saves the current tmux window layout as a reusable template" - return 1 - fi + [[ -z "$template_name" ]] && { echo "Usage: tw-save "; return 1; } _tw_check_tmux || return 1 - - if [[ -z "$TMUX" ]]; then - _tw_print_error "Must be run from inside tmux" - return 1 - fi + [[ -z "$TMUX" ]] && { _tw_print_error "Must be run from inside tmux"; return 1; } _tw_init_templates local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux" - - if [[ -f "$template_file" ]]; then + [[ -f "$template_file" ]] && { read -q "REPLY?Template '$template_name' exists. Overwrite? [y/N]: " echo [[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1 - fi + } _tw_print_step "Saving current layout as template: $template_name" - - # Get current window layout - local layout=$(tmux display-message -p '#{window_layout}') local pane_count=$(tmux display-message -p '#{window_panes}') - # Create template with layout commands cat > "$template_file" << EOF # Custom template: $template_name # Saved: $(date) # Panes: $pane_count - -# Note: This is a simplified layout recreation -# You may need to adjust split percentages and commands - EOF - # Generate split commands based on pane count if (( pane_count == 2 )); then echo "split-window -h -p 50" >> "$template_file" elif (( pane_count == 3 )); then - cat >> "$template_file" << 'EOF' -split-window -h -p 50 -split-window -v -p 50 -EOF + echo "split-window -h -p 50" >> "$template_file" + echo "split-window -v -p 50" >> "$template_file" elif (( pane_count == 4 )); then - cat >> "$template_file" << 'EOF' -split-window -h -p 50 -split-window -v -p 50 -select-pane -t 0 -split-window -v -p 50 -EOF + echo "split-window -h -p 50" >> "$template_file" + echo "split-window -v -p 50" >> "$template_file" + echo "select-pane -t 0" >> "$template_file" + echo "split-window -v -p 50" >> "$template_file" fi echo "" >> "$template_file" @@ -439,27 +288,19 @@ EOF _tw_print_success "Template saved: $template_name" echo " File: $template_file" - echo " Edit: ${TW_CYAN}tw-template-edit $template_name${TW_NC}" + echo " Edit: ${DF_CYAN}tw-template-edit $template_name${DF_NC}" } -# ============================================================================ -# Quick Workspace (attach or create) -# ============================================================================ - tw() { local workspace_name="$1" local template="${2:-$TW_DEFAULT_TEMPLATE}" - if [[ -z "$workspace_name" ]]; then - tw-list - return 0 - fi + [[ -z "$workspace_name" ]] && { tw-list; return 0; } _tw_check_tmux || return 1 local session_name="${TW_SESSION_PREFIX}-${workspace_name}" - # If session exists, attach. Otherwise create. if tmux has-session -t "$session_name" 2>/dev/null; then tw-attach "$workspace_name" else @@ -468,10 +309,6 @@ tw() { fi } -# ============================================================================ -# Fuzzy Search (requires fzf) -# ============================================================================ - twf() { if ! command -v fzf &>/dev/null; then _tw_print_error "fzf not installed" @@ -480,7 +317,6 @@ twf() { _tw_check_tmux || return 1 - # Get list of sessions local sessions=() tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then @@ -489,57 +325,33 @@ twf() { fi done - if [[ ${#sessions[@]} -eq 0 ]]; then - _tw_print_info "No workspaces found" - return 1 - fi + [[ ${#sessions[@]} -eq 0 ]] && { _tw_print_info "No workspaces found"; return 1; } - # Fuzzy select local selection=$(printf '%s\n' "${sessions[@]}" | \ - fzf --height=40% \ - --layout=reverse \ - --border=rounded \ - --prompt='Workspace > ' \ - --preview='tmux list-windows -t work-{} 2>/dev/null || echo "No preview"') + fzf --height=40% --layout=reverse --border=rounded --prompt='Workspace > ') - if [[ -n "$selection" ]]; then - tw-attach "$selection" - fi + [[ -n "$selection" ]] && tw-attach "$selection" } -# ============================================================================ -# Pane Synchronization Toggle -# ============================================================================ - tw-sync() { - if [[ -z "$TMUX" ]]; then - _tw_print_error "Must be run from inside tmux" - return 1 - fi + [[ -z "$TMUX" ]] && { _tw_print_error "Must be run from inside tmux"; return 1; } local current=$(tmux show-window-option -v synchronize-panes 2>/dev/null) if [[ "$current" == "on" ]]; then tmux set-window-option synchronize-panes off - _tw_print_info "Pane synchronization: ${TW_RED}OFF${TW_NC}" + _tw_print_info "Pane synchronization: ${DF_RED}OFF${DF_NC}" else tmux set-window-option synchronize-panes on - _tw_print_info "Pane synchronization: ${TW_GREEN}ON${TW_NC}" + _tw_print_info "Pane synchronization: ${DF_GREEN}ON${DF_NC}" fi } -# ============================================================================ -# Rename Workspace -# ============================================================================ - tw-rename() { local old_name="$1" local new_name="$2" - if [[ -z "$old_name" || -z "$new_name" ]]; then - echo "Usage: tw-rename " - return 1 - fi + [[ -z "$old_name" || -z "$new_name" ]] && { echo "Usage: tw-rename "; return 1; } _tw_check_tmux || return 1 @@ -552,7 +364,6 @@ tw-rename() { fi tmux rename-session -t "$old_session" "$new_session" - _tw_print_success "Renamed: $old_name → $new_name" } diff --git a/zsh/lib/colors.zsh b/zsh/lib/colors.zsh new file mode 100644 index 0000000..96e1d18 --- /dev/null +++ b/zsh/lib/colors.zsh @@ -0,0 +1,155 @@ +# ============================================================================ +# Shared Color Definitions for Dotfiles +# ============================================================================ +# Source this file in scripts and functions to get consistent color support. +# +# Usage in zsh functions: +# source "${0:A:h}/../lib/colors.zsh" 2>/dev/null || source "$HOME/.dotfiles/zsh/lib/colors.zsh" +# +# Usage in bash scripts: +# source "$HOME/.dotfiles/zsh/lib/colors.zsh" +# +# All variables are prefixed with DF_ (dotfiles) to avoid conflicts. +# ============================================================================ + +# Prevent double-sourcing +[[ -n "$_DF_COLORS_LOADED" ]] && return 0 +typeset -g _DF_COLORS_LOADED=1 + +# ============================================================================ +# Standard Colors (ANSI escape codes) +# ============================================================================ + +typeset -g DF_RED=$'\033[0;31m' +typeset -g DF_GREEN=$'\033[0;32m' +typeset -g DF_YELLOW=$'\033[1;33m' +typeset -g DF_BLUE=$'\033[0;34m' +typeset -g DF_MAGENTA=$'\033[0;35m' +typeset -g DF_CYAN=$'\033[0;36m' +typeset -g DF_WHITE=$'\033[0;37m' + +# Bold variants +typeset -g DF_BOLD_RED=$'\033[1;31m' +typeset -g DF_BOLD_GREEN=$'\033[1;32m' +typeset -g DF_BOLD_YELLOW=$'\033[1;33m' +typeset -g DF_BOLD_BLUE=$'\033[1;34m' +typeset -g DF_BOLD_MAGENTA=$'\033[1;35m' +typeset -g DF_BOLD_CYAN=$'\033[1;36m' +typeset -g DF_BOLD_WHITE=$'\033[1;37m' + +# Text styles +typeset -g DF_BOLD=$'\033[1m' +typeset -g DF_DIM=$'\033[2m' +typeset -g DF_ITALIC=$'\033[3m' +typeset -g DF_UNDERLINE=$'\033[4m' +typeset -g DF_RESET=$'\033[0m' +typeset -g DF_NC=$'\033[0m' # Alias for reset (No Color) + +# ============================================================================ +# 256-Color Palette (used in theme and MOTD) +# ============================================================================ + +typeset -g DF_GREY=$'\033[38;5;242m' +typeset -g DF_LIGHT_GREY=$'\033[38;5;248m' +typeset -g DF_DARK_GREY=$'\033[38;5;239m' +typeset -g DF_ORANGE=$'\033[38;5;208m' +typeset -g DF_LIGHT_ORANGE=$'\033[38;5;220m' +typeset -g DF_PINK=$'\033[38;5;213m' +typeset -g DF_PURPLE=$'\033[38;5;141m' +typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' +typeset -g DF_LIGHT_GREEN=$'\033[38;5;82m' +typeset -g DF_BRIGHT_GREEN=$'\033[38;5;118m' +typeset -g DF_TEAL=$'\033[38;5;51m' + +# ============================================================================ +# Semantic Colors (for consistent UI) +# ============================================================================ + +typeset -g DF_SUCCESS="$DF_GREEN" +typeset -g DF_ERROR="$DF_RED" +typeset -g DF_WARNING="$DF_YELLOW" +typeset -g DF_INFO="$DF_CYAN" +typeset -g DF_HINT="$DF_DIM" +typeset -g DF_ACCENT="$DF_BLUE" +typeset -g DF_MUTED="$DF_GREY" + +# ============================================================================ +# Common Print Functions +# ============================================================================ + +# Print a step/section header +df_print_step() { + echo -e "${DF_GREEN}==>${DF_NC} $1" +} + +# Print success message +df_print_success() { + echo -e "${DF_GREEN}✓${DF_NC} $1" +} + +# Print error message (to stderr) +df_print_error() { + echo -e "${DF_RED}✗${DF_NC} $1" >&2 +} + +# Print warning message +df_print_warning() { + echo -e "${DF_YELLOW}⚠${DF_NC} $1" +} + +# Print info message +df_print_info() { + echo -e "${DF_CYAN}ℹ${DF_NC} $1" +} + +# Print a section divider +df_print_section() { + echo "" + echo -e "${DF_BLUE}▶${DF_NC} $1" + echo -e "${DF_CYAN}─────────────────────────────────────────────────────────────${DF_NC}" +} + +# ============================================================================ +# MOTD-Style Header Function +# ============================================================================ + +# Prints a standardized header box for scripts +# Usage: df_print_header "script-name" +df_print_header() { + local script_name="${1:-script}" + local user="${USER:-root}" + local hostname="${HOST:-${HOSTNAME:-$(hostname -s 2>/dev/null)}}" + local datetime=$(date '+%a %b %d %H:%M') + local width=66 + + # Build horizontal line + local hline="" + for ((i=0; i