1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 12:41:46 +00:00
Files
Mole/mole
Tw93 6cf6a995cd Fix: Improve Homebrew uninstallation feedback in 'mo remove'
When 'mole remove' is used and Mole was installed via Homebrew, the script
now provides more explicit feedback if the 'brew uninstall --force mole'
command fails. Previously, errors were silently ignored.

This change ensures that if Homebrew uninstallation encounters an issue,
the user is informed with the error output and instructed on how to
manually complete the uninstallation, preventing inconsistencies where
Homebrew still believes Mole is installed.

Additionally, a minor improvement to config_dir resolution in update_mole
was included for robustness.
2025-12-31 00:17:40 +08:00

888 lines
28 KiB
Bash
Executable File

#!/bin/bash
# Mole - Main Entry Point
# A comprehensive macOS maintenance tool
#
# Clean - Remove junk files and optimize system
# Uninstall - Remove applications completely
# Analyze - Interactive disk space explorer
#
# Usage:
# ./mole # Interactive main menu
# ./mole clean # Direct clean mode
# ./mole uninstall # Direct uninstall mode
# ./mole analyze # Disk space explorer
# ./mole --help # Show help
set -euo pipefail
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common functions
source "$SCRIPT_DIR/lib/core/common.sh"
# Set up cleanup trap for temporary files
trap cleanup_temp_files EXIT INT TERM
# Version info
VERSION="1.17.0"
MOLE_TAGLINE="Deep clean and optimize your Mac."
# Check TouchID configuration
is_touchid_configured() {
local pam_sudo_file="/etc/pam.d/sudo"
[[ -f "$pam_sudo_file" ]] && grep -q "pam_tid.so" "$pam_sudo_file" 2> /dev/null
}
# Get latest version from remote repository
get_latest_version() {
curl -fsSL --connect-timeout 2 --max-time 3 -H "Cache-Control: no-cache" \
"https://raw.githubusercontent.com/tw93/mole/main/mole" 2> /dev/null |
grep '^VERSION=' | head -1 | sed 's/VERSION="\(.*\)"/\1/'
}
# Get latest version from GitHub API (works for both Homebrew and manual installations)
get_latest_version_from_github() {
local version
version=$(curl -fsSL --connect-timeout 2 --max-time 3 \
"https://api.github.com/repos/tw93/mole/releases/latest" 2> /dev/null |
grep '"tag_name"' | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
# Remove 'v' or 'V' prefix if present
version="${version#v}"
version="${version#V}"
echo "$version"
}
# Check if installed via Homebrew
is_homebrew_install() {
# Fast path: check if mole binary is a Homebrew symlink
local mole_path
mole_path=$(command -v mole 2> /dev/null) || return 1
# Check if mole is a symlink pointing to Homebrew Cellar
if [[ -L "$mole_path" ]] && readlink "$mole_path" | grep -q "Cellar/mole"; then
# Symlink looks good, but verify brew actually manages it
if command -v brew > /dev/null 2>&1; then
# Use fast brew list check
brew list --formula 2> /dev/null | grep -q "^mole$" && return 0
else
# brew not available - cannot update/remove via Homebrew
return 1
fi
fi
# Fallback: check common Homebrew paths and verify with Cellar
if [[ -f "$mole_path" ]]; then
case "$mole_path" in
/opt/homebrew/bin/mole | /usr/local/bin/mole)
# Verify Cellar directory exists
if [[ -d /opt/homebrew/Cellar/mole ]] || [[ -d /usr/local/Cellar/mole ]]; then
# Double-check with brew if available
if command -v brew > /dev/null 2>&1; then
brew list --formula 2> /dev/null | grep -q "^mole$" && return 0
else
return 0 # Cellar exists, probably Homebrew install
fi
fi
;;
esac
fi
# Last resort: check custom Homebrew prefix
if command -v brew > /dev/null 2>&1; then
local brew_prefix
brew_prefix=$(brew --prefix 2> /dev/null)
if [[ -n "$brew_prefix" && "$mole_path" == "$brew_prefix/bin/mole" && -d "$brew_prefix/Cellar/mole" ]]; then
brew list --formula 2> /dev/null | grep -q "^mole$" && return 0
fi
fi
return 1
}
# Check for updates (non-blocking, always check in background)
check_for_updates() {
local msg_cache="$HOME/.cache/mole/update_message"
ensure_user_dir "$(dirname "$msg_cache")"
ensure_user_file "$msg_cache"
# Background version check
# Always check in background, display result from previous check
(
local latest
# Use GitHub API for version check (works for both Homebrew and manual installs)
# Try API first (faster and more reliable)
latest=$(get_latest_version_from_github)
if [[ -z "$latest" ]]; then
# Fallback to parsing mole script from raw GitHub
latest=$(get_latest_version)
fi
if [[ -n "$latest" && "$VERSION" != "$latest" && "$(printf '%s\n' "$VERSION" "$latest" | sort -V | head -1)" == "$VERSION" ]]; then
printf "\nUpdate available: %s → %s, run %smo update%s\n\n" "$VERSION" "$latest" "$GREEN" "$NC" > "$msg_cache"
else
echo -n > "$msg_cache"
fi
) &
disown 2> /dev/null || true
}
# Show update notification if available
show_update_notification() {
local msg_cache="$HOME/.cache/mole/update_message"
if [[ -f "$msg_cache" && -s "$msg_cache" ]]; then
cat "$msg_cache"
echo
fi
}
show_brand_banner() {
cat << EOF
${GREEN} __ __ _ ${NC}
${GREEN}| \/ | ___ | | ___ ${NC}
${GREEN}| |\/| |/ _ \| |/ _ \\${NC}
${GREEN}| | | | (_) | | __/${NC} ${BLUE}https://github.com/tw93/mole${NC}
${GREEN}|_| |_|\___/|_|\___|${NC} ${GREEN}${MOLE_TAGLINE}${NC}
EOF
}
animate_mole_intro() {
# Non-interactive: skip animation
if [[ ! -t 1 ]]; then
return
fi
clear_screen
printf '\n'
hide_cursor
local -a mole_lines=()
if is_christmas_season; then
while IFS= read -r line; do
mole_lines+=("$line")
done << 'EOF'
*
/o\
{/\_/\}
____/ o o \
/~____ =o= /
(______)__m_m)
/ \
__/ /\ \__
/__/ \__\_
EOF
else
while IFS= read -r line; do
mole_lines+=("$line")
done << 'EOF'
/\_/\
____/ o o \
/~____ =o= /
(______)__m_m)
/ \
__/ /\ \__
/__/ \__\_
EOF
fi
local idx
local hat_color="${RED}"
local body_cutoff
local body_color="${PURPLE}"
local ground_color="${GREEN}"
if is_christmas_season; then
body_cutoff=6
for idx in "${!mole_lines[@]}"; do
if ((idx < 3)); then
printf "%s\n" "${hat_color}${mole_lines[$idx]}${NC}"
elif ((idx < body_cutoff)); then
printf "%s\n" "${body_color}${mole_lines[$idx]}${NC}"
else
printf "%s\n" "${ground_color}${mole_lines[$idx]}${NC}"
fi
sleep 0.1
done
else
body_cutoff=4
for idx in "${!mole_lines[@]}"; do
if ((idx < body_cutoff)); then
printf "%s\n" "${body_color}${mole_lines[$idx]}${NC}"
else
printf "%s\n" "${ground_color}${mole_lines[$idx]}${NC}"
fi
sleep 0.1
done
fi
printf '\n'
sleep 0.5
printf '\033[2J\033[H'
show_cursor
}
show_version() {
local os_ver
if command -v sw_vers > /dev/null; then
os_ver=$(sw_vers -productVersion)
else
os_ver="Unknown"
fi
local arch
arch=$(uname -m)
local kernel
kernel=$(uname -r)
local sip_status
if command -v csrutil > /dev/null; then
sip_status=$(csrutil status 2> /dev/null | grep -o "enabled\|disabled" || echo "Unknown")
# Capitalize first letter
sip_status="$(tr '[:lower:]' '[:upper:]' <<< "${sip_status:0:1}")${sip_status:1}"
else
sip_status="Unknown"
fi
local disk_free
disk_free=$(df -h / 2> /dev/null | awk 'NR==2 {print $4}' || echo "Unknown")
local install_method="Manual"
if is_homebrew_install; then
install_method="Homebrew"
fi
printf '\nMole version %s\n' "$VERSION"
printf 'macOS: %s\n' "$os_ver"
printf 'Architecture: %s\n' "$arch"
printf 'Kernel: %s\n' "$kernel"
printf 'SIP: %s\n' "$sip_status"
printf 'Disk Free: %s\n' "$disk_free"
printf 'Install: %s\n' "$install_method"
printf 'Shell: %s\n\n' "${SHELL:-Unknown}"
}
show_help() {
show_brand_banner
echo
printf "%s%s%s\n" "$BLUE" "COMMANDS" "$NC"
printf " %s%-28s%s %s\n" "$GREEN" "mo" "$NC" "Main menu"
printf " %s%-28s%s %s\n" "$GREEN" "mo clean" "$NC" "Free up disk space"
printf " %s%-28s%s %s\n" "$GREEN" "mo uninstall" "$NC" "Remove apps completely"
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize" "$NC" "Check and maintain system"
printf " %s%-28s%s %s\n" "$GREEN" "mo analyze" "$NC" "Explore disk usage"
printf " %s%-28s%s %s\n" "$GREEN" "mo status" "$NC" "Monitor system health"
printf " %s%-28s%s %s\n" "$GREEN" "mo purge" "$NC" "Remove old project artifacts"
printf " %s%-28s%s %s\n" "$GREEN" "mo touchid" "$NC" "Configure Touch ID for sudo"
printf " %s%-28s%s %s\n" "$GREEN" "mo update" "$NC" "Update to latest version"
printf " %s%-28s%s %s\n" "$GREEN" "mo remove" "$NC" "Remove Mole from system"
printf " %s%-28s%s %s\n" "$GREEN" "mo --help" "$NC" "Show help"
printf " %s%-28s%s %s\n" "$GREEN" "mo --version" "$NC" "Show version"
echo
printf " %s%-28s%s %s\n" "$GREEN" "mo clean --dry-run" "$NC" "Preview cleanup"
printf " %s%-28s%s %s\n" "$GREEN" "mo clean --whitelist" "$NC" "Manage protected caches"
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --dry-run" "$NC" "Preview optimization"
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --whitelist" "$NC" "Manage protected items"
printf " %s%-28s%s %s\n" "$GREEN" "mo purge --paths" "$NC" "Configure scan directories"
echo
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
echo
}
# Simple update function
update_mole() {
# Set up cleanup trap for update process
local update_interrupted=false
trap 'update_interrupted=true; echo ""; exit 130' INT TERM
# Check if installed via Homebrew
if is_homebrew_install; then
update_via_homebrew "$VERSION"
exit 0
fi
# Check for updates
local latest
latest=$(get_latest_version_from_github)
# Fallback to raw GitHub if API fails
[[ -z "$latest" ]] && latest=$(get_latest_version)
if [[ -z "$latest" ]]; then
log_error "Unable to check for updates. Check network connection."
echo -e "${YELLOW}Tip:${NC} Check if you can access GitHub (https://github.com)"
echo -e "${YELLOW}Tip:${NC} Try again with: ${GRAY}mo update${NC}"
exit 1
fi
if [[ "$VERSION" == "$latest" ]]; then
echo ""
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version (${VERSION})"
echo ""
exit 0
fi
# Download and run installer with progress
if [[ -t 1 ]]; then
start_inline_spinner "Downloading latest version..."
else
echo "Downloading latest version..."
fi
local installer_url="https://raw.githubusercontent.com/tw93/mole/main/install.sh"
local tmp_installer
tmp_installer="$(mktemp_file)" || {
log_error "Update failed"
exit 1
}
# Download installer with progress and better error handling
local download_error=""
if command -v curl > /dev/null 2>&1; then
download_error=$(curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer" 2>&1) || {
local curl_exit=$?
if [[ -t 1 ]]; then stop_inline_spinner; fi
rm -f "$tmp_installer"
log_error "Update failed (curl error: $curl_exit)"
# Provide helpful error messages based on curl exit codes
case $curl_exit in
6) echo -e "${YELLOW}Tip:${NC} Could not resolve host. Check DNS or network connection." ;;
7) echo -e "${YELLOW}Tip:${NC} Failed to connect. Check network or proxy settings." ;;
22) echo -e "${YELLOW}Tip:${NC} HTTP 404 Not Found. The installer may have moved." ;;
28) echo -e "${YELLOW}Tip:${NC} Connection timed out. Try again or check firewall." ;;
*) echo -e "${YELLOW}Tip:${NC} Check network connection and try again." ;;
esac
echo -e "${YELLOW}Tip:${NC} URL: $installer_url"
exit 1
}
elif command -v wget > /dev/null 2>&1; then
download_error=$(wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1) || {
if [[ -t 1 ]]; then stop_inline_spinner; fi
rm -f "$tmp_installer"
log_error "Update failed (wget error)"
echo -e "${YELLOW}Tip:${NC} Check network connection and try again."
echo -e "${YELLOW}Tip:${NC} URL: $installer_url"
exit 1
}
else
if [[ -t 1 ]]; then stop_inline_spinner; fi
rm -f "$tmp_installer"
log_error "curl or wget required"
echo -e "${YELLOW}Tip:${NC} Install curl with: ${GRAY}brew install curl${NC}"
exit 1
fi
if [[ -t 1 ]]; then stop_inline_spinner; fi
chmod +x "$tmp_installer"
# Determine install directory
local mole_path
mole_path="$(command -v mole 2> /dev/null || echo "$0")"
local install_dir
install_dir="$(cd "$(dirname "$mole_path")" && pwd)"
local requires_sudo="false"
if [[ ! -w "$install_dir" ]]; then
requires_sudo="true"
elif [[ -e "$install_dir/mole" && ! -w "$install_dir/mole" ]]; then
requires_sudo="true"
fi
if [[ "$requires_sudo" == "true" ]]; then
if ! request_sudo_access "Mole update requires admin access"; then
log_error "Update aborted (admin access denied)"
rm -f "$tmp_installer"
exit 1
fi
fi
if [[ -t 1 ]]; then
start_inline_spinner "Installing update..."
else
echo "Installing update..."
fi
# Helper function to process installer output
process_install_output() {
local output="$1"
if [[ -t 1 ]]; then stop_inline_spinner; fi
local filtered_output
filtered_output=$(printf '%s\n' "$output" | sed '/^$/d')
if [[ -n "$filtered_output" ]]; then
printf '\n%s\n' "$filtered_output"
fi
# Only show success message if installer didn't already do so
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
local new_version
new_version=$("$mole_path" --version 2> /dev/null | awk 'NF {print $NF}' || echo "")
printf '\n%s\n\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})"
else
printf '\n'
fi
}
# Run installer with visible output (but capture for error handling)
local install_output
local update_tag="V${latest#V}"
local config_dir="${MOLE_CONFIG_DIR:-$SCRIPT_DIR}"
if [[ ! -f "$config_dir/lib/core/common.sh" ]]; then
config_dir="$HOME/.config/mole"
fi
if install_output=$(MOLE_VERSION="$update_tag" "$tmp_installer" --prefix "$install_dir" --config "$config_dir" --update 2>&1); then
process_install_output "$install_output"
else
# Retry without --update flag
if install_output=$(MOLE_VERSION="$update_tag" "$tmp_installer" --prefix "$install_dir" --config "$config_dir" 2>&1); then
process_install_output "$install_output"
else
if [[ -t 1 ]]; then stop_inline_spinner; fi
rm -f "$tmp_installer"
log_error "Update failed"
echo "$install_output" | tail -10 >&2 # Show last 10 lines of error
exit 1
fi
fi
rm -f "$tmp_installer"
rm -f "$HOME/.cache/mole/update_message"
}
# Remove Mole from system
remove_mole() {
# Detect all installations with loading
if [[ -t 1 ]]; then
start_inline_spinner "Detecting Mole installations..."
else
echo "Detecting installations..."
fi
local is_homebrew=false
local brew_cmd=""
local brew_has_mole="false"
local -a manual_installs=()
local -a alias_installs=()
if command -v brew > /dev/null 2>&1; then
brew_cmd="brew"
elif [[ -x "/opt/homebrew/bin/brew" ]]; then
brew_cmd="/opt/homebrew/bin/brew"
elif [[ -x "/usr/local/bin/brew" ]]; then
brew_cmd="/usr/local/bin/brew"
fi
if [[ -n "$brew_cmd" ]]; then
if "$brew_cmd" list --formula 2> /dev/null | grep -q "^mole$"; then
brew_has_mole="true"
fi
fi
# Check Homebrew
if [[ "$brew_has_mole" == "true" ]] || is_homebrew_install; then
is_homebrew=true
fi
# Find mole installations using which/command
local found_mole
found_mole=$(command -v mole 2> /dev/null || true)
if [[ -n "$found_mole" && -f "$found_mole" ]]; then
# Check if it's not a Homebrew symlink
if [[ ! -L "$found_mole" ]] || ! readlink "$found_mole" | grep -q "Cellar/mole"; then
manual_installs+=("$found_mole")
fi
fi
# Also check common locations as fallback
local -a fallback_paths=(
"/usr/local/bin/mole"
"$HOME/.local/bin/mole"
"/opt/local/bin/mole"
)
for path in "${fallback_paths[@]}"; do
if [[ -f "$path" && "$path" != "$found_mole" ]]; then
# Check if it's not a Homebrew symlink
if [[ ! -L "$path" ]] || ! readlink "$path" | grep -q "Cellar/mole"; then
manual_installs+=("$path")
fi
fi
done
# Find mo alias
local found_mo
found_mo=$(command -v mo 2> /dev/null || true)
if [[ -n "$found_mo" && -f "$found_mo" ]]; then
alias_installs+=("$found_mo")
fi
# Also check common locations for mo
local -a alias_fallback=(
"/usr/local/bin/mo"
"$HOME/.local/bin/mo"
"/opt/local/bin/mo"
)
for alias in "${alias_fallback[@]}"; do
if [[ -f "$alias" && "$alias" != "$found_mo" ]]; then
alias_installs+=("$alias")
fi
done
if [[ -t 1 ]]; then
stop_inline_spinner
fi
printf '\n'
# Check if anything to remove
local manual_count=${#manual_installs[@]}
local alias_count=${#alias_installs[@]}
if [[ "$is_homebrew" == "false" && ${manual_count:-0} -eq 0 && ${alias_count:-0} -eq 0 ]]; then
printf '%s\n\n' "${YELLOW}No Mole installation detected${NC}"
exit 0
fi
# List items for removal
echo -e "${YELLOW}Remove Mole${NC} - will delete the following:"
if [[ "$is_homebrew" == "true" ]]; then
echo " - Mole via Homebrew"
fi
for install in ${manual_installs[@]+"${manual_installs[@]}"} ${alias_installs[@]+"${alias_installs[@]}"}; do
echo " - $install"
done
echo " - ~/.config/mole"
echo " - ~/.cache/mole"
echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to confirm, ${GRAY}ESC${NC} to cancel: "
# Read single key
IFS= read -r -s -n1 key || key=""
drain_pending_input # Clean up any escape sequence remnants
case "$key" in
$'\e')
exit 0
;;
"" | $'\n' | $'\r')
printf "\r\033[K" # Clear the prompt line
# Continue with removal
;;
*)
exit 0
;;
esac
# Remove Homebrew installation
local has_error=false
if [[ "$is_homebrew" == "true" ]]; then
if [[ -z "$brew_cmd" ]]; then
log_error "Homebrew command not found. Please ensure Homebrew is installed and in your PATH."
log_warning "You may need to manually run: brew uninstall --force mole"
exit 1
fi
log_admin "Attempting to uninstall Mole via Homebrew..."
local brew_uninstall_output
if ! brew_uninstall_output=$("$brew_cmd" uninstall --force mole 2>&1); then
has_error=true
log_error "Homebrew uninstallation failed:"
printf "%s\n" "$brew_uninstall_output" | sed "s/^/${RED} | ${NC}/" >&2
log_warning "Please manually run: ${YELLOW}brew uninstall --force mole${NC}"
echo "" # Add a blank line for readability
else
log_success "Mole uninstalled via Homebrew."
fi
fi
# Remove manual installations
if [[ ${manual_count:-0} -gt 0 ]]; then
for install in "${manual_installs[@]}"; do
if [[ -f "$install" ]]; then
# Check if directory requires sudo (deletion is a directory operation)
if [[ ! -w "$(dirname "$install")" ]]; then
# Requires sudo
if ! sudo rm -f "$install" 2> /dev/null; then
has_error=true
fi
else
# Regular user permission
if ! rm -f "$install" 2> /dev/null; then
has_error=true
fi
fi
fi
done
fi
if [[ ${alias_count:-0} -gt 0 ]]; then
for alias in "${alias_installs[@]}"; do
if [[ -f "$alias" ]]; then
# Check if directory requires sudo
if [[ ! -w "$(dirname "$alias")" ]]; then
sudo rm -f "$alias" 2> /dev/null || true
else
rm -f "$alias" 2> /dev/null || true
fi
fi
done
fi
# Clean up cache first (silent)
if [[ -d "$HOME/.cache/mole" ]]; then
rm -rf "$HOME/.cache/mole" 2> /dev/null || true
fi
# Clean up configuration last (silent)
if [[ -d "$HOME/.config/mole" ]]; then
rm -rf "$HOME/.config/mole" 2> /dev/null || true
fi
# Show final result
local final_message
if [[ "$has_error" == "true" ]]; then
final_message="${YELLOW}${ICON_ERROR} Mole uninstalled with some errors, thank you for using Mole!${NC}"
else
final_message="${GREEN}${ICON_SUCCESS} Mole uninstalled successfully, thank you for using Mole!${NC}"
fi
printf '\n%s\n\n' "$final_message"
exit 0
}
# Display main menu options with minimal refresh to avoid flicker
show_main_menu() {
local selected="${1:-1}"
local _full_draw="${2:-true}" # Kept for compatibility (unused)
local banner="${MAIN_MENU_BANNER:-}"
local update_message="${MAIN_MENU_UPDATE_MESSAGE:-}"
# Fallback if globals missing (should not happen)
if [[ -z "$banner" ]]; then
banner="$(show_brand_banner)"
MAIN_MENU_BANNER="$banner"
fi
printf '\033[H' # Move cursor to home
local line=""
# Leading spacer
printf '\r\033[2K\n'
# Brand banner
while IFS= read -r line || [[ -n "$line" ]]; do
printf '\r\033[2K%s\n' "$line"
done <<< "$banner"
# Update notification block (if present)
if [[ -n "$update_message" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
printf '\r\033[2K%s\n' "$line"
done <<< "$update_message"
fi
# Spacer before menu options
printf '\r\033[2K\n'
printf '\r\033[2K%s\n' "$(show_menu_option 1 "Clean Free up disk space" "$([[ $selected -eq 1 ]] && echo true || echo false)")"
printf '\r\033[2K%s\n' "$(show_menu_option 2 "Uninstall Remove apps completely" "$([[ $selected -eq 2 ]] && echo true || echo false)")"
printf '\r\033[2K%s\n' "$(show_menu_option 3 "Optimize Check and maintain system" "$([[ $selected -eq 3 ]] && echo true || echo false)")"
printf '\r\033[2K%s\n' "$(show_menu_option 4 "Analyze Explore disk usage" "$([[ $selected -eq 4 ]] && echo true || echo false)")"
printf '\r\033[2K%s\n' "$(show_menu_option 5 "Status Monitor system health" "$([[ $selected -eq 5 ]] && echo true || echo false)")"
if [[ -t 0 ]]; then
printf '\r\033[2K\n'
# Show TouchID if not configured, otherwise show Update
local controls="${GRAY}↑↓ | Enter | M More | "
if ! is_touchid_configured; then
controls="${controls}T TouchID"
else
controls="${controls}U Update"
fi
controls="${controls} | Q Quit${NC}"
printf '\r\033[2K%s\n' "$controls"
printf '\r\033[2K\n'
fi
# Clear any remaining content below without full screen wipe
printf '\033[J'
}
# Interactive main menu loop
interactive_main_menu() {
# Show intro animation only once per terminal tab
if [[ -t 1 ]]; then
local tty_name
tty_name=$(tty 2> /dev/null || echo "")
if [[ -n "$tty_name" ]]; then
local flag_file
local cache_dir="$HOME/.cache/mole"
ensure_user_dir "$cache_dir"
flag_file="$cache_dir/intro_$(echo "$tty_name" | tr -c '[:alnum:]_' '_')"
if [[ ! -f "$flag_file" ]]; then
animate_mole_intro
ensure_user_file "$flag_file"
fi
fi
fi
local current_option=1
local first_draw=true
local brand_banner=""
local msg_cache="$HOME/.cache/mole/update_message"
local update_message=""
brand_banner="$(show_brand_banner)"
MAIN_MENU_BANNER="$brand_banner"
if [[ -f "$msg_cache" && -s "$msg_cache" ]]; then
update_message="$(cat "$msg_cache" 2> /dev/null || echo "")"
fi
MAIN_MENU_UPDATE_MESSAGE="$update_message"
cleanup_and_exit() {
show_cursor
exit 0
}
trap cleanup_and_exit INT
hide_cursor
while true; do
show_main_menu $current_option "$first_draw"
if [[ "$first_draw" == "true" ]]; then
first_draw=false
fi
local key
if ! key=$(read_key); then
continue
fi
case "$key" in
"UP") ((current_option > 1)) && ((current_option--)) ;;
"DOWN") ((current_option < 5)) && ((current_option++)) ;;
"ENTER")
show_cursor
case $current_option in
1) exec "$SCRIPT_DIR/bin/clean.sh" ;;
2) exec "$SCRIPT_DIR/bin/uninstall.sh" ;;
3) exec "$SCRIPT_DIR/bin/optimize.sh" ;;
4) exec "$SCRIPT_DIR/bin/analyze.sh" ;;
5) exec "$SCRIPT_DIR/bin/status.sh" ;;
esac
;;
"CHAR:1")
show_cursor
exec "$SCRIPT_DIR/bin/clean.sh"
;;
"CHAR:2")
show_cursor
exec "$SCRIPT_DIR/bin/uninstall.sh"
;;
"CHAR:3")
show_cursor
exec "$SCRIPT_DIR/bin/optimize.sh"
;;
"CHAR:4")
show_cursor
exec "$SCRIPT_DIR/bin/analyze.sh"
;;
"CHAR:5")
show_cursor
exec "$SCRIPT_DIR/bin/status.sh"
;;
"MORE")
show_cursor
clear
show_help
exit 0
;;
"VERSION")
show_cursor
clear
show_version
exit 0
;;
"TOUCHID")
show_cursor
exec "$SCRIPT_DIR/bin/touchid.sh"
;;
"UPDATE")
show_cursor
clear
update_mole
exit 0
;;
"QUIT") cleanup_and_exit ;;
esac
# Drain any accumulated input after processing (e.g., touchpad scroll events)
drain_pending_input
done
}
main() {
# Parse global flags
local -a args=()
for arg in "$@"; do
case "$arg" in
--debug)
export MO_DEBUG=1
;;
*)
args+=("$arg")
;;
esac
done
case "${args[0]:-""}" in
"optimize")
exec "$SCRIPT_DIR/bin/optimize.sh" "${args[@]:1}"
;;
"clean")
exec "$SCRIPT_DIR/bin/clean.sh" "${args[@]:1}"
;;
"uninstall")
exec "$SCRIPT_DIR/bin/uninstall.sh" "${args[@]:1}"
;;
"analyze")
exec "$SCRIPT_DIR/bin/analyze.sh" "${args[@]:1}"
;;
"status")
exec "$SCRIPT_DIR/bin/status.sh" "${args[@]:1}"
;;
"purge")
exec "$SCRIPT_DIR/bin/purge.sh" "${args[@]:1}"
;;
"touchid")
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
;;
"update")
update_mole
exit 0
;;
"remove")
remove_mole
;;
"help" | "--help" | "-h")
show_help
exit 0
;;
"version" | "--version" | "-V")
show_version
exit 0
;;
"")
check_for_updates
interactive_main_menu
;;
*)
echo "Unknown command: ${args[0]}"
echo "Use 'mole --help' for usage information."
exit 1
;;
esac
}
main "$@"