1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 15:04:42 +00:00

style: standardize punctuation across codebase

- Replace parentheses with commas for supplementary info
- Use commas instead of em-dashes for separators
- Update bullet points from - to * in some contexts
- Improve version extraction regex with fallback logic
This commit is contained in:
tw93
2026-01-26 14:36:06 +08:00
parent a7bad3d79a
commit e966838c82
36 changed files with 219 additions and 205 deletions

View File

@@ -39,7 +39,7 @@ brew install mole
curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash
``` ```
**Windows:** Mole is designed for macOS, but we offer an experimental Windows version based on user demand. See the [windows branch](https://github.com/tw93/Mole/tree/windows) for early adopters only. **Windows:** Mole is designed for macOS, but we offer an experimental Windows version based on user demand. See the [windows branch](https://github.com/tw93/Mole/tree/windows), for early adopters only.
**Run:** **Run:**
@@ -210,7 +210,7 @@ Select Categories to Clean - 18.5GB (8 selected)
● backend-service 2.5GB | node_modules ● backend-service 2.5GB | node_modules
``` ```
> **Use with caution:** This will permanently delete selected artifacts. Review carefully before confirming. Recent projects less than 7 days old are marked and unselected by default. > **Use with caution:** This will permanently delete selected artifacts. Review carefully before confirming. Recent projects, less than 7 days old, are marked and unselected by default.
<details> <details>
<summary><strong>Custom Scan Paths</strong></summary> <summary><strong>Custom Scan Paths</strong></summary>
@@ -282,4 +282,4 @@ Join thousands of users worldwide who trust Mole to keep their Macs clean and op
## License ## License
MIT License feel free to enjoy and participate in open source. MIT License, feel free to enjoy and participate in open source.

View File

@@ -381,7 +381,7 @@ safe_clean() {
stop_section_spinner stop_section_spinner
fi fi
debug_log "Cleaning: $description (${#existing_paths[@]} items)" debug_log "Cleaning: $description, ${#existing_paths[@]} items"
# Enhanced debug output with risk level and details # Enhanced debug output with risk level and details
if [[ "${MO_DEBUG:-}" == "1" && ${#existing_paths[@]} -gt 0 ]]; then if [[ "${MO_DEBUG:-}" == "1" && ${#existing_paths[@]} -gt 0 ]]; then
@@ -612,7 +612,7 @@ safe_clean() {
debug_log "Permission denied while cleaning: $description" debug_log "Permission denied while cleaning: $description"
fi fi
if [[ $removal_failed_count -gt 0 && "$DRY_RUN" != "true" ]]; then if [[ $removal_failed_count -gt 0 && "$DRY_RUN" != "true" ]]; then
debug_log "Skipped $removal_failed_count items (permission denied or in use) for: $description" debug_log "Skipped $removal_failed_count items, permission denied or in use, for: $description"
fi fi
if [[ $removed_any -eq 1 ]]; then if [[ $removed_any -eq 1 ]]; then
@@ -627,7 +627,7 @@ safe_clean() {
fi fi
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $label ${YELLOW}($size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $label${NC}, ${YELLOW}$size_human dry${NC}"
local paths_temp=$(create_temp_file) local paths_temp=$(create_temp_file)
@@ -678,7 +678,7 @@ safe_clean() {
' | while IFS='|' read -r display_path total_size child_count; do ' | while IFS='|' read -r display_path total_size child_count; do
local size_human=$(bytes_to_human "$((total_size * 1024))") local size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ $child_count -gt 1 ]]; then if [[ $child_count -gt 1 ]]; then
echo "$display_path # $size_human ($child_count items)" >> "$EXPORT_LIST_FILE" echo "$display_path # $size_human, $child_count items" >> "$EXPORT_LIST_FILE"
else else
echo "$display_path # $size_human" >> "$EXPORT_LIST_FILE" echo "$display_path # $size_human" >> "$EXPORT_LIST_FILE"
fi fi
@@ -687,7 +687,7 @@ safe_clean() {
rm -f "$paths_temp" rm -f "$paths_temp"
fi fi
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $label ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} $label${NC}, ${GREEN}$size_human${NC}"
fi fi
((files_cleaned += total_count)) ((files_cleaned += total_count))
((total_size_cleaned += total_size_kb)) ((total_size_cleaned += total_size_kb))
@@ -711,7 +711,7 @@ start_cleanup() {
fi fi
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e "${YELLOW}Dry Run Mode${NC} - Preview only, no deletions" echo -e "${YELLOW}Dry Run Mode${NC}, Preview only, no deletions"
echo "" echo ""
SYSTEM_CLEAN=false SYSTEM_CLEAN=false
@@ -737,7 +737,7 @@ EOF
echo -e "${GREEN}${ICON_SUCCESS}${NC} Admin access already available" echo -e "${GREEN}${ICON_SUCCESS}${NC} Admin access already available"
echo "" echo ""
else else
echo -ne "${PURPLE}${ICON_ARROW}${NC} System caches need sudo ${GREEN}Enter${NC} continue, ${GRAY}Space${NC} skip: " echo -ne "${PURPLE}${ICON_ARROW}${NC} System caches need sudo. ${GREEN}Enter${NC} continue, ${GRAY}Space${NC} skip: "
local choice local choice
choice=$(read_key) choice=$(read_key)
@@ -774,10 +774,10 @@ EOF
echo "Running in non-interactive mode" echo "Running in non-interactive mode"
if sudo -n true 2> /dev/null; then if sudo -n true 2> /dev/null; then
SYSTEM_CLEAN=true SYSTEM_CLEAN=true
echo " ${ICON_LIST} System-level cleanup enabled (sudo session active)" echo " ${ICON_LIST} System-level cleanup enabled, sudo session active"
else else
SYSTEM_CLEAN=false SYSTEM_CLEAN=false
echo " ${ICON_LIST} System-level cleanup skipped (requires sudo)" echo " ${ICON_LIST} System-level cleanup skipped, requires sudo"
fi fi
echo " ${ICON_LIST} User-level cleanup will proceed automatically" echo " ${ICON_LIST} User-level cleanup will proceed automatically"
echo "" echo ""
@@ -790,7 +790,7 @@ perform_cleanup() {
if [[ "${MOLE_TEST_MODE:-0}" == "1" ]]; then if [[ "${MOLE_TEST_MODE:-0}" == "1" ]]; then
test_mode_enabled=true test_mode_enabled=true
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e "${YELLOW}Dry Run Mode${NC} - Preview only, no deletions" echo -e "${YELLOW}Dry Run Mode${NC}, Preview only, no deletions"
echo "" echo ""
fi fi
echo -e "${GREEN}${ICON_LIST}${NC} User app cache" echo -e "${GREEN}${ICON_LIST}${NC} User app cache"
@@ -1054,7 +1054,7 @@ perform_cleanup() {
else else
summary_status="info" summary_status="info"
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
summary_details+=("No significant reclaimable space detected (system already clean).") summary_details+=("No significant reclaimable space detected, system already clean.")
else else
summary_details+=("System was already clean; no additional space freed.") summary_details+=("System was already clean; no additional space freed.")
fi fi

View File

@@ -84,7 +84,7 @@ if [[ $# -eq 0 ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed stale completion entries from $config_file" echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed stale completion entries from $config_file"
echo "" echo ""
fi fi
log_error "mole not found in PATH - install Mole before enabling completion" log_error "mole not found in PATH, install Mole before enabling completion"
exit 1 exit 1
fi fi

View File

@@ -387,7 +387,7 @@ select_installers() {
scroll_indicator=" ${GRAY}[${current_pos}/${total_items}]${NC}" scroll_indicator=" ${GRAY}[${current_pos}/${total_items}]${NC}"
fi fi
printf "${PURPLE_BOLD}Select Installers to Remove${NC}%s ${GRAY}- ${selected_human} ($selected_count selected)${NC}\n" "$scroll_indicator" printf "${PURPLE_BOLD}Select Installers to Remove${NC}%s ${GRAY}, ${selected_human}, ${selected_count} selected${NC}\n" "$scroll_indicator"
printf "%s\n" "$clear_line" printf "%s\n" "$clear_line"
# Calculate visible range # Calculate visible range
@@ -546,13 +546,13 @@ delete_selected_installers() {
local file_size="${INSTALLER_SIZES[$idx]}" local file_size="${INSTALLER_SIZES[$idx]}"
local size_human local size_human
size_human=$(bytes_to_human "$file_size") size_human=$(bytes_to_human "$file_size")
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $(basename "$file_path") ${GRAY}(${size_human})${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} $(basename "$file_path") ${GRAY}, ${size_human}${NC}"
fi fi
done done
# Confirm deletion # Confirm deletion
echo "" echo ""
echo -ne "${PURPLE}${ICON_ARROW}${NC} Delete ${#selected_indices[@]} installer(s) (${confirm_human}) ${GREEN}Enter${NC} confirm, ${GRAY}ESC${NC} cancel: " echo -ne "${PURPLE}${ICON_ARROW}${NC} Delete ${#selected_indices[@]} installers, ${confirm_human} ${GREEN}Enter${NC} confirm, ${GRAY}ESC${NC} cancel: "
IFS= read -r -s -n1 confirm || confirm="" IFS= read -r -s -n1 confirm || confirm=""
case "$confirm" in case "$confirm" in
@@ -655,7 +655,7 @@ show_summary() {
local freed_mb local freed_mb
freed_mb=$(echo "$total_size_freed_kb" | awk '{printf "%.2f", $1/1024}') freed_mb=$(echo "$total_size_freed_kb" | awk '{printf "%.2f", $1/1024}')
summary_details+=("Removed ${GREEN}$total_deleted${NC} installer(s), freed ${GREEN}${freed_mb}MB${NC}") summary_details+=("Removed ${GREEN}$total_deleted${NC} installers, freed ${GREEN}${freed_mb}MB${NC}")
summary_details+=("Your Mac is cleaner now!") summary_details+=("Your Mac is cleaner now!")
else else
summary_details+=("No installers were removed") summary_details+=("No installers were removed")

View File

@@ -78,7 +78,7 @@ show_optimization_summary() {
local total_applied=$((safe_count + confirm_count)) local total_applied=$((safe_count + confirm_count))
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
summary_title="Dry Run Complete - No Changes Made" summary_title="Dry Run Complete, No Changes Made"
summary_details+=("Would apply ${YELLOW}${total_applied:-0}${NC} optimizations") summary_details+=("Would apply ${YELLOW}${total_applied:-0}${NC} optimizations")
summary_details+=("Run without ${YELLOW}--dry-run${NC} to apply these changes") summary_details+=("Run without ${YELLOW}--dry-run${NC} to apply these changes")
else else
@@ -115,9 +115,9 @@ show_optimization_summary() {
fi fi
if [[ -n "$key_stat" ]]; then if [[ -n "$key_stat" ]]; then
summary_details+=("Applied ${GREEN}${total_applied:-0}${NC} optimizations ${key_stat}") summary_details+=("Applied ${GREEN}${total_applied:-0}${NC} optimizations, ${key_stat}")
else else
summary_details+=("Applied ${GREEN}${total_applied:-0}${NC} optimizations all services tuned") summary_details+=("Applied ${GREEN}${total_applied:-0}${NC} optimizations, all services tuned")
fi fi
local summary_line3="" local summary_line3=""
@@ -126,11 +126,11 @@ show_optimization_summary() {
if [[ -n "${AUTO_FIX_DETAILS:-}" ]]; then if [[ -n "${AUTO_FIX_DETAILS:-}" ]]; then
local detail_join local detail_join
detail_join=$(echo "${AUTO_FIX_DETAILS}" | paste -sd ", " -) detail_join=$(echo "${AUTO_FIX_DETAILS}" | paste -sd ", " -)
[[ -n "$detail_join" ]] && summary_line3+=" ${detail_join}" [[ -n "$detail_join" ]] && summary_line3+=": ${detail_join}"
fi fi
summary_details+=("$summary_line3") summary_details+=("$summary_line3")
fi fi
summary_details+=("System fully optimized — faster, more secure and responsive") summary_details+=("System fully optimized")
fi fi
print_summary_block "$summary_title" "${summary_details[@]}" print_summary_block "$summary_title" "${summary_details[@]}"
@@ -226,12 +226,12 @@ cleanup_path() {
if [[ "$removed" == "true" ]]; then if [[ "$removed" == "true" ]]; then
if [[ -n "$size_display" ]]; then if [[ -n "$size_display" ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} $label ${GREEN}(${size_display})${NC}" echo -e "${GREEN}${ICON_SUCCESS}${NC} $label${NC}, ${GREEN}${size_display}${NC}"
else else
echo -e "${GREEN}${ICON_SUCCESS}${NC} $label" echo -e "${GREEN}${ICON_SUCCESS}${NC} $label"
fi fi
else else
echo -e "${GRAY}${ICON_WARNING}${NC} Skipped $label ${GRAY}(grant Full Disk Access to your terminal and retry)${NC}" echo -e "${GRAY}${ICON_WARNING}${NC} Skipped $label${GRAY}, grant Full Disk Access to your terminal and retry${NC}"
fi fi
} }
@@ -252,7 +252,7 @@ collect_security_fix_actions() {
fi fi
if [[ "${GATEKEEPER_DISABLED:-}" == "true" ]]; then if [[ "${GATEKEEPER_DISABLED:-}" == "true" ]]; then
if ! is_whitelisted "gatekeeper"; then if ! is_whitelisted "gatekeeper"; then
SECURITY_FIXES+=("gatekeeper|Enable Gatekeeper (App download protection)") SECURITY_FIXES+=("gatekeeper|Enable Gatekeeper, app download protection")
fi fi
fi fi
if touchid_supported && ! touchid_configured; then if touchid_supported && ! touchid_configured; then
@@ -304,7 +304,7 @@ apply_firewall_fix() {
FIREWALL_DISABLED=false FIREWALL_DISABLED=false
return 0 return 0
fi fi
echo -e " ${GRAY}${ICON_WARNING}${NC} Failed to enable firewall (check permissions)" echo -e " ${GRAY}${ICON_WARNING}${NC} Failed to enable firewall, check permissions"
return 1 return 1
} }
@@ -327,7 +327,7 @@ apply_touchid_fix() {
perform_security_fixes() { perform_security_fixes() {
if ! ensure_sudo_session "Security changes require admin access"; then if ! ensure_sudo_session "Security changes require admin access"; then
echo -e "${GRAY}${ICON_WARNING}${NC} Skipped security fixes (sudo denied)" echo -e "${GRAY}${ICON_WARNING}${NC} Skipped security fixes, sudo denied"
return 1 return 1
fi fi
@@ -391,7 +391,7 @@ main() {
# Dry-run indicator. # Dry-run indicator.
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC} - No files will be modified\n" echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No files will be modified\n"
fi fi
if ! command -v jq > /dev/null 2>&1; then if ! command -v jq > /dev/null 2>&1; then

View File

@@ -220,7 +220,7 @@ perform_purge() {
# Show help message # Show help message
show_help() { show_help() {
echo -e "${PURPLE_BOLD}Mole Purge${NC} - Clean old project build artifacts" echo -e "${PURPLE_BOLD}Mole Purge${NC}, Clean old project build artifacts"
echo "" echo ""
echo -e "${YELLOW}Usage:${NC} mo purge [options]" echo -e "${YELLOW}Usage:${NC} mo purge [options]"
echo "" echo ""
@@ -231,7 +231,7 @@ show_help() {
echo "" echo ""
echo -e "${YELLOW}Default Paths:${NC}" echo -e "${YELLOW}Default Paths:${NC}"
for path in "${DEFAULT_PURGE_SEARCH_PATHS[@]}"; do for path in "${DEFAULT_PURGE_SEARCH_PATHS[@]}"; do
echo " - $path" echo " * $path"
done done
} }

View File

@@ -141,7 +141,7 @@ enable_touchid() {
sudo mv "$temp_file" "$PAM_SUDO_FILE" sudo mv "$temp_file" "$PAM_SUDO_FILE"
log_success "Touch ID migrated to sudo_local" log_success "Touch ID migrated to sudo_local"
else else
log_success "Touch ID enabled (via sudo_local) - try: sudo ls" log_success "Touch ID enabled, via sudo_local, try: sudo ls"
fi fi
return 0 return 0
else else
@@ -188,7 +188,7 @@ enable_touchid() {
# Apply the changes # Apply the changes
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
log_success "Touch ID enabled - try: sudo ls" log_success "Touch ID enabled, try: sudo ls"
return 0 return 0
else else
log_error "Failed to enable Touch ID" log_error "Failed to enable Touch ID"
@@ -219,7 +219,7 @@ disable_touchid() {
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file" grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE" sudo mv "$temp_file" "$PAM_SUDO_FILE"
fi fi
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled (removed from sudo_local)${NC}" echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled, removed from sudo_local${NC}"
echo "" echo ""
return 0 return 0
else else

View File

@@ -496,7 +496,7 @@ main() {
rm -f "$apps_file" rm -f "$apps_file"
continue continue
fi fi
echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} app(s):" echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} apps:"
local -a summary_rows=() local -a summary_rows=()
local max_name_display_width=0 local max_name_display_width=0
local max_size_width=0 local max_size_width=0

View File

@@ -565,7 +565,7 @@ main() {
continue continue
fi fi
# Show selected apps with clean alignment # Show selected apps with clean alignment
echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} app(s):" echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} apps:"
local -a summary_rows=() local -a summary_rows=()
local max_name_width=0 local max_name_width=0
local max_size_width=0 local max_size_width=0

View File

@@ -332,9 +332,9 @@ func (m *model) scheduleOverviewScans() tea.Cmd {
if len(pendingIndices) > 0 { if len(pendingIndices) > 0 {
firstEntry := m.entries[pendingIndices[0]] firstEntry := m.entries[pendingIndices[0]]
if len(pendingIndices) == 1 { if len(pendingIndices) == 1 {
m.status = fmt.Sprintf("Scanning %s... (%d left)", firstEntry.Name, remaining) m.status = fmt.Sprintf("Scanning %s..., %d left", firstEntry.Name, remaining)
} else { } else {
m.status = fmt.Sprintf("Scanning %d directories... (%d left)", len(pendingIndices), remaining) m.status = fmt.Sprintf("Scanning %d directories..., %d left", len(pendingIndices), remaining)
} }
} }
@@ -736,7 +736,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
if len(m.largeMultiSelected) > 0 { if len(m.largeMultiSelected) > 0 {
count := len(m.largeMultiSelected) count := len(m.largeMultiSelected)
if count > maxBatchOpen { if count > maxBatchOpen {
m.status = fmt.Sprintf("Too many items to open (max %d, selected %d)", maxBatchOpen, count) m.status = fmt.Sprintf("Too many items to open, max %d, selected %d", maxBatchOpen, count)
return m, nil return m, nil
} }
for path := range m.largeMultiSelected { for path := range m.largeMultiSelected {
@@ -761,7 +761,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
if len(m.multiSelected) > 0 { if len(m.multiSelected) > 0 {
count := len(m.multiSelected) count := len(m.multiSelected)
if count > maxBatchOpen { if count > maxBatchOpen {
m.status = fmt.Sprintf("Too many items to open (max %d, selected %d)", maxBatchOpen, count) m.status = fmt.Sprintf("Too many items to open, max %d, selected %d", maxBatchOpen, count)
return m, nil return m, nil
} }
for path := range m.multiSelected { for path := range m.multiSelected {
@@ -790,7 +790,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
if len(m.largeMultiSelected) > 0 { if len(m.largeMultiSelected) > 0 {
count := len(m.largeMultiSelected) count := len(m.largeMultiSelected)
if count > maxBatchReveal { if count > maxBatchReveal {
m.status = fmt.Sprintf("Too many items to reveal (max %d, selected %d)", maxBatchReveal, count) m.status = fmt.Sprintf("Too many items to reveal, max %d, selected %d", maxBatchReveal, count)
return m, nil return m, nil
} }
for path := range m.largeMultiSelected { for path := range m.largeMultiSelected {
@@ -815,7 +815,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
if len(m.multiSelected) > 0 { if len(m.multiSelected) > 0 {
count := len(m.multiSelected) count := len(m.multiSelected)
if count > maxBatchReveal { if count > maxBatchReveal {
m.status = fmt.Sprintf("Too many items to reveal (max %d, selected %d)", maxBatchReveal, count) m.status = fmt.Sprintf("Too many items to reveal, max %d, selected %d", maxBatchReveal, count)
return m, nil return m, nil
} }
for path := range m.multiSelected { for path := range m.multiSelected {
@@ -860,7 +860,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
} }
} }
} }
m.status = fmt.Sprintf("%d selected (%s)", count, humanizeBytes(totalSize)) m.status = fmt.Sprintf("%d selected, %s", count, humanizeBytes(totalSize))
} else { } else {
m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize)) m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize))
} }
@@ -886,7 +886,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
} }
} }
} }
m.status = fmt.Sprintf("%d selected (%s)", count, humanizeBytes(totalSize)) m.status = fmt.Sprintf("%d selected, %s", count, humanizeBytes(totalSize))
} else { } else {
m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize)) m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize))
} }
@@ -1011,7 +1011,7 @@ func (m model) enterSelectedDir() (tea.Model, tea.Cmd) {
} }
return m, tea.Batch(m.scanCmd(m.path), tickCmd()) return m, tea.Batch(m.scanCmd(m.path), tickCmd())
} }
m.status = fmt.Sprintf("File: %s (%s)", selected.Name, humanizeBytes(selected.Size)) m.status = fmt.Sprintf("File: %s, %s", selected.Name, humanizeBytes(selected.Size))
return m, nil return m, nil
} }

View File

@@ -594,7 +594,7 @@ func getDirectorySizeFromDuWithExclude(path string, excludePath string) (int64,
return 0, fmt.Errorf("du timeout after %v", duTimeout) return 0, fmt.Errorf("du timeout after %v", duTimeout)
} }
if stderr.Len() > 0 { if stderr.Len() > 0 {
return 0, fmt.Errorf("du failed: %v (%s)", err, stderr.String()) return 0, fmt.Errorf("du failed: %v, %s", err, stderr.String())
} }
return 0, fmt.Errorf("du failed: %v", err) return 0, fmt.Errorf("du failed: %v", err)
} }

View File

@@ -86,7 +86,7 @@ func (m model) View() string {
if m.scanning && percent >= 100 { if m.scanning && percent >= 100 {
percent = 99 percent = 99
} }
progressPrefix = fmt.Sprintf(" %s(%.0f%%)%s", colorCyan, percent, colorReset) progressPrefix = fmt.Sprintf(" %s%.0f%%%s", colorCyan, percent, colorReset)
} }
fmt.Fprintf(&b, "%s%s%s%s Scanning%s: %s%s files%s, %s%s dirs%s, %s%s%s\n", fmt.Fprintf(&b, "%s%s%s%s Scanning%s: %s%s files%s, %s%s dirs%s, %s%s%s\n",
@@ -342,7 +342,7 @@ func (m model) View() string {
} else if m.showLargeFiles { } else if m.showLargeFiles {
selectCount := len(m.largeMultiSelected) selectCount := len(m.largeMultiSelected)
if selectCount > 0 { if selectCount > 0 {
fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del(%d) | ← Back | Q Quit%s\n", colorGray, selectCount, colorReset) fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del %d | ← Back | Q Quit%s\n", colorGray, selectCount, colorReset)
} else { } else {
fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del | ← Back | Q Quit%s\n", colorGray, colorReset) fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del | ← Back | Q Quit%s\n", colorGray, colorReset)
} }
@@ -351,13 +351,13 @@ func (m model) View() string {
selectCount := len(m.multiSelected) selectCount := len(m.multiSelected)
if selectCount > 0 { if selectCount > 0 {
if largeFileCount > 0 { if largeFileCount > 0 {
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del(%d) | T Top(%d) | Q Quit%s\n", colorGray, selectCount, largeFileCount, colorReset) fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del %d | T Top %d | Q Quit%s\n", colorGray, selectCount, largeFileCount, colorReset)
} else { } else {
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del(%d) | Q Quit%s\n", colorGray, selectCount, colorReset) fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del %d | Q Quit%s\n", colorGray, selectCount, colorReset)
} }
} else { } else {
if largeFileCount > 0 { if largeFileCount > 0 {
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | T Top(%d) | Q Quit%s\n", colorGray, largeFileCount, colorReset) fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | T Top %d | Q Quit%s\n", colorGray, largeFileCount, colorReset)
} else { } else {
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | Q Quit%s\n", colorGray, colorReset) fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | Q Quit%s\n", colorGray, colorReset)
} }
@@ -390,12 +390,12 @@ func (m model) View() string {
} }
if deleteCount > 1 { if deleteCount > 1 {
fmt.Fprintf(&b, "%sDelete:%s %d items (%s) %sPress Enter to confirm | ESC cancel%s\n", fmt.Fprintf(&b, "%sDelete:%s %d items, %s %sPress Enter to confirm | ESC cancel%s\n",
colorRed, colorReset, colorRed, colorReset,
deleteCount, humanizeBytes(totalDeleteSize), deleteCount, humanizeBytes(totalDeleteSize),
colorGray, colorReset) colorGray, colorReset)
} else { } else {
fmt.Fprintf(&b, "%sDelete:%s %s (%s) %sPress Enter to confirm | ESC cancel%s\n", fmt.Fprintf(&b, "%sDelete:%s %s, %s %sPress Enter to confirm | ESC cancel%s\n",
colorRed, colorReset, colorRed, colorReset,
m.deleteTarget.Name, humanizeBytes(m.deleteTarget.Size), m.deleteTarget.Name, humanizeBytes(m.deleteTarget.Size),
colorGray, colorReset) colorGray, colorReset)

View File

@@ -145,7 +145,7 @@ func renderHeader(m MetricsSnapshot, errMsg string, animFrame int, termWidth int
cpuInfo := m.Hardware.CPUModel cpuInfo := m.Hardware.CPUModel
// Append GPU core count when available. // Append GPU core count when available.
if len(m.GPU) > 0 && m.GPU[0].CoreCount > 0 { if len(m.GPU) > 0 && m.GPU[0].CoreCount > 0 {
cpuInfo += fmt.Sprintf(" (%dGPU)", m.GPU[0].CoreCount) cpuInfo += fmt.Sprintf(", %dGPU", m.GPU[0].CoreCount)
} }
infoParts = append(infoParts, cpuInfo) infoParts = append(infoParts, cpuInfo)
} }
@@ -218,7 +218,7 @@ func renderCPUCard(cpu CPUStatus, thermal ThermalStatus) cardData {
lines = append(lines, fmt.Sprintf("Total %s %s", usageBar, headerText)) lines = append(lines, fmt.Sprintf("Total %s %s", usageBar, headerText))
if cpu.PerCoreEstimated { if cpu.PerCoreEstimated {
lines = append(lines, subtleStyle.Render("Per-core data unavailable (using averaged load)")) lines = append(lines, subtleStyle.Render("Per-core data unavailable, using averaged load"))
} else if len(cpu.PerCore) > 0 { } else if len(cpu.PerCore) > 0 {
type coreUsage struct { type coreUsage struct {
idx int idx int
@@ -239,10 +239,10 @@ func renderCPUCard(cpu CPUStatus, thermal ThermalStatus) cardData {
// Load line at the end // Load line at the end
if cpu.PCoreCount > 0 && cpu.ECoreCount > 0 { if cpu.PCoreCount > 0 && cpu.ECoreCount > 0 {
lines = append(lines, fmt.Sprintf("Load %.2f / %.2f / %.2f (%dP+%dE)", lines = append(lines, fmt.Sprintf("Load %.2f / %.2f / %.2f, %dP+%dE",
cpu.Load1, cpu.Load5, cpu.Load15, cpu.PCoreCount, cpu.ECoreCount)) cpu.Load1, cpu.Load5, cpu.Load15, cpu.PCoreCount, cpu.ECoreCount))
} else { } else {
lines = append(lines, fmt.Sprintf("Load %.2f / %.2f / %.2f (%d cores)", lines = append(lines, fmt.Sprintf("Load %.2f / %.2f / %.2f, %d cores",
cpu.Load1, cpu.Load5, cpu.Load15, cpu.LogicalCPU)) cpu.Load1, cpu.Load5, cpu.Load15, cpu.LogicalCPU))
} }
@@ -270,7 +270,7 @@ func renderMemoryCard(mem MemoryStatus) cardData {
if mem.SwapTotal > 0 { if mem.SwapTotal > 0 {
swapPercent = (float64(mem.SwapUsed) / float64(mem.SwapTotal)) * 100.0 swapPercent = (float64(mem.SwapUsed) / float64(mem.SwapTotal)) * 100.0
} }
swapText := fmt.Sprintf("(%s/%s)", humanBytesCompact(mem.SwapUsed), humanBytesCompact(mem.SwapTotal)) swapText := fmt.Sprintf("%s/%s", humanBytesCompact(mem.SwapUsed), humanBytesCompact(mem.SwapTotal))
lines = append(lines, fmt.Sprintf("Swap %s %5.1f%% %s", progressBar(swapPercent), swapPercent, swapText)) lines = append(lines, fmt.Sprintf("Swap %s %5.1f%% %s", progressBar(swapPercent), swapPercent, swapText))
lines = append(lines, fmt.Sprintf("Total %s / %s", humanBytes(mem.Used), humanBytes(mem.Total))) lines = append(lines, fmt.Sprintf("Total %s / %s", humanBytes(mem.Used), humanBytes(mem.Total)))
@@ -361,7 +361,7 @@ func formatDiskLine(label string, d DiskStatus) string {
bar := progressBar(d.UsedPercent) bar := progressBar(d.UsedPercent)
used := humanBytesShort(d.Used) used := humanBytesShort(d.Used)
total := humanBytesShort(d.Total) total := humanBytesShort(d.Total)
return fmt.Sprintf("%-6s %s %5.1f%% (%s/%s)", label, bar, d.UsedPercent, used, total) return fmt.Sprintf("%-6s %s %5.1f%%, %s/%s", label, bar, d.UsedPercent, used, total)
} }
func ioBar(rate float64) string { func ioBar(rate float64) string {

View File

@@ -165,7 +165,7 @@ resolve_source_dir() {
url="https://github.com/tw93/mole/archive/refs/tags/${branch}.tar.gz" url="https://github.com/tw93/mole/archive/refs/tags/${branch}.tar.gz"
fi fi
start_line_spinner "Fetching Mole source (${branch})..." start_line_spinner "Fetching Mole source, ${branch}..."
if command -v curl > /dev/null 2>&1; then if command -v curl > /dev/null 2>&1; then
if curl -fsSL --connect-timeout 10 --max-time 60 -o "$tmp/mole.tar.gz" "$url" 2> /dev/null; then if curl -fsSL --connect-timeout 10 --max-time 60 -o "$tmp/mole.tar.gz" "$url" 2> /dev/null; then
if tar -xzf "$tmp/mole.tar.gz" -C "$tmp" 2> /dev/null; then if tar -xzf "$tmp/mole.tar.gz" -C "$tmp" 2> /dev/null; then
@@ -509,7 +509,7 @@ download_binary() {
log_success "Downloaded ${binary_name} binary" log_success "Downloaded ${binary_name} binary"
else else
if [[ -t 1 ]]; then stop_line_spinner; fi if [[ -t 1 ]]; then stop_line_spinner; fi
log_warning "Could not download ${binary_name} binary (v${version}), trying local build" log_warning "Could not download ${binary_name} binary, v${version}, trying local build"
if build_binary_from_source "$binary_name" "$target_path"; then if build_binary_from_source "$binary_name" "$target_path"; then
return 0 return 0
fi fi
@@ -659,9 +659,9 @@ print_usage_summary() {
local message="Mole ${action} successfully" local message="Mole ${action} successfully"
if [[ "$action" == "updated" && -n "$previous_version" && -n "$new_version" && "$previous_version" != "$new_version" ]]; then if [[ "$action" == "updated" && -n "$previous_version" && -n "$new_version" && "$previous_version" != "$new_version" ]]; then
message+=" (${previous_version} -> ${new_version})" message+=", ${previous_version} -> ${new_version}"
elif [[ -n "$new_version" ]]; then elif [[ -n "$new_version" ]]; then
message+=" (version ${new_version})" message+=", version ${new_version}"
fi fi
log_confirm "$message" log_confirm "$message"
@@ -763,7 +763,7 @@ perform_update() {
fi fi
if [[ "$installed_version" == "$target_version" ]]; then if [[ "$installed_version" == "$target_version" ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version ($installed_version)" echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version, $installed_version"
exit 0 exit 0
fi fi
@@ -794,7 +794,7 @@ perform_update() {
updated_version="$target_version" updated_version="$target_version"
fi fi
echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version ($updated_version)" echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, $updated_version"
} }
parse_args "$@" parse_args "$@"

View File

@@ -46,9 +46,9 @@ clean_ds_store_tree() {
local size_human local size_human
size_human=$(bytes_to_human "$total_bytes") size_human=$(bytes_to_human "$total_bytes")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $label ${YELLOW}($file_count files, $size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $label${NC}, ${YELLOW}$file_count files, $size_human dry${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $label ${GREEN}($file_count files, $size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} $label${NC}, ${GREEN}$file_count files, $size_human${NC}"
fi fi
local size_kb=$(((total_bytes + 1023) / 1024)) local size_kb=$(((total_bytes + 1023) / 1024))
((files_cleaned += file_count)) ((files_cleaned += file_count))
@@ -70,7 +70,7 @@ scan_installed_apps() {
current_time=$(get_epoch_seconds) current_time=$(get_epoch_seconds)
local age=$((current_time - cache_mtime)) local age=$((current_time - cache_mtime))
if [[ $age -lt $cache_age_seconds ]]; then if [[ $age -lt $cache_age_seconds ]]; then
debug_log "Using cached app list (age: ${age}s)" debug_log "Using cached app list, age: ${age}s"
if [[ -r "$cache_file" ]] && [[ -s "$cache_file" ]]; then if [[ -r "$cache_file" ]] && [[ -s "$cache_file" ]]; then
if cat "$cache_file" > "$installed_bundles" 2> /dev/null; then if cat "$cache_file" > "$installed_bundles" 2> /dev/null; then
return 0 return 0
@@ -82,7 +82,7 @@ scan_installed_apps() {
fi fi
fi fi
fi fi
debug_log "Scanning installed applications (cache expired or missing)" debug_log "Scanning installed applications, cache expired or missing"
local -a app_dirs=( local -a app_dirs=(
"/Applications" "/Applications"
"/System/Applications" "/System/Applications"
@@ -310,7 +310,7 @@ clean_orphaned_app_data() {
stop_section_spinner stop_section_spinner
if [[ $orphaned_count -gt 0 ]]; then if [[ $orphaned_count -gt 0 ]]; then
local orphaned_mb=$(echo "$total_orphaned_kb" | awk '{printf "%.1f", $1/1024}') local orphaned_mb=$(echo "$total_orphaned_kb" | awk '{printf "%.1f", $1/1024}')
echo " ${GREEN}${ICON_SUCCESS}${NC} Cleaned $orphaned_count items (~${orphaned_mb}MB)" echo " ${GREEN}${ICON_SUCCESS}${NC} Cleaned $orphaned_count items, about ${orphaned_mb}MB"
note_activity note_activity
fi fi
rm -f "$installed_bundles" rm -f "$installed_bundles"
@@ -512,7 +512,7 @@ clean_orphaned_system_services() {
else else
orphaned_kb_display="${total_orphaned_kb}KB" orphaned_kb_display="${total_orphaned_kb}KB"
fi fi
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Cleaned $orphaned_count orphaned services (~$orphaned_kb_display)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Cleaned $orphaned_count orphaned services, about $orphaned_kb_display"
note_activity note_activity
fi fi

View File

@@ -86,9 +86,9 @@ clean_homebrew() {
freed_space=$(printf '%s\n' "$brew_output" | grep -o "[0-9.]*[KMGT]B freed" 2> /dev/null | tail -1 || true) freed_space=$(printf '%s\n' "$brew_output" | grep -o "[0-9.]*[KMGT]B freed" 2> /dev/null | tail -1 || true)
if [[ $removed_count -gt 0 ]] || [[ -n "$freed_space" ]]; then if [[ $removed_count -gt 0 ]] || [[ -n "$freed_space" ]]; then
if [[ -n "$freed_space" ]]; then if [[ -n "$freed_space" ]]; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Homebrew cleanup ${GREEN}($freed_space)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Homebrew cleanup${NC}, ${GREEN}$freed_space${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Homebrew cleanup (${removed_count} items)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Homebrew cleanup, ${removed_count} items"
fi fi
fi fi
elif [[ $brew_exit -eq 124 ]]; then elif [[ $brew_exit -eq 124 ]]; then
@@ -102,7 +102,7 @@ clean_homebrew() {
local removed_packages local removed_packages
removed_packages=$(printf '%s\n' "$autoremove_output" | grep -c "^Uninstalling" 2> /dev/null || true) removed_packages=$(printf '%s\n' "$autoremove_output" | grep -c "^Uninstalling" 2> /dev/null || true)
if [[ $removed_packages -gt 0 ]]; then if [[ $removed_packages -gt 0 ]]; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Removed orphaned dependencies (${removed_packages} packages)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Removed orphaned dependencies, ${removed_packages} packages"
fi fi
elif [[ $autoremove_exit -eq 124 ]]; then elif [[ $autoremove_exit -eq 124 ]]; then
echo -e " ${GRAY}${ICON_WARNING}${NC} Autoremove timed out · run ${GRAY}brew autoremove${NC} manually" echo -e " ${GRAY}${ICON_WARNING}${NC} Autoremove timed out · run ${GRAY}brew autoremove${NC} manually"

View File

@@ -22,7 +22,7 @@ check_tcc_permissions() {
echo "" echo ""
echo -e "${BLUE}First-time setup${NC}" echo -e "${BLUE}First-time setup${NC}"
echo -e "${GRAY}macOS will request permissions to access Library folders.${NC}" echo -e "${GRAY}macOS will request permissions to access Library folders.${NC}"
echo -e "${GRAY}You may see ${GREEN}${#tcc_dirs[@]} permission dialogs${NC}${GRAY} - please approve them all.${NC}" echo -e "${GRAY}You may see ${GREEN}${#tcc_dirs[@]} permission dialogs${NC}${GRAY}, please approve them all.${NC}"
echo "" echo ""
echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to continue: " echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to continue: "
read -r read -r
@@ -75,12 +75,12 @@ clean_service_worker_cache() {
local cleaned_mb=$((cleaned_size / 1024)) local cleaned_mb=$((cleaned_size / 1024))
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
if [[ $protected_count -gt 0 ]]; then if [[ $protected_count -gt 0 ]]; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker (${cleaned_mb}MB, ${protected_count} protected)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker, ${cleaned_mb}MB, ${protected_count} protected"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker (${cleaned_mb}MB)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker, ${cleaned_mb}MB"
fi fi
else else
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $browser_name Service Worker (would clean ${cleaned_mb}MB, ${protected_count} protected)" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $browser_name Service Worker, would clean ${cleaned_mb}MB, ${protected_count} protected"
fi fi
note_activity note_activity
if [[ "$spinner_was_running" == "true" ]]; then if [[ "$spinner_was_running" == "true" ]]; then

View File

@@ -606,7 +606,7 @@ select_purge_categories() {
fi fi
printf "%s\n" "$clear_line" printf "%s\n" "$clear_line"
printf "%s${PURPLE_BOLD}Select Categories to Clean${NC}%s ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}\n" "$clear_line" "$scroll_indicator" printf "%s${PURPLE_BOLD}Select Categories to Clean${NC}%s ${GRAY}, ${selected_gb}GB, ${selected_count} selected${NC}\n" "$clear_line" "$scroll_indicator"
printf "%s\n" "$clear_line" printf "%s\n" "$clear_line"
IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}" IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}"
@@ -1135,7 +1135,7 @@ clean_project_artifacts() {
fi fi
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
stop_inline_spinner stop_inline_spinner
echo -e "${GREEN}${ICON_SUCCESS}${NC} $project_path - $artifact_type ${GREEN}($size_human)${NC}" echo -e "${GREEN}${ICON_SUCCESS}${NC} $project_path, $artifact_type${NC}, ${GREEN}$size_human${NC}"
fi fi
done done
# Update count # Update count

View File

@@ -39,18 +39,18 @@ clean_deep_system() {
if [[ -d "/macOS Install Data" ]]; then if [[ -d "/macOS Install Data" ]]; then
local mtime=$(get_file_mtime "/macOS Install Data") local mtime=$(get_file_mtime "/macOS Install Data")
local age_days=$((($(get_epoch_seconds) - mtime) / 86400)) local age_days=$((($(get_epoch_seconds) - mtime) / 86400))
debug_log "Found macOS Install Data (age: ${age_days} days)" debug_log "Found macOS Install Data, age ${age_days} days"
if [[ $age_days -ge 30 ]]; then if [[ $age_days -ge 30 ]]; then
local size_kb=$(get_path_size_kb "/macOS Install Data") local size_kb=$(get_path_size_kb "/macOS Install Data")
if [[ -n "$size_kb" && "$size_kb" -gt 0 ]]; then if [[ -n "$size_kb" && "$size_kb" -gt 0 ]]; then
local size_human=$(bytes_to_human "$((size_kb * 1024))") local size_human=$(bytes_to_human "$((size_kb * 1024))")
debug_log "Cleaning macOS Install Data: $size_human (${age_days} days old)" debug_log "Cleaning macOS Install Data: $size_human, ${age_days} days old"
if safe_sudo_remove "/macOS Install Data"; then if safe_sudo_remove "/macOS Install Data"; then
log_success "macOS Install Data ($size_human)" log_success "macOS Install Data, $size_human"
fi fi
fi fi
else else
debug_log "Keeping macOS Install Data (only ${age_days} days old, needs 30+)" debug_log "Keeping macOS Install Data, only ${age_days} days old, needs 30+"
fi fi
fi fi
start_section_spinner "Scanning system caches..." start_section_spinner "Scanning system caches..."
@@ -70,13 +70,13 @@ clean_deep_system() {
local current_time local current_time
current_time=$(get_epoch_seconds) current_time=$(get_epoch_seconds)
if [[ $((current_time - last_update_time)) -ge $update_interval ]]; then if [[ $((current_time - last_update_time)) -ge $update_interval ]]; then
start_section_spinner "Scanning system caches... ($found_count found)" start_section_spinner "Scanning system caches... $found_count found"
last_update_time=$current_time last_update_time=$current_time
fi fi
fi fi
done < <(run_with_timeout 5 command find /private/var/folders -type d -name "*.code_sign_clone" -path "*/X/*" -print0 2> /dev/null || true) done < <(run_with_timeout 5 command find /private/var/folders -type d -name "*.code_sign_clone" -path "*/X/*" -print0 2> /dev/null || true)
stop_section_spinner stop_section_spinner
[[ $code_sign_cleaned -gt 0 ]] && log_success "Browser code signature caches ($code_sign_cleaned items)" [[ $code_sign_cleaned -gt 0 ]] && log_success "Browser code signature caches, $code_sign_cleaned items"
start_section_spinner "Cleaning system diagnostic logs..." start_section_spinner "Cleaning system diagnostic logs..."
local diag_cleaned=0 local diag_cleaned=0
@@ -178,7 +178,7 @@ clean_time_machine_failed_backups() {
local backup_name=$(basename "$inprogress_file") local backup_name=$(basename "$inprogress_file")
local size_human=$(bytes_to_human "$((size_kb * 1024))") local size_human=$(bytes_to_human "$((size_kb * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Incomplete backup: $backup_name ${YELLOW}($size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Incomplete backup: $backup_name${NC}, ${YELLOW}$size_human dry${NC}"
((tm_cleaned++)) ((tm_cleaned++))
note_activity note_activity
continue continue
@@ -188,7 +188,7 @@ clean_time_machine_failed_backups() {
continue continue
fi fi
if tmutil delete "$inprogress_file" 2> /dev/null; then if tmutil delete "$inprogress_file" 2> /dev/null; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete backup: $backup_name ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete backup: $backup_name${NC}, ${GREEN}$size_human${NC}"
((tm_cleaned++)) ((tm_cleaned++))
((files_cleaned++)) ((files_cleaned++))
((total_size_cleaned += size_kb)) ((total_size_cleaned += size_kb))
@@ -224,7 +224,7 @@ clean_time_machine_failed_backups() {
local backup_name=$(basename "$inprogress_file") local backup_name=$(basename "$inprogress_file")
local size_human=$(bytes_to_human "$((size_kb * 1024))") local size_human=$(bytes_to_human "$((size_kb * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Incomplete APFS backup in $bundle_name: $backup_name ${YELLOW}($size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Incomplete APFS backup in $bundle_name: $backup_name${NC}, ${YELLOW}$size_human dry${NC}"
((tm_cleaned++)) ((tm_cleaned++))
note_activity note_activity
continue continue
@@ -233,7 +233,7 @@ clean_time_machine_failed_backups() {
continue continue
fi fi
if tmutil delete "$inprogress_file" 2> /dev/null; then if tmutil delete "$inprogress_file" 2> /dev/null; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete APFS backup in $bundle_name: $backup_name ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete APFS backup in $bundle_name: $backup_name${NC}, ${GREEN}$size_human${NC}"
((tm_cleaned++)) ((tm_cleaned++))
((files_cleaned++)) ((files_cleaned++))
((total_size_cleaned += size_kb)) ((total_size_cleaned += size_kb))

View File

@@ -14,10 +14,10 @@ clean_user_essentials() {
[[ "$trash_count" =~ ^[0-9]+$ ]] || trash_count="0" [[ "$trash_count" =~ ^[0-9]+$ ]] || trash_count="0"
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
[[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty ($trash_count items)" || echo -e " ${GRAY}${ICON_EMPTY}${NC} Trash · already empty" [[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty, $trash_count items" || echo -e " ${GRAY}${ICON_EMPTY}${NC} Trash · already empty"
elif [[ $trash_count -gt 0 ]]; then elif [[ $trash_count -gt 0 ]]; then
if osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then if osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · emptied ($trash_count items)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · emptied, $trash_count items"
note_activity note_activity
else else
safe_clean ~/.Trash/* "Trash" safe_clean ~/.Trash/* "Trash"
@@ -97,9 +97,9 @@ clean_chrome_old_versions() {
local size_human local size_human
size_human=$(bytes_to_human "$((total_size * 1024))") size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Chrome old versions ${YELLOW}(${cleaned_count} dirs, $size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Chrome old versions${NC}, ${YELLOW}${cleaned_count} dirs, $size_human dry${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Chrome old versions ${GREEN}(${cleaned_count} dirs, $size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Chrome old versions${NC}, ${GREEN}${cleaned_count} dirs, $size_human${NC}"
fi fi
((files_cleaned += cleaned_count)) ((files_cleaned += cleaned_count))
((total_size_cleaned += total_size)) ((total_size_cleaned += total_size))
@@ -183,9 +183,9 @@ clean_edge_old_versions() {
local size_human local size_human
size_human=$(bytes_to_human "$((total_size * 1024))") size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Edge old versions ${YELLOW}(${cleaned_count} dirs, $size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Edge old versions${NC}, ${YELLOW}${cleaned_count} dirs, $size_human dry${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Edge old versions ${GREEN}(${cleaned_count} dirs, $size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Edge old versions${NC}, ${GREEN}${cleaned_count} dirs, $size_human${NC}"
fi fi
((files_cleaned += cleaned_count)) ((files_cleaned += cleaned_count))
((total_size_cleaned += total_size)) ((total_size_cleaned += total_size))
@@ -245,9 +245,9 @@ clean_edge_updater_old_versions() {
local size_human local size_human
size_human=$(bytes_to_human "$((total_size * 1024))") size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Edge updater old versions ${YELLOW}(${cleaned_count} dirs, $size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Edge updater old versions${NC}, ${YELLOW}${cleaned_count} dirs, $size_human dry${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Edge updater old versions ${GREEN}(${cleaned_count} dirs, $size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Edge updater old versions${NC}, ${GREEN}${cleaned_count} dirs, $size_human${NC}"
fi fi
((files_cleaned += cleaned_count)) ((files_cleaned += cleaned_count))
((total_size_cleaned += total_size)) ((total_size_cleaned += total_size))
@@ -285,12 +285,12 @@ scan_external_volumes() {
local network_count=${#network_volumes[@]} local network_count=${#network_volumes[@]}
if [[ $volume_count -eq 0 ]]; then if [[ $volume_count -eq 0 ]]; then
if [[ $network_count -gt 0 ]]; then if [[ $network_count -gt 0 ]]; then
echo -e " ${GRAY}${ICON_LIST}${NC} External volumes (${network_count} network volume(s) skipped)" echo -e " ${GRAY}${ICON_LIST}${NC} External volumes, ${network_count} network volumes skipped"
note_activity note_activity
fi fi
return 0 return 0
fi fi
start_section_spinner "Scanning $volume_count external volume(s)..." start_section_spinner "Scanning $volume_count external volumes..."
for volume in "${candidate_volumes[@]}"; do for volume in "${candidate_volumes[@]}"; do
[[ -d "$volume" && -r "$volume" ]] || continue [[ -d "$volume" && -r "$volume" ]] || continue
local volume_trash="$volume/.Trashes" local volume_trash="$volume/.Trashes"
@@ -300,7 +300,7 @@ scan_external_volumes() {
done < <(command find "$volume_trash" -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true) done < <(command find "$volume_trash" -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true)
fi fi
if [[ "$PROTECT_FINDER_METADATA" != "true" ]]; then if [[ "$PROTECT_FINDER_METADATA" != "true" ]]; then
clean_ds_store_tree "$volume" "$(basename "$volume") volume (.DS_Store)" clean_ds_store_tree "$volume" "$(basename "$volume") volume, .DS_Store"
fi fi
done done
stop_section_spinner stop_section_spinner
@@ -310,7 +310,7 @@ clean_finder_metadata() {
if [[ "$PROTECT_FINDER_METADATA" == "true" ]]; then if [[ "$PROTECT_FINDER_METADATA" == "true" ]]; then
return return
fi fi
clean_ds_store_tree "$HOME" "Home directory (.DS_Store)" clean_ds_store_tree "$HOME" "Home directory, .DS_Store"
} }
# macOS system caches and user-level leftovers. # macOS system caches and user-level leftovers.
clean_macos_system_caches() { clean_macos_system_caches() {
@@ -389,7 +389,7 @@ clean_mail_downloads() {
done done
if [[ $count -gt 0 ]]; then if [[ $count -gt 0 ]]; then
local cleaned_mb=$(echo "$cleaned_kb" | awk '{printf "%.1f", $1/1024}' || echo "0.0") local cleaned_mb=$(echo "$cleaned_kb" | awk '{printf "%.1f", $1/1024}' || echo "0.0")
echo " ${GREEN}${ICON_SUCCESS}${NC} Cleaned $count mail attachments (~${cleaned_mb}MB)" echo " ${GREEN}${ICON_SUCCESS}${NC} Cleaned $count mail attachments, about ${cleaned_mb}MB"
note_activity note_activity
fi fi
} }
@@ -418,9 +418,9 @@ clean_sandboxed_app_caches() {
if [[ "$found_any" == "true" ]]; then if [[ "$found_any" == "true" ]]; then
local size_human=$(bytes_to_human "$((total_size * 1024))") local size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Sandboxed app caches ${YELLOW}($size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Sandboxed app caches${NC}, ${YELLOW}$size_human dry${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Sandboxed app caches ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Sandboxed app caches${NC}, ${GREEN}$size_human${NC}"
fi fi
((files_cleaned += cleaned_count)) ((files_cleaned += cleaned_count))
((total_size_cleaned += total_size)) ((total_size_cleaned += total_size))
@@ -603,9 +603,9 @@ clean_application_support_logs() {
if [[ "$found_any" == "true" ]]; then if [[ "$found_any" == "true" ]]; then
local size_human=$(bytes_to_human "$((total_size * 1024))") local size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Application Support logs/caches ${YELLOW}($size_human dry)${NC}" echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Application Support logs/caches${NC}, ${YELLOW}$size_human dry${NC}"
else else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Application Support logs/caches ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Application Support logs/caches${NC}, ${GREEN}$size_human${NC}"
fi fi
((files_cleaned += cleaned_count)) ((files_cleaned += cleaned_count))
((total_size_cleaned += total_size)) ((total_size_cleaned += total_size))

View File

@@ -687,7 +687,7 @@ update_progress_if_needed() {
if [[ $((current_time - last_time)) -ge $interval ]]; then if [[ $((current_time - last_time)) -ge $interval ]]; then
# Update the spinner with progress # Update the spinner with progress
stop_section_spinner stop_section_spinner
start_section_spinner "Scanning items... ($completed/$total)" start_section_spinner "Scanning items... $completed/$total"
# Update the last_update_time variable # Update the last_update_time variable
eval "$last_update_var=$current_time" eval "$last_update_var=$current_time"
@@ -717,7 +717,7 @@ push_spinner_state() {
fi fi
MOLE_SPINNER_STACK+=("$current_state") MOLE_SPINNER_STACK+=("$current_state")
debug_log "Pushed spinner state: $current_state (stack depth: ${#MOLE_SPINNER_STACK[@]})" debug_log "Pushed spinner state: $current_state, stack depth: ${#MOLE_SPINNER_STACK[@]}"
} }
# Pop and restore spinner state from stack # Pop and restore spinner state from stack
@@ -730,7 +730,7 @@ pop_spinner_state() {
# Stack depth safety check # Stack depth safety check
if [[ ${#MOLE_SPINNER_STACK[@]} -gt 10 ]]; then if [[ ${#MOLE_SPINNER_STACK[@]} -gt 10 ]]; then
debug_log "Warning: Spinner stack depth excessive (${#MOLE_SPINNER_STACK[@]}), possible leak" debug_log "Warning: Spinner stack depth excessive, ${#MOLE_SPINNER_STACK[@]}, possible leak"
fi fi
local last_idx=$((${#MOLE_SPINNER_STACK[@]} - 1)) local last_idx=$((${#MOLE_SPINNER_STACK[@]} - 1))
@@ -745,7 +745,7 @@ pop_spinner_state() {
done done
MOLE_SPINNER_STACK=("${new_stack[@]}") MOLE_SPINNER_STACK=("${new_stack[@]}")
debug_log "Popped spinner state: $state (remaining depth: ${#MOLE_SPINNER_STACK[@]})" debug_log "Popped spinner state: $state, remaining depth: ${#MOLE_SPINNER_STACK[@]}"
# Restore state if needed # Restore state if needed
if [[ "$state" == running:* ]]; then if [[ "$state" == running:* ]]; then
@@ -822,7 +822,7 @@ get_terminal_info() {
local info="Terminal: ${TERM:-unknown}" local info="Terminal: ${TERM:-unknown}"
if is_ansi_supported; then if is_ansi_supported; then
info+=" (ANSI supported)" info+=", ANSI supported"
if command -v tput > /dev/null 2>&1; then if command -v tput > /dev/null 2>&1; then
local cols=$(tput cols 2> /dev/null || echo "?") local cols=$(tput cols 2> /dev/null || echo "?")
@@ -831,7 +831,7 @@ get_terminal_info() {
info+=" ${cols}x${lines}, ${colors} colors" info+=" ${cols}x${lines}, ${colors} colors"
fi fi
else else
info+=" (ANSI not supported)" info+=", ANSI not supported"
fi fi
echo "$info" echo "$info"
@@ -852,11 +852,11 @@ validate_terminal_environment() {
# Check if running in a known problematic terminal # Check if running in a known problematic terminal
case "${TERM:-}" in case "${TERM:-}" in
dumb) dumb)
log_warning "Running in 'dumb' terminal - limited functionality" log_warning "Running in 'dumb' terminal, limited functionality"
((warnings++)) ((warnings++))
;; ;;
unknown) unknown)
log_warning "Terminal type unknown - may have display issues" log_warning "Terminal type unknown, may have display issues"
((warnings++)) ((warnings++))
;; ;;
esac esac
@@ -865,7 +865,7 @@ validate_terminal_environment() {
if command -v tput > /dev/null 2>&1; then if command -v tput > /dev/null 2>&1; then
local cols=$(tput cols 2> /dev/null || echo "80") local cols=$(tput cols 2> /dev/null || echo "80")
if [[ "$cols" -lt 60 ]]; then if [[ "$cols" -lt 60 ]]; then
log_warning "Terminal width ($cols cols) is narrow - output may wrap" log_warning "Terminal width, $cols cols, is narrow, output may wrap"
((warnings++)) ((warnings++))
fi fi
fi fi

View File

@@ -81,7 +81,7 @@ update_via_homebrew() {
installed_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}') installed_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}')
[[ -z "$installed_version" ]] && installed_version=$(mo --version 2> /dev/null | awk '/Mole version/ {print $3; exit}') [[ -z "$installed_version" ]] && installed_version=$(mo --version 2> /dev/null | awk '/Mole version/ {print $3; exit}')
echo "" echo ""
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version (${installed_version:-$current_version})" echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version, ${installed_version:-$current_version}"
echo "" echo ""
elif echo "$upgrade_output" | grep -q "Error:"; then elif echo "$upgrade_output" | grep -q "Error:"; then
log_error "Homebrew upgrade failed" log_error "Homebrew upgrade failed"
@@ -93,7 +93,7 @@ update_via_homebrew() {
new_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}') new_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}')
[[ -z "$new_version" ]] && new_version=$(mo --version 2> /dev/null | awk '/Mole version/ {print $3; exit}') [[ -z "$new_version" ]] && new_version=$(mo --version 2> /dev/null | awk '/Mole version/ {print $3; exit}')
echo "" echo ""
echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-$current_version})" echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, ${new_version:-$current_version}"
echo "" echo ""
fi fi

View File

@@ -211,7 +211,7 @@ safe_remove() {
MOLE_PERMISSION_DENIED_COUNT=${MOLE_PERMISSION_DENIED_COUNT:-0} MOLE_PERMISSION_DENIED_COUNT=${MOLE_PERMISSION_DENIED_COUNT:-0}
MOLE_PERMISSION_DENIED_COUNT=$((MOLE_PERMISSION_DENIED_COUNT + 1)) MOLE_PERMISSION_DENIED_COUNT=$((MOLE_PERMISSION_DENIED_COUNT + 1))
export MOLE_PERMISSION_DENIED_COUNT export MOLE_PERMISSION_DENIED_COUNT
debug_log "Permission denied: $path (may need Full Disk Access)" debug_log "Permission denied: $path, may need Full Disk Access"
else else
[[ "$silent" != "true" ]] && log_error "Failed to remove: $path" [[ "$silent" != "true" ]] && log_error "Failed to remove: $path"
fi fi
@@ -267,20 +267,20 @@ safe_sudo_remove() {
fi fi
fi fi
debug_file_action "[DRY RUN] Would remove (sudo)" "$path" "$file_size" "$file_age" debug_file_action "[DRY RUN] Would remove, sudo" "$path" "$file_size" "$file_age"
else else
debug_log "[DRY RUN] Would remove (sudo): $path" debug_log "[DRY RUN] Would remove, sudo: $path"
fi fi
return 0 return 0
fi fi
debug_log "Removing (sudo): $path" debug_log "Removing, sudo: $path"
# Perform the deletion # Perform the deletion
if sudo rm -rf "$path" 2> /dev/null; then # SAFE: safe_sudo_remove implementation if sudo rm -rf "$path" 2> /dev/null; then # SAFE: safe_sudo_remove implementation
return 0 return 0
else else
log_error "Failed to remove (sudo): $path" log_error "Failed to remove, sudo: $path"
return 1 return 1
fi fi
} }
@@ -309,11 +309,11 @@ safe_find_delete() {
# Validate type filter # Validate type filter
if [[ "$type_filter" != "f" && "$type_filter" != "d" ]]; then if [[ "$type_filter" != "f" && "$type_filter" != "d" ]]; then
log_error "Invalid type filter: $type_filter (must be 'f' or 'd')" log_error "Invalid type filter: $type_filter, must be 'f' or 'd'"
return 1 return 1
fi fi
debug_log "Finding in $base_dir: $pattern (age: ${age_days}d, type: $type_filter)" debug_log "Finding in $base_dir: $pattern, age: ${age_days}d, type: $type_filter"
local find_args=("-maxdepth" "5" "-name" "$pattern" "-type" "$type_filter") local find_args=("-maxdepth" "5" "-name" "$pattern" "-type" "$type_filter")
if [[ "$age_days" -gt 0 ]]; then if [[ "$age_days" -gt 0 ]]; then
@@ -340,7 +340,7 @@ safe_sudo_find_delete() {
# Validate base directory (use sudo for permission-restricted dirs) # Validate base directory (use sudo for permission-restricted dirs)
if ! sudo test -d "$base_dir" 2> /dev/null; then if ! sudo test -d "$base_dir" 2> /dev/null; then
debug_log "Directory does not exist (skipping): $base_dir" debug_log "Directory does not exist, skipping: $base_dir"
return 0 return 0
fi fi
@@ -351,11 +351,11 @@ safe_sudo_find_delete() {
# Validate type filter # Validate type filter
if [[ "$type_filter" != "f" && "$type_filter" != "d" ]]; then if [[ "$type_filter" != "f" && "$type_filter" != "d" ]]; then
log_error "Invalid type filter: $type_filter (must be 'f' or 'd')" log_error "Invalid type filter: $type_filter, must be 'f' or 'd'"
return 1 return 1
fi fi
debug_log "Finding (sudo) in $base_dir: $pattern (age: ${age_days}d, type: $type_filter)" debug_log "Finding, sudo, in $base_dir: $pattern, age: ${age_days}d, type: $type_filter"
local find_args=("-maxdepth" "5" "-name" "$pattern" "-type" "$type_filter") local find_args=("-maxdepth" "5" "-name" "$pattern" "-type" "$type_filter")
if [[ "$age_days" -gt 0 ]]; then if [[ "$age_days" -gt 0 ]]; then

View File

@@ -138,10 +138,9 @@ debug_file_action() {
local file_age="${4:-}" local file_age="${4:-}"
if [[ "${MO_DEBUG:-}" == "1" ]]; then if [[ "${MO_DEBUG:-}" == "1" ]]; then
local msg=" - $file_path" local msg=" * $file_path"
[[ -n "$file_size" ]] && msg+=" ($file_size" [[ -n "$file_size" ]] && msg+=", $file_size"
[[ -n "$file_age" ]] && msg+=", ${file_age} days old" [[ -n "$file_age" ]] && msg+=", ${file_age} days old"
[[ -n "$file_size" ]] && msg+=")"
# Output to stderr # Output to stderr
echo -e "${GRAY}[DEBUG] $action: $msg${NC}" >&2 echo -e "${GRAY}[DEBUG] $action: $msg${NC}" >&2
@@ -165,10 +164,10 @@ debug_risk_level() {
esac esac
# Output to stderr with color # Output to stderr with color
echo -e "${GRAY}[DEBUG] Risk Level: ${color}${risk_level}${GRAY} ($reason)${NC}" >&2 echo -e "${GRAY}[DEBUG] Risk Level: ${color}${risk_level}${GRAY}, $reason${NC}" >&2
# Also log to file # Also log to file
echo "Risk Level: $risk_level ($reason)" >> "$DEBUG_LOG_FILE" 2> /dev/null || true echo "Risk Level: $risk_level, $reason" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
fi fi
} }
@@ -187,16 +186,16 @@ log_system_info() {
# Start block in debug log file # Start block in debug log file
{ {
echo "----------------------------------------------------------------------" echo "----------------------------------------------------------------------"
echo "Mole Debug Session - $(date '+%Y-%m-%d %H:%M:%S')" echo "Mole Debug Session, $(date '+%Y-%m-%d %H:%M:%S')"
echo "----------------------------------------------------------------------" echo "----------------------------------------------------------------------"
echo "User: $USER" echo "User: $USER"
echo "Hostname: $(hostname)" echo "Hostname: $(hostname)"
echo "Architecture: $(uname -m)" echo "Architecture: $(uname -m)"
echo "Kernel: $(uname -r)" echo "Kernel: $(uname -r)"
if command -v sw_vers > /dev/null; then if command -v sw_vers > /dev/null; then
echo "macOS: $(sw_vers -productVersion) ($(sw_vers -buildVersion))" echo "macOS: $(sw_vers -productVersion), $(sw_vers -buildVersion)"
fi fi
echo "Shell: ${SHELL:-unknown} (${TERM:-unknown})" echo "Shell: ${SHELL:-unknown}, ${TERM:-unknown}"
# Check sudo status non-interactively # Check sudo status non-interactively
if sudo -n true 2> /dev/null; then if sudo -n true 2> /dev/null; then

View File

@@ -60,7 +60,7 @@ _request_password() {
# Show hint on first attempt about Touch ID appearing again # Show hint on first attempt about Touch ID appearing again
if [[ $show_hint == true ]] && check_touchid_support; then if [[ $show_hint == true ]] && check_touchid_support; then
echo -e "${GRAY}Note: Touch ID dialog may appear once more - just cancel it${NC}" > "$tty_path" echo -e "${GRAY}Note: Touch ID dialog may appear once more, just cancel it${NC}" > "$tty_path"
show_hint=false show_hint=false
fi fi
@@ -143,7 +143,7 @@ request_sudo_access() {
fi fi
# Touch ID is available and not in clamshell mode # Touch ID is available and not in clamshell mode
echo -e "${PURPLE}${ICON_ARROW}${NC} ${prompt_msg} ${GRAY}(Touch ID or password)${NC}" echo -e "${PURPLE}${ICON_ARROW}${NC} ${prompt_msg} ${GRAY}, Touch ID or password${NC}"
# Start sudo in background so we can monitor and control it # Start sudo in background so we can monitor and control it
sudo -v < /dev/null > /dev/null 2>&1 & sudo -v < /dev/null > /dev/null 2>&1 &

View File

@@ -100,7 +100,7 @@ run_with_timeout() {
# ======================================================================== # ========================================================================
if [[ "${MO_DEBUG:-0}" == "1" ]]; then if [[ "${MO_DEBUG:-0}" == "1" ]]; then
echo "[TIMEOUT] Shell fallback (${duration}s): $*" >&2 echo "[TIMEOUT] Shell fallback, ${duration}s: $*" >&2
fi fi
# Start command in background # Start command in background

View File

@@ -58,7 +58,7 @@ manage_purge_paths() {
if [[ -d "$path" ]]; then if [[ -d "$path" ]]; then
echo -e " ${GREEN}${NC} $display_path" echo -e " ${GREEN}${NC} $display_path"
else else
echo -e " ${GRAY}${NC} $display_path ${GRAY}(not found)${NC}" echo -e " ${GRAY}${NC} $display_path${GRAY}, not found${NC}"
fi fi
done done
fi fi
@@ -76,7 +76,7 @@ manage_purge_paths() {
echo "" echo ""
if [[ $custom_count -gt 0 ]]; then if [[ $custom_count -gt 0 ]]; then
echo -e "${GRAY}Using custom config with $custom_count path(s)${NC}" echo -e "${GRAY}Using custom config with $custom_count paths${NC}"
else else
echo -e "${GRAY}Using ${#DEFAULT_PURGE_SEARCH_PATHS[@]} default paths${NC}" echo -e "${GRAY}Using ${#DEFAULT_PURGE_SEARCH_PATHS[@]} default paths${NC}"
fi fi

View File

@@ -18,12 +18,12 @@ format_brew_update_label() {
((formulas > 0)) && details+=("${formulas} formula") ((formulas > 0)) && details+=("${formulas} formula")
((casks > 0)) && details+=("${casks} cask") ((casks > 0)) && details+=("${casks} cask")
local detail_str="(${total} updates)" local detail_str=", ${total} updates"
if ((${#details[@]} > 0)); then if ((${#details[@]} > 0)); then
detail_str="($( detail_str=", $(
IFS=', ' IFS=', '
printf '%s' "${details[*]}" printf '%s' "${details[*]}"
))" )"
fi fi
printf " • Homebrew%s" "$detail_str" printf " • Homebrew%s" "$detail_str"
} }
@@ -54,7 +54,7 @@ ask_for_updates() {
if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
has_updates=true has_updates=true
update_list+=(" • App Store (${APPSTORE_UPDATE_COUNT} apps)") update_list+=(" • App Store, ${APPSTORE_UPDATE_COUNT} apps")
fi fi
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
@@ -132,10 +132,10 @@ perform_updates() {
echo -e "${GRAY}No updates to perform${NC}" echo -e "${GRAY}No updates to perform${NC}"
return 0 return 0
elif [[ $updated_count -eq $total_count ]]; then elif [[ $updated_count -eq $total_count ]]; then
echo -e "${GREEN}All updates completed (${updated_count}/${total_count})${NC}" echo -e "${GREEN}All updates completed, ${updated_count}/${total_count}${NC}"
return 0 return 0
else else
echo -e "${RED}Update failed (${updated_count}/${total_count})${NC}" echo -e "${RED}Update failed, ${updated_count}/${total_count}${NC}"
return 1 return 1
fi fi
} }

View File

@@ -148,7 +148,7 @@ CloudKit cache|$HOME/Library/Caches/CloudKit/*|system_cache
Trash|$HOME/.Trash|system_cache Trash|$HOME/.Trash|system_cache
EOF EOF
# Add FINDER_METADATA with constant reference # Add FINDER_METADATA with constant reference
echo "Finder metadata (.DS_Store)|$FINDER_METADATA_SENTINEL|system_cache" echo "Finder metadata, .DS_Store|$FINDER_METADATA_SENTINEL|system_cache"
} }
# Get all optimize items with their patterns # Get all optimize items with their patterns
@@ -284,13 +284,13 @@ manage_whitelist_categories() {
items_source=$(get_optimize_whitelist_items) items_source=$(get_optimize_whitelist_items)
active_config_file="$WHITELIST_CONFIG_OPTIMIZE" active_config_file="$WHITELIST_CONFIG_OPTIMIZE"
local display_config="${active_config_file/#$HOME/~}" local display_config="${active_config_file/#$HOME/~}"
menu_title="Whitelist Manager Select system checks to ignore menu_title="Whitelist Manager, Select system checks to ignore
${GRAY}Edit: ${display_config}${NC}" ${GRAY}Edit: ${display_config}${NC}"
else else
items_source=$(get_all_cache_items) items_source=$(get_all_cache_items)
active_config_file="$WHITELIST_CONFIG_CLEAN" active_config_file="$WHITELIST_CONFIG_CLEAN"
local display_config="${active_config_file/#$HOME/~}" local display_config="${active_config_file/#$HOME/~}"
menu_title="Whitelist Manager Select caches to protect menu_title="Whitelist Manager, Select caches to protect
${GRAY}Edit: ${display_config}${NC}" ${GRAY}Edit: ${display_config}${NC}"
fi fi
@@ -416,7 +416,7 @@ ${GRAY}Edit: ${display_config}${NC}"
if [[ ${#custom_patterns[@]} -gt 0 ]]; then if [[ ${#custom_patterns[@]} -gt 0 ]]; then
summary_lines+=("Protected ${#selected_patterns[@]} predefined + ${#custom_patterns[@]} custom patterns") summary_lines+=("Protected ${#selected_patterns[@]} predefined + ${#custom_patterns[@]} custom patterns")
else else
summary_lines+=("Protected ${total_protected} cache(s)") summary_lines+=("Protected ${total_protected} caches")
fi fi
local display_config="${active_config_file/#$HOME/~}" local display_config="${active_config_file/#$HOME/~}"
summary_lines+=("Config: ${GRAY}${display_config}${NC}") summary_lines+=("Config: ${GRAY}${display_config}${NC}")

View File

@@ -263,7 +263,7 @@ opt_sqlite_vacuum() {
fi fi
if ! command -v sqlite3 > /dev/null 2>&1; then if ! command -v sqlite3 > /dev/null 2>&1; then
echo -e " ${GRAY}-${NC} Database optimization already optimal (sqlite3 unavailable)" echo -e " ${GRAY}-${NC} Database optimization already optimal, sqlite3 unavailable"
return 0 return 0
fi fi
@@ -584,7 +584,7 @@ opt_disk_permissions_repair() {
opt_msg "User directory permissions repaired" opt_msg "User directory permissions repaired"
opt_msg "File access issues resolved" opt_msg "File access issues resolved"
else else
echo -e " ${YELLOW}!${NC} Failed to repair permissions (may not be needed)" echo -e " ${YELLOW}!${NC} Failed to repair permissions, may not be needed"
fi fi
else else
opt_msg "User directory permissions repaired" opt_msg "User directory permissions repaired"
@@ -705,7 +705,7 @@ opt_spotlight_index_optimize() {
fi fi
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
echo -e " ${BLUE}${NC} Spotlight search is slow, rebuilding index (may take 1-2 hours)" echo -e " ${BLUE}${NC} Spotlight search is slow, rebuilding index, may take 1-2 hours"
if sudo mdutil -E / > /dev/null 2>&1; then if sudo mdutil -E / > /dev/null 2>&1; then
opt_msg "Spotlight index rebuild started" opt_msg "Spotlight index rebuild started"
echo -e " ${GRAY}Indexing will continue in background${NC}" echo -e " ${GRAY}Indexing will continue in background${NC}"

View File

@@ -307,7 +307,7 @@ batch_uninstall_applications() {
local brew_tag="" local brew_tag=""
[[ "$is_brew_cask" == "true" ]] && brew_tag=" ${CYAN}[Brew]${NC}" [[ "$is_brew_cask" == "true" ]] && brew_tag=" ${CYAN}[Brew]${NC}"
echo -e "${BLUE}${ICON_CONFIRM}${NC} ${app_name}${brew_tag} ${GRAY}(${app_size_display})${NC}" echo -e "${BLUE}${ICON_CONFIRM}${NC} ${app_name}${brew_tag} ${GRAY}, ${app_size_display}${NC}"
# Show detailed file list for ALL apps (brew casks leave user data behind) # Show detailed file list for ALL apps (brew casks leave user data behind)
local related_files=$(decode_file_list "$encoded_files" "$app_name") local related_files=$(decode_file_list "$encoded_files" "$app_name")
@@ -352,7 +352,7 @@ batch_uninstall_applications() {
echo "" echo ""
local removal_note="Remove ${app_total} ${app_text}" local removal_note="Remove ${app_total} ${app_text}"
[[ -n "$size_display" ]] && removal_note+=" (${size_display})" [[ -n "$size_display" ]] && removal_note+=", ${size_display}"
if [[ ${#running_apps[@]} -gt 0 ]]; then if [[ ${#running_apps[@]} -gt 0 ]]; then
removal_note+=" ${YELLOW}[Running]${NC}" removal_note+=" ${YELLOW}[Running]${NC}"
fi fi
@@ -516,7 +516,7 @@ batch_uninstall_applications() {
# Show failure # Show failure
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
if [[ ${#app_details[@]} -gt 1 ]]; then if [[ ${#app_details[@]} -gt 1 ]]; then
echo -e "${ICON_ERROR} [$current_index/${#app_details[@]}] ${app_name} ${GRAY}($reason)${NC}" echo -e "${ICON_ERROR} [$current_index/${#app_details[@]}] ${app_name} ${GRAY}, $reason${NC}"
else else
echo -e "${ICON_ERROR} ${app_name} failed: $reason" echo -e "${ICON_ERROR} ${app_name} failed: $reason"
fi fi
@@ -592,7 +592,7 @@ batch_uninstall_applications() {
still*running*) reason_summary="is still running" ;; still*running*) reason_summary="is still running" ;;
remove*failed*) reason_summary="could not be removed" ;; remove*failed*) reason_summary="could not be removed" ;;
permission*denied*) reason_summary="permission denied" ;; permission*denied*) reason_summary="permission denied" ;;
owned*by*) reason_summary="$first_reason (try with sudo)" ;; owned*by*) reason_summary="$first_reason, try with sudo" ;;
*) reason_summary="$first_reason" ;; *) reason_summary="$first_reason" ;;
esac esac
fi fi

27
mole
View File

@@ -247,14 +247,14 @@ update_mole() {
if [[ -z "$latest" ]]; then if [[ -z "$latest" ]]; then
log_error "Unable to check for updates. Check network connection." 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} Check if you can access GitHub, https://github.com"
echo -e "${YELLOW}Tip:${NC} Try again with: ${GRAY}mo update${NC}" echo -e "${YELLOW}Tip:${NC} Try again with: ${GRAY}mo update${NC}"
exit 1 exit 1
fi fi
if [[ "$VERSION" == "$latest" && "$force_update" != "true" ]]; then if [[ "$VERSION" == "$latest" && "$force_update" != "true" ]]; then
echo "" echo ""
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version (${VERSION})" echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version, ${VERSION}"
echo "" echo ""
exit 0 exit 0
fi fi
@@ -278,7 +278,7 @@ update_mole() {
local curl_exit=$? local curl_exit=$?
if [[ -t 1 ]]; then stop_inline_spinner; fi if [[ -t 1 ]]; then stop_inline_spinner; fi
rm -f "$tmp_installer" rm -f "$tmp_installer"
log_error "Update failed (curl error: $curl_exit)" log_error "Update failed, curl error: $curl_exit"
case $curl_exit in case $curl_exit in
6) echo -e "${YELLOW}Tip:${NC} Could not resolve host. Check DNS or network connection." ;; 6) echo -e "${YELLOW}Tip:${NC} Could not resolve host. Check DNS or network connection." ;;
@@ -294,7 +294,7 @@ update_mole() {
download_error=$(wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1) || { download_error=$(wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1) || {
if [[ -t 1 ]]; then stop_inline_spinner; fi if [[ -t 1 ]]; then stop_inline_spinner; fi
rm -f "$tmp_installer" rm -f "$tmp_installer"
log_error "Update failed (wget error)" log_error "Update failed, wget error"
echo -e "${YELLOW}Tip:${NC} Check network connection and try again." echo -e "${YELLOW}Tip:${NC} Check network connection and try again."
echo -e "${YELLOW}Tip:${NC} URL: $installer_url" echo -e "${YELLOW}Tip:${NC} URL: $installer_url"
exit 1 exit 1
@@ -324,7 +324,7 @@ update_mole() {
if [[ "$requires_sudo" == "true" ]]; then if [[ "$requires_sudo" == "true" ]]; then
if ! request_sudo_access "Mole update requires admin access"; then if ! request_sudo_access "Mole update requires admin access"; then
log_error "Update aborted (admin access denied)" log_error "Update aborted, admin access denied"
rm -f "$tmp_installer" rm -f "$tmp_installer"
exit 1 exit 1
fi fi
@@ -349,14 +349,17 @@ update_mole() {
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
local new_version local new_version
new_version=$(printf '%s\n' "$output" | sed -n 's/.*(version \([^)]*\)).*/\1/p' | head -1) new_version=$(printf '%s\n' "$output" | sed -n 's/.*-> \([^[:space:]]\{1,\}\).*/\1/p' | head -1)
if [[ -z "$new_version" ]]; then
new_version=$(printf '%s\n' "$output" | sed -n 's/.*version[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*/\1/p' | head -1)
fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version=$("$mole_path" --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "") new_version=$("$mole_path" --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "")
fi fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version="$fallback_version" new_version="$fallback_version"
fi fi
printf '\n%s\n\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})" printf '\n%s\n\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, ${new_version:-unknown}"
else else
printf '\n' printf '\n'
fi fi
@@ -484,15 +487,15 @@ remove_mole() {
exit 0 exit 0
fi fi
echo -e "${YELLOW}Remove Mole${NC} - will delete the following:" echo -e "${YELLOW}Remove Mole${NC}, will delete the following:"
if [[ "$is_homebrew" == "true" ]]; then if [[ "$is_homebrew" == "true" ]]; then
echo " - Mole via Homebrew" echo " * Mole via Homebrew"
fi fi
for install in ${manual_installs[@]+"${manual_installs[@]}"} ${alias_installs[@]+"${alias_installs[@]}"}; do for install in ${manual_installs[@]+"${manual_installs[@]}"} ${alias_installs[@]+"${alias_installs[@]}"}; do
echo " - $install" echo " * $install"
done done
echo " - ~/.config/mole" echo " * ~/.config/mole"
echo " - ~/.cache/mole" echo " * ~/.cache/mole"
echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to confirm, ${GRAY}ESC${NC} to cancel: " echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to confirm, ${GRAY}ESC${NC} to cancel: "
IFS= read -r -s -n1 key || key="" IFS= read -r -s -n1 key || key=""

View File

@@ -14,7 +14,7 @@ usage() {
Usage: ./scripts/check.sh [--format|--no-format] Usage: ./scripts/check.sh [--format|--no-format]
Options: Options:
--format Apply formatting fixes only (shfmt, gofmt) --format Apply formatting fixes only, shfmt, gofmt
--no-format Skip formatting and run checks only --no-format Skip formatting and run checks only
--help Show this help --help Show this help
EOF EOF
@@ -55,7 +55,7 @@ readonly ICON_ERROR="☻"
readonly ICON_WARNING="●" readonly ICON_WARNING="●"
readonly ICON_LIST="•" readonly ICON_LIST="•"
echo -e "${BLUE}=== Mole Check (${MODE}) ===${NC}\n" echo -e "${BLUE}=== Mole Check, ${MODE} ===${NC}\n"
SHELL_FILES=$(find . -type f \( -name "*.sh" -o -name "mole" \) \ SHELL_FILES=$(find . -type f \( -name "*.sh" -o -name "mole" \) \
-not -path "./.git/*" \ -not -path "./.git/*" \
@@ -75,11 +75,11 @@ if [[ "$MODE" == "format" ]]; then
fi fi
if command -v goimports > /dev/null 2>&1; then if command -v goimports > /dev/null 2>&1; then
echo -e "${YELLOW}Formatting Go code (goimports)...${NC}" echo -e "${YELLOW}Formatting Go code, goimports...${NC}"
goimports -w -local github.com/tw93/Mole ./cmd goimports -w -local github.com/tw93/Mole ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting complete${NC}\n" echo -e "${GREEN}${ICON_SUCCESS} Go formatting complete${NC}\n"
elif command -v go > /dev/null 2>&1; then elif command -v go > /dev/null 2>&1; then
echo -e "${YELLOW}Formatting Go code (gofmt)...${NC}" echo -e "${YELLOW}Formatting Go code, gofmt...${NC}"
gofmt -w ./cmd gofmt -w ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting complete${NC}\n" echo -e "${GREEN}${ICON_SUCCESS} Go formatting complete${NC}\n"
else else
@@ -100,11 +100,11 @@ if [[ "$MODE" != "check" ]]; then
fi fi
if command -v goimports > /dev/null 2>&1; then if command -v goimports > /dev/null 2>&1; then
echo -e "${YELLOW}2. Formatting Go code (goimports)...${NC}" echo -e "${YELLOW}2. Formatting Go code, goimports...${NC}"
goimports -w -local github.com/tw93/Mole ./cmd goimports -w -local github.com/tw93/Mole ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting applied${NC}\n" echo -e "${GREEN}${ICON_SUCCESS} Go formatting applied${NC}\n"
elif command -v go > /dev/null 2>&1; then elif command -v go > /dev/null 2>&1; then
echo -e "${YELLOW}2. Formatting Go code (gofmt)...${NC}" echo -e "${YELLOW}2. Formatting Go code, gofmt...${NC}"
gofmt -w ./cmd gofmt -w ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting applied${NC}\n" echo -e "${GREEN}${ICON_SUCCESS} Go formatting applied${NC}\n"
fi fi
@@ -148,18 +148,18 @@ fi
echo -e "${YELLOW}5. Running syntax check...${NC}" echo -e "${YELLOW}5. Running syntax check...${NC}"
if ! bash -n mole; then if ! bash -n mole; then
echo -e "${RED}${ICON_ERROR} Syntax check failed (mole)${NC}\n" echo -e "${RED}${ICON_ERROR} Syntax check failed, mole${NC}\n"
exit 1 exit 1
fi fi
for script in bin/*.sh; do for script in bin/*.sh; do
if ! bash -n "$script"; then if ! bash -n "$script"; then
echo -e "${RED}${ICON_ERROR} Syntax check failed ($script)${NC}\n" echo -e "${RED}${ICON_ERROR} Syntax check failed, $script${NC}\n"
exit 1 exit 1
fi fi
done done
find lib -name "*.sh" | while read -r script; do find lib -name "*.sh" | while read -r script; do
if ! bash -n "$script"; then if ! bash -n "$script"; then
echo -e "${RED}${ICON_ERROR} Syntax check failed ($script)${NC}\n" echo -e "${RED}${ICON_ERROR} Syntax check failed, $script${NC}\n"
exit 1 exit 1
fi fi
done done

View File

@@ -392,7 +392,7 @@ ${command}
</dict> </dict>
</plist> </plist>
EOF EOF
log_success "Workflow ready: ${name} (keyword: ${keyword})" log_success "Workflow ready: ${name}, keyword: ${keyword}"
done done
log_step "Open Alfred preferences → Workflows if you need to adjust keywords." log_step "Open Alfred preferences → Workflows if you need to adjust keywords."
@@ -413,11 +413,11 @@ main() {
echo "" echo ""
log_success "Done! Raycast and Alfred are ready with 5 commands:" log_success "Done! Raycast and Alfred are ready with 5 commands:"
echo " • clean - Deep system cleanup" echo " • clean, Deep system cleanup"
echo " • uninstall - Remove applications" echo " • uninstall, Remove applications"
echo " • optimize - System health & tuning" echo " • optimize, System health & tuning"
echo " • analyze - Disk space explorer" echo " • analyze, Disk space explorer"
echo " • status - Live system monitor" echo " • status, Live system monitor"
echo "" echo ""
} }

View File

@@ -183,7 +183,7 @@ echo ""
echo "6. Testing installation..." echo "6. Testing installation..."
# Skip if Homebrew mole is installed (install.sh will refuse to overwrite) # Skip if Homebrew mole is installed (install.sh will refuse to overwrite)
if brew list mole &> /dev/null; then if brew list mole &> /dev/null; then
printf "${GREEN}${ICON_SUCCESS} Installation test skipped (Homebrew)${NC}\n" printf "${GREEN}${ICON_SUCCESS} Installation test skipped, Homebrew${NC}\n"
elif ./install.sh --prefix /tmp/mole-test > /dev/null 2>&1; then elif ./install.sh --prefix /tmp/mole-test > /dev/null 2>&1; then
if [ -f /tmp/mole-test/mole ]; then if [ -f /tmp/mole-test/mole ]; then
printf "${GREEN}${ICON_SUCCESS} Installation test passed${NC}\n" printf "${GREEN}${ICON_SUCCESS} Installation test passed${NC}\n"
@@ -203,5 +203,5 @@ if [[ $FAILED -eq 0 ]]; then
printf "${GREEN}${ICON_SUCCESS} All tests passed!${NC}\n" printf "${GREEN}${ICON_SUCCESS} All tests passed!${NC}\n"
exit 0 exit 0
fi fi
printf "${RED}${ICON_ERROR} $FAILED test(s) failed!${NC}\n" printf "${RED}${ICON_ERROR} $FAILED tests failed!${NC}\n"
exit 1 exit 1

View File

@@ -78,8 +78,8 @@ ask_for_updates
EOF EOF
[ "$status" -eq 1 ] # ESC cancels [ "$status" -eq 1 ] # ESC cancels
[[ "$output" == *"Homebrew (5 updates)"* ]] [[ "$output" == *"Homebrew, 3 formula, 2 cask"* ]]
[[ "$output" == *"App Store (1 apps)"* ]] [[ "$output" == *"App Store, 1 apps"* ]]
[[ "$output" == *"macOS system"* ]] [[ "$output" == *"macOS system"* ]]
[[ "$output" == *"Mole"* ]] [[ "$output" == *"Mole"* ]]
} }
@@ -253,24 +253,27 @@ process_install_output() {
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
local new_version local new_version
new_version=$(printf '%s\n' "$output" | sed -n 's/.*(version \([^)]*\)).*/\1/p' | head -1) new_version=$(printf '%s\n' "$output" | sed -n 's/.*-> \([^[:space:]]\{1,\}\).*/\1/p' | head -1)
if [[ -z "$new_version" ]]; then
new_version=$(printf '%s\n' "$output" | sed -n 's/.*version[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*/\1/p' | head -1)
fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "") new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "")
fi fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version="$fallback_version" new_version="$fallback_version"
fi fi
printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})" printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, ${new_version:-unknown}"
fi fi
} }
output="Installing Mole... output="Installing Mole...
◎ Mole installed successfully (version 1.23.1)" ◎ Mole installed successfully, version 1.23.1"
process_install_output "$output" "1.23.0" process_install_output "$output" "1.23.0"
EOF EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == *"Updated to latest version (1.23.1)"* ]] [[ "$output" == *"Updated to latest version, 1.23.1"* ]]
[[ "$output" != *"1.23.0"* ]] [[ "$output" != *"1.23.0"* ]]
} }
@@ -293,14 +296,17 @@ process_install_output() {
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
local new_version local new_version
new_version=$(printf '%s\n' "$output" | sed -n 's/.*(version \([^)]*\)).*/\1/p' | head -1) new_version=$(printf '%s\n' "$output" | sed -n 's/.*-> \([^[:space:]]\{1,\}\).*/\1/p' | head -1)
if [[ -z "$new_version" ]]; then
new_version=$(printf '%s\n' "$output" | sed -n 's/.*version[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*/\1/p' | head -1)
fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "") new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "")
fi fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version="$fallback_version" new_version="$fallback_version"
fi fi
printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})" printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, ${new_version:-unknown}"
fi fi
} }
@@ -311,7 +317,7 @@ EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == *"Installation completed"* ]] [[ "$output" == *"Installation completed"* ]]
[[ "$output" == *"Updated to latest version (1.23.1)"* ]] [[ "$output" == *"Updated to latest version, 1.23.1"* ]]
} }
@test "process_install_output handles empty output with fallback version" { @test "process_install_output handles empty output with fallback version" {
@@ -333,14 +339,17 @@ process_install_output() {
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
local new_version local new_version
new_version=$(printf '%s\n' "$output" | sed -n 's/.*(version \([^)]*\)).*/\1/p' | head -1) new_version=$(printf '%s\n' "$output" | sed -n 's/.*-> \([^[:space:]]\{1,\}\).*/\1/p' | head -1)
if [[ -z "$new_version" ]]; then
new_version=$(printf '%s\n' "$output" | sed -n 's/.*version[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*/\1/p' | head -1)
fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "") new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "")
fi fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version="$fallback_version" new_version="$fallback_version"
fi fi
printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})" printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, ${new_version:-unknown}"
fi fi
} }
@@ -349,7 +358,7 @@ process_install_output "$output" "1.23.1"
EOF EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == *"Updated to latest version (1.23.1)"* ]] [[ "$output" == *"Updated to latest version, 1.23.1"* ]]
} }
@test "process_install_output does not extract wrong parentheses content" { @test "process_install_output does not extract wrong parentheses content" {
@@ -371,14 +380,17 @@ process_install_output() {
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
local new_version local new_version
new_version=$(printf '%s\n' "$output" | sed -n 's/.*(version \([^)]*\)).*/\1/p' | head -1) new_version=$(printf '%s\n' "$output" | sed -n 's/.*-> \([^[:space:]]\{1,\}\).*/\1/p' | head -1)
if [[ -z "$new_version" ]]; then
new_version=$(printf '%s\n' "$output" | sed -n 's/.*version[[:space:]]\{1,\}\([^[:space:]]\{1,\}\).*/\1/p' | head -1)
fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "") new_version=$(command -v mo > /dev/null 2>&1 && mo --version 2> /dev/null | awk 'NR==1 && NF {print $NF}' || echo "")
fi fi
if [[ -z "$new_version" ]]; then if [[ -z "$new_version" ]]; then
new_version="$fallback_version" new_version="$fallback_version"
fi fi
printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})" printf '\n%s\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version, ${new_version:-unknown}"
fi fi
} }
@@ -389,7 +401,7 @@ EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == *"Downloading (progress: 100%)"* ]] [[ "$output" == *"Downloading (progress: 100%)"* ]]
[[ "$output" == *"Updated to latest version (1.23.1)"* ]] [[ "$output" == *"Updated to latest version, 1.23.1"* ]]
[[ "$output" != *"progress: 100%"* ]] || [[ "$output" == *"Downloading (progress: 100%)"* ]] [[ "$output" != *"progress: 100%"* ]] || [[ "$output" == *"Downloading (progress: 100%)"* ]]
} }
@@ -418,7 +430,7 @@ curl() {
if [[ -n "$out" ]]; then if [[ -n "$out" ]]; then
cat > "$out" << 'INSTALLER' cat > "$out" << 'INSTALLER'
#!/usr/bin/env bash #!/usr/bin/env bash
echo "Mole installed successfully (version $CURRENT_VERSION)" echo "Mole installed successfully, version $CURRENT_VERSION"
INSTALLER INSTALLER
return 0 return 0
fi fi