diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..ec8bb8b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report +title: '' +labels: bug +assignees: Knuspii + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Linux Ubuntu] + - Terminal: + + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..dc6cc20 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: Knuspii + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec326ac..bdb4fe3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,13 @@ # 🚀 How Can you Contribute? ## 1. Adding New Programs/Caches The most common way to contribute is by adding support for more applications. +* Open the cc_caches.go file. * Locate the getPrograms() function * Add a new Program struct to the slice. * **Important:** Ensure you provide paths for both Windows and Linux where possible. * Use filepath.Join and environment variables (like localAppData) and maintain cross-platform compatibility. -## 2. Improving the UI/TUI +## 2. Improving the TUI I aim for a "Crunchy" retro terminal feel. Improvements to the menu navigation, spinner, or banner are welcome, provided they don't add external dependencies. ## 3. Bug Reports & Feature Requests @@ -22,7 +23,7 @@ If you find a bug or have an idea: ## 📥 Pull Request Process * Fork the repository and create your branch from main. * Test your changes! Run a dry-run (-d) to ensure paths are detected correctly. -* Commit with descriptive messages (e.g., "Add Spotify cache support"). +* Commit with descriptive messages (e.g., "Add: Spotify cache support"). * Open a PR and describe what your changes do and why they are necessary. ## ⚖️ License diff --git a/README.md b/README.md index bb3860d..45275c3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@

- - Go Report Card - Build - GitHub Stars -
- - Platform + + Build + + + Release + + + Downloads + + + Stars +

@@ -24,17 +29,21 @@ ### Supported Software List: | Category | Software / Path | | :--- | :--- | -| **System** | System Logs, System Temp Folders, Thumbnail Cache, Shader Cache | -| **Browsers** | Firefox, Google Chrome, Microsoft Edge, Brave, Opera | -| **Development** | Visual Studio Code, Slack, JetBrains IDE, Go, Pip, NPM, Yarn, Cargo, NuGet, Gradle | -| **Gaming** | Steam, Epic Games(Heroic) | -| **Apps** | Discord, Spotify, Thunderbird, Telegram | +| **System:** | System Logs, System Temp, Thumbnail, Shell History, Package Managers, Shader Cache | +| **Browsers:** | Firefox, Google Chrome, Microsoft Edge, Brave, Opera | +| **Development:** | Visual Studio Code, Slack, JetBrains IDE, Go, Pip, NPM, Yarn, Cargo, NuGet, Gradle | +| **Gaming:** | Steam, Epic Games(Heroic) | +| **Apps:** | Discord, Spotify, Thunderbird, Telegram | **Flatpak is supported** \ -**Snap is not supported** +**Snap is NOT supported** + +--- ## 📥 How to Install & Download ![Download](https://img.shields.io/github/downloads/knuspii/crunchycleaner/total?color=green) -Paste the corresponding command into your terminal and restart it afterwards. \ +**Download the latest binary and just run/open it: [[Download here]](https://github.com/knuspii/crunchycleaner/releases)** \ +Or paste the corresponding command into your terminal and restart it afterwards to fully install it. \ +\ For Linux (using sudo): ``` curl -L https://github.com/Knuspii/CrunchyCleaner/releases/latest/download/crunchycleaner -o cc && sudo install -m 755 cc /usr/local/bin/crunchycleaner && rm cc @@ -47,10 +56,7 @@ For Go (using go install): ``` go install github.com/knuspii/crunchycleaner/v2@latest ``` -After that you can just type `crunchycleaner -v` into your terminal to verify it's installed. \ - -Or download the binary and just run/open it: -[[Download here]](https://github.com/knuspii/crunchycleaner/releases) +After that you can just type `crunchycleaner -v` into your terminal to verify it's installed. --- @@ -58,19 +64,16 @@ Or download the binary and just run/open it: ``` -a Automate cleaning (select all and start immediately) -d Simulation mode without deleting files (for testing) - -t Skip terminal resizing and environment initialization -v Display version information ``` -If you only use a terminal, you might want to use the `-t` flag. - --- > [!WARNING] > You use this tool at your own risk! > [!NOTE] -> AI was used for this project in some parts. +> AI was used for this project in some parts. BUT THIS IS NOT VIBECODED! ## Q&A **Q:** Will this break my system? \ @@ -84,8 +87,11 @@ If you only use a terminal, you might want to use the `-t` flag. ## Other Downloads CrunchyCleaner is also available on SourceForge \ +\ [![Download CrunchyCleaner](https://a.fsdn.com/con/app/sf-download-button)](https://sourceforge.net/projects/crunchycleaner/files/latest/download) -[![Download CrunchyCleaner](https://img.shields.io/sourceforge/dt/crunchycleaner.svg)](https://sourceforge.net/projects/crunchycleaner/files/latest/download) +[![Total Downloads](https://img.shields.io/sourceforge/dt/crunchycleaner.svg)](https://sourceforge.net/projects/crunchycleaner/files/latest/download) + +--- ### 🎖️ Featured on [![Awesome TUIs](https://img.shields.io/badge/Awesome-TUIs-orange?style=flat-square)](https://github.com/rothgar/awesome-tuis) diff --git a/assets/github.png b/assets/github.png index e76c730..c4d25ac 100644 Binary files a/assets/github.png and b/assets/github.png differ diff --git a/assets/github.xcf b/assets/github.xcf index a4850da..5ce7590 100644 Binary files a/assets/github.xcf and b/assets/github.xcf differ diff --git a/assets/logo.png b/assets/logo.png index 701230b..bf84a84 100644 Binary files a/assets/logo.png and b/assets/logo.png differ diff --git a/assets/logo.xcf b/assets/logo.xcf index 8e04ebb..568b75e 100644 Binary files a/assets/logo.xcf and b/assets/logo.xcf differ diff --git a/cc_caches.go b/cc_caches.go new file mode 100644 index 0000000..dd2fbb1 --- /dev/null +++ b/cc_caches.go @@ -0,0 +1,224 @@ +// ################################################################## +// CrunchyCleaner +// Here are all paths to the specific caches. +// ################################################################## + +package main + +import ( + "os" + "path/filepath" + "runtime" +) + +func getPrograms() []Program { + if runtime.GOOS == "windows" { + // Windows + home, _ := os.UserHomeDir() + appData := os.Getenv("APPDATA") + localAppData := os.Getenv("LOCALAPPDATA") + programFilesX86 := os.Getenv("ProgramFiles(x86)") + programFiles := os.Getenv("ProgramFiles") + programData := os.Getenv("ProgramData") + winDir := os.Getenv("WINDIR") + return []Program{ + {"System Logs (Admin)", []string{ + filepath.Join(winDir, "Panther"), + filepath.Join(winDir, "Logs"), + }, false}, + {"Font Cache (Admin)", []string{filepath.Join(winDir, "ServiceProfiles/LocalService/AppData/Local/FontCache")}, false}, + {"System Temp Folders (Admin)", []string{filepath.Join(winDir, "Temp")}, false}, + {"Update Logs (Admin)", []string{filepath.Join(winDir, "SoftwareDistribution/Download")}, false}, + {"User Temp Folder", []string{filepath.Join(localAppData, "Temp")}, false}, + {"Thumbnail Cache", []string{filepath.Join(localAppData, "Microsoft/Windows/Explorer")}, false}, + {"Powershell History", []string{filepath.Join(appData, "Microsoft/Windows/PowerShell/PSReadLine")}, false}, + {"Package Manager Caches", []string{ + filepath.Join(home, "scoop/apps/scoop/current/cache"), + filepath.Join(programData, "chocolatey/cache"), + }, false}, + {"Firefox Cache", []string{ + filepath.Join(localAppData, "Mozilla/Firefox/Profiles/*/cache2"), + filepath.Join(localAppData, "Mozilla/Firefox/Profiles/*/jumpListCache"), + filepath.Join(appData, "Mozilla/Firefox/Profiles/*/shader-cache"), + }, false}, + {"Chrome Cache", []string{ + filepath.Join(localAppData, "Google/Chrome/User Data/Default/Cache"), + filepath.Join(localAppData, "Google/Chrome/User Data/Default/Code Cache"), + filepath.Join(localAppData, "Google/Chrome/User Data/*/Cache"), + filepath.Join(localAppData, "Google/Chrome/User Data/Default/Media Cache"), + }, false}, + {"Edge Cache", []string{ + filepath.Join(localAppData, "Microsoft/Edge/User Data/Default/Cache"), + filepath.Join(localAppData, "Microsoft/Edge/User Data/*/Cache"), + filepath.Join(localAppData, "Microsoft/Edge/User Data/Default/Media Cache"), + }, false}, + {"Brave Cache", []string{ + filepath.Join(localAppData, "BraveSoftware/Brave-Browser/User Data/Default/Cache"), + filepath.Join(localAppData, "BraveSoftware/Brave-Browser/User Data/*/Cache"), + filepath.Join(localAppData, "BraveSoftware/Brave-Browser/User Data/Default/Media Cache"), + }, false}, + {"Opera Cache", []string{ + filepath.Join(localAppData, "Opera Software/Opera Stable/Cache"), + filepath.Join(localAppData, "Opera Software/Opera Stable/Code Cache"), + }, false}, + {"Thunderbird Cache", []string{ + filepath.Join(localAppData, "Thunderbird/Profiles/*/cache2"), + }, false}, + {"Steam Cache", []string{ + filepath.Join(programFilesX86, "Steam/appcache"), + filepath.Join(programFiles, "Steam/appcache"), + filepath.Join(localAppData, "Steam/htmlcache"), + }, false}, + {"Epic Games Cache", []string{filepath.Join(localAppData, "EpicGamesLauncher/Saved/webcache")}, false}, + {"Discord Cache", []string{ + filepath.Join(appData, "discord/Cache"), + filepath.Join(appData, "discord/Code Cache"), + filepath.Join(appData, "discord/GPUCache"), + }, false}, + {"Telegram Cache", []string{filepath.Join(appData, "Telegram Desktop/tdata/user_data/cache")}, false}, + {"Spotify Cache", []string{filepath.Join(localAppData, "Spotify/Storage")}, false}, + {"VS Code Cache", []string{ + filepath.Join(appData, "Code/Cache"), + filepath.Join(appData, "Code/CachedData"), + filepath.Join(appData, "Code/CachedExtensionVSIXs"), + filepath.Join(appData, "Code/User/workspaceStorage"), + filepath.Join(appData, "Code/GPUCache"), + }, false}, + {"JetBrains IDE Cache", []string{filepath.Join(localAppData, "JetBrains/*/system/caches")}, false}, + {"Slack Cache", []string{ + filepath.Join(appData, "Slack/Cache"), + filepath.Join(appData, "Slack/Code Cache"), + filepath.Join(appData, "Slack/GPUCache"), + }, false}, + {"Shader Cache", []string{ + filepath.Join(localAppData, "D3DSCache"), + filepath.Join(localAppData, "NVIDIA/GLCache"), + filepath.Join(localAppData, "NVIDIA/PerDriverVersion/DXCache"), + }, false}, + {"Go Build Cache", []string{filepath.Join(localAppData, "go-build")}, false}, + {"Pip Cache", []string{filepath.Join(localAppData, "pip/Cache")}, false}, + {"NPM Cache", []string{filepath.Join(appData, "npm-cache/_cacache")}, false}, + {"Yarn Cache", []string{ + filepath.Join(localAppData, "Yarn/Cache"), + filepath.Join(appData, "Yarn/Cache"), + }, false}, + {"Cargo Cache", []string{ + filepath.Join(home, ".cargo/registry/cache"), + filepath.Join(home, ".cargo/git/db"), + }, false}, + {"NuGet Cache", []string{filepath.Join(home, ".nuget/packages")}, false}, + {"Gradle Cache", []string{filepath.Join(home, ".gradle/caches")}, false}, + } + } else { + // Linux + home, _ := os.UserHomeDir() + cache := ".cache/" + flatpak := ".var/app/" + return []Program{ + {"System Logs (Root)", []string{"/var/log/"}, false}, + {"System Temp Folders (Root)", []string{"/tmp"}, false}, + {"Package Manager Caches (Root)", []string{ + "/var/cache/apt/archives/*.deb", + "/var/cache/pacman/pkg/*", + "/var/cache/dnf/*", + "/var/cache/snapd/*", + }, false}, + {"Thumbnail Cache", []string{filepath.Join(home, cache, "thumbnails")}, false}, + {"Shell History", []string{filepath.Join(home, "*_history")}, false}, + {"Firefox Cache", []string{ + filepath.Join(home, cache, "mozilla/firefox/*/cache2"), + filepath.Join(home, flatpak, "org.mozilla.firefox/cache/mozilla/firefox/*/cache2"), + }, false}, + {"Chromium Cache", []string{ + filepath.Join(home, cache, "chromium/*/Cache"), + filepath.Join(home, cache, "chromium/*/Code Cache"), + filepath.Join(home, flatpak, "com.google.Chrome/cache/chromium/*/Cache"), + filepath.Join(home, flatpak, "com.google.Chrome/cache/chromium/*/Code Cache"), + }, false}, + {"Edge Cache", []string{ + filepath.Join(home, cache, "microsoft-edge/*/Cache"), + filepath.Join(home, cache, "microsoft-edge/*/Code Cache"), + filepath.Join(home, flatpak, "com.microsoft.Edge/cache/microsoft-edge/*/Cache"), + filepath.Join(home, flatpak, "com.microsoft.Edge/cache/microsoft-edge/*/Code Cache"), + }, false}, + {"Brave Cache", []string{ + filepath.Join(home, cache, "BraveSoftware/Brave-Browser/*/Cache"), + filepath.Join(home, cache, "BraveSoftware/Brave-Browser/*/Code Cache"), + filepath.Join(home, flatpak, "com.brave.Browser/cache/Brave-Browser/*/Cache"), + filepath.Join(home, flatpak, "com.brave.Browser/cache/Brave-Browser/*/Code Cache"), + }, false}, + {"Opera Cache", []string{ + filepath.Join(home, cache, "opera/Cache"), + filepath.Join(home, ".config/opera/Cache"), + filepath.Join(home, flatpak, "com.opera.Opera/cache/opera/Cache"), + filepath.Join(home, flatpak, "com.opera.Opera/config/opera/Cache"), + }, false}, + {"Thunderbird Cache", []string{ + filepath.Join(home, cache, "thunderbird/*/cache2"), + filepath.Join(home, flatpak, "org.mozilla.Thunderbird/cache/mozilla/Thunderbird/*/cache2"), + }, false}, + {"Steam Cache", []string{ + filepath.Join(home, ".steam/steam/appcache"), + filepath.Join(home, ".local/share/Steam/appcache"), + filepath.Join(home, ".local/share/Steam/config/htmlcache"), + filepath.Join(home, flatpak, "com.valvesoftware.Steam/steam/steam/appcache"), + filepath.Join(home, flatpak, "com.valvesoftware.Steam/.local/share/Steam/appcache"), + filepath.Join(home, flatpak, "com.valvesoftware.Steam/.local/share/Steam/config/htmlcache"), + }, false}, + {"Epic Games (Heroic/Lutris) Cache", []string{ + filepath.Join(home, ".config/heroic/WebCache"), + filepath.Join(home, ".local/share/lutris/runtime"), + filepath.Join(home, flatpak, "com.heroicgameslauncher.hgl/config/heroic/WebCache"), + filepath.Join(home, flatpak, "com.heroicgameslauncher.hgl/.local/share/lutris/runtime"), + }, false}, + {"Discord Cache", []string{ + filepath.Join(home, ".config/discord/Cache"), + filepath.Join(home, ".config/discord/Code Cache"), + filepath.Join(home, ".config/discord/GPUCache"), + filepath.Join(home, flatpak, "com.discordapp.Discord/config/discord/Cache"), + filepath.Join(home, flatpak, "com.discordapp.Discord/config/discord/Code Cache"), + filepath.Join(home, flatpak, "com.discordapp.Discord/config/discord/GPUCache"), + }, false}, + {"Telegram Cache", []string{filepath.Join( + home, ".local/share/TelegramDesktop/tdata/user_data/cache"), + filepath.Join(home, flatpak, "org.telegram.desktop/data/TelegramDesktop/tdata/user_data/cache"), + }, false}, + {"Spotify Cache", []string{ + filepath.Join(home, cache, "spotify"), + filepath.Join(home, flatpak, "com.spotify.Client/cache/spotify"), + }, false}, + {"VS Code Cache", []string{ + filepath.Join(home, ".config/Code/Cache"), + filepath.Join(home, ".config/Code/Code Cache"), + filepath.Join(home, ".config/Code/CachedData"), + filepath.Join(home, ".config/Code/GPUCache"), + filepath.Join(home, ".config/Code/User/workspaceStorage"), + filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/Cache"), + filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/Code Cache"), + filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/CachedData"), + filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/GPUCache"), + filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/User/workspaceStorage"), + }, false}, + {"JetBrains IDE Cache", []string{filepath.Join(home, cache, "JetBrains/*/caches")}, false}, + {"Slack Cache", []string{ + filepath.Join(home, ".config/Slack/Cache"), + filepath.Join(home, ".config/Slack/Code Cache"), + filepath.Join(home, ".config/Slack/GPUCache"), + filepath.Join(home, flatpak, "com.slack.Slack/config/Slack/Cache"), + filepath.Join(home, flatpak, "com.slack.Slack/config/Slack/Code Cache"), + filepath.Join(home, flatpak, "com.slack.Slack/config/Slack/GPUCache"), + }, false}, + {"Shader Cache", []string{ + filepath.Join(home, cache, "mesa_shader_cache"), + filepath.Join(home, cache, "nvidia/GLCache"), + }, false}, + {"Go Build Cache", []string{filepath.Join(home, cache, "go-build")}, false}, + {"Pip Cache", []string{filepath.Join(home, cache, "pip")}, false}, + {"NPM Cache", []string{filepath.Join(home, ".npm/_cacache")}, false}, + {"Yarn Cache", []string{filepath.Join(home, cache, "yarn")}, false}, + {"Cargo Cache", []string{filepath.Join(home, ".cargo/registry/cache")}, false}, + {"NuGet Cache", []string{filepath.Join(home, ".nuget/packages")}, false}, + {"Gradle Cache", []string{filepath.Join(home, ".gradle/caches")}, false}, + } + } +} diff --git a/cc_funcs.go b/cc_funcs.go new file mode 100644 index 0000000..eabd04a --- /dev/null +++ b/cc_funcs.go @@ -0,0 +1,126 @@ +// ################################################################## +// CrunchyCleaner +// Here are all tech functions. +// ################################################################## + +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/shirou/gopsutil/v4/disk" +) + +// getDiskMetrics uses gopsutil to fetch precise, platform-independent disk data. +func getDiskMetrics() (freeGB float64, totalStr, freeStr string) { + const GB = 1024 * 1024 * 1024 + + // On Linux, "/" is the root. On Windows, gopsutil handles the mapping to the system drive. + usage, err := disk.Usage("/") + if err != nil { + // Fallback for specific Windows environments if "/" fails + usage, err = disk.Usage("C:") + if err != nil { + return 0, "N/A", "N/A" + } + } + + freeGB = float64(usage.Free) / GB + totalStr = fmt.Sprintf("%.2f GB", float64(usage.Total)/GB) + freeStr = fmt.Sprintf("%.2f GB", freeGB) + return +} + +// formatMB converts bytes to a string representing Megabytes +func formatMB(bytes int64) string { + mb := float64(bytes) / 1024 / 1024 + return fmt.Sprintf("%.2f MB", mb) +} + +// getDirSize utilizes WalkDir (introduced in Go 1.16), which is significantly +// faster than the older filepath.Walk because it avoids unnecessary Lstat calls. +func getDirSize(path string) int64 { + var size int64 + // Resolve glob patterns (e.g., paths containing '*') + matches, _ := filepath.Glob(expandHome(path)) + + for _, m := range matches { + // WalkDir is the high-performance standard for scanning directories + _ = filepath.WalkDir(m, func(_ string, d os.DirEntry, err error) error { + if err != nil { + // If a folder is restricted (e.g. System Cache), we just skip it + return nil + } + if !d.IsDir() { + info, err := d.Info() + if err == nil { + size += info.Size() + } + } + return nil + }) + } + return size +} + +// expandHome resolves the shorthand '~/ ' to the absolute user home directory +func expandHome(path string) string { + if strings.HasPrefix(path, "~/") { + home, _ := os.UserHomeDir() + return filepath.Join(home, path[2:]) + } + return path +} + +// function to scan which programs actually exist on the disk +func scanForExisting() []Program { + allPrograms := getPrograms() + existing := []Program{} + for _, p := range allPrograms { + found := false + var totalSize int64 + for _, path := range p.Paths { + matches, _ := filepath.Glob(expandHome(path)) + if len(matches) > 0 { + found = true + totalSize += getDirSize(path) + } + } + if found { + // We format the name here so it's ready for both UI and Logs + p.Name = fmt.Sprintf("%-30s %s(%s)%s", p.Name, YELLOW, formatMB(totalSize), RC) + existing = append(existing, p) + } + } + return existing +} + +func deletePath(path string) { + info, err := os.Stat(path) + if err != nil { + return + } + + if !info.IsDir() { + _ = os.Remove(path) + return + } + + entries, err := os.ReadDir(path) + if err != nil { + fmt.Printf(CLEARLINE) + logWarn("Cannot read " + path + ": " + err.Error()) + return + } + + for _, e := range entries { + full := filepath.Join(path, e.Name()) + if err := os.RemoveAll(full); err != nil { + fmt.Printf(CLEARLINE) + logWarn("Skipped " + e.Name() + ": " + err.Error()) + } + } +} diff --git a/cc_main.go b/cc_main.go index 9011b2b..45a2bde 100644 --- a/cc_main.go +++ b/cc_main.go @@ -24,7 +24,6 @@ import ( "flag" "fmt" "os" - "os/exec" "os/signal" "os/user" "path/filepath" @@ -34,13 +33,12 @@ import ( "time" "github.com/eiannone/keyboard" - "github.com/shirou/gopsutil/v3/disk" ) // Global constants for UI and Versioning const ( - CC_VERSION = "2.6" - COLS = 62 + CC_VERSION = "2.7" + COLS = 60 LINES = 32 GOOS = runtime.GOOS CLEARLINE = "\r\033[K" @@ -52,10 +50,8 @@ const ( ) var ( - origCols, origLines int // CLI Flags Flagversion = flag.Bool("v", false, "Display version information") - Flagnoinit = flag.Bool("t", false, "Skip terminal resizing and environment initialization") Flagdryrun = flag.Bool("d", false, "Simulation mode without deleting files (for testing)") Flagauto = flag.Bool("a", false, "Automate cleaning (select all and start immediately)") ) @@ -69,48 +65,6 @@ type Program struct { // ========================= HELPER FUNCTIONS ========================= -// initApp prepares the terminal environment (Title, Resize, User Info) -func initApp() { - fmt.Printf("Initializing CrunchyCleaner %s...\n", CC_VERSION) - - // Get current terminal size - if GOOS == "windows" { - cmd := exec.Command( - "powershell", "-NoProfile", "-Command", - "$s=$Host.UI.RawUI.WindowSize; Write-Output \"$($s.Width) $($s.Height)\"", - ) - - out, _ := cmd.Output() - fmt.Sscanf(strings.TrimSpace(string(out)), "%d %d", &origCols, &origLines) - - } else { - cmd := exec.Command("sh", "-c", "stty size < /dev/tty") - - out, _ := cmd.Output() - fmt.Sscanf(strings.TrimSpace(string(out)), "%d %d", &origLines, &origCols) - } - - // Clear screen - if GOOS == "windows" { - // Windows CMD requires an external call to 'cls' - cmd := exec.Command("cls") - cmd.Stdout = os.Stdout - cmd.Run() - } else { - cmd := exec.Command("clear") - cmd.Stdout = os.Stdout - cmd.Run() - - } - // Fallback use ANSI escape sequences - fmt.Print(CLEARSCREEN) - - // Set Terminal Title via ANSI sequence - fmt.Printf("\033]0;CrunchyCleaner %s\007", CC_VERSION) - // Resize terminal - terminalresize(COLS, LINES) -} - // cc_exit provides a clean termination of the application func cc_exit() { // Close keyboard @@ -119,11 +73,6 @@ func cc_exit() { // Enable cursor fmt.Print("\033[?25h") - // Restore size - if !*Flagnoinit && !*Flagauto { - terminalresize(origCols, origLines) - } - fmt.Printf("\nExiting CrunchyCleaner...\n") os.Exit(0) } @@ -158,285 +107,6 @@ func spinner(text string, stop chan bool, ack chan bool) { } } -func terminalresize(w int, h int) { - // OS-specific Terminal Resizing - if GOOS == "windows" { - psCmd := fmt.Sprintf( - `$w=(Get-Host).UI.RawUI; - $newSize=New-Object System.Management.Automation.Host.Size(%d,%d); - $newBuffer=New-Object System.Management.Automation.Host.Size(%d,999); - $w.BufferSize=$newBuffer; - $w.WindowSize=$newSize`, - w, h, w, - ) - exec.Command("powershell", "-NoProfile", "-Command", psCmd).Run() - } - - // Generic ANSI resize fallback for modern terminals - fmt.Printf("\033[8;%d;%dt", h, w) -} - -// ========================= PROGRAMS ========================= - -func getPrograms() []Program { - if runtime.GOOS == "windows" { - // Windows - home, _ := os.UserHomeDir() - appData := os.Getenv("APPDATA") - localAppData := os.Getenv("LOCALAPPDATA") - programFilesX86 := os.Getenv("ProgramFiles(x86)") - programFiles := os.Getenv("ProgramFiles") - winDir := os.Getenv("WINDIR") - return []Program{ - {"System Logs (Admin)", []string{ - filepath.Join(winDir, "Panther"), - filepath.Join(winDir, "Logs"), - }, false}, - {"Font Cache (Admin)", []string{filepath.Join(winDir, "ServiceProfiles/LocalService/AppData/Local/FontCache")}, false}, - {"System Temp Folders (Admin)", []string{filepath.Join(winDir, "Temp")}, false}, - {"Update Logs (Admin)", []string{filepath.Join(winDir, "SoftwareDistribution/Download")}, false}, - {"User Temp Folder", []string{filepath.Join(localAppData, "Temp")}, false}, - {"Thumbnail Cache", []string{filepath.Join(localAppData, "Microsoft/Windows/Explorer")}, false}, - {"Firefox Cache", []string{ - filepath.Join(localAppData, "Mozilla/Firefox/Profiles/*/cache2"), - filepath.Join(localAppData, "Mozilla/Firefox/Profiles/*/jumpListCache"), - filepath.Join(appData, "Mozilla/Firefox/Profiles/*/shader-cache"), - }, false}, - {"Chrome Cache", []string{ - filepath.Join(localAppData, "Google/Chrome/User Data/Default/Cache"), - filepath.Join(localAppData, "Google/Chrome/User Data/Default/Code Cache"), - filepath.Join(localAppData, "Google/Chrome/User Data/*/Cache"), - filepath.Join(localAppData, "Google/Chrome/User Data/Default/Media Cache"), - }, false}, - {"Edge Cache", []string{ - filepath.Join(localAppData, "Microsoft/Edge/User Data/Default/Cache"), - filepath.Join(localAppData, "Microsoft/Edge/User Data/*/Cache"), - filepath.Join(localAppData, "Microsoft/Edge/User Data/Default/Media Cache"), - }, false}, - {"Brave Cache", []string{ - filepath.Join(localAppData, "BraveSoftware/Brave-Browser/User Data/Default/Cache"), - filepath.Join(localAppData, "BraveSoftware/Brave-Browser/User Data/*/Cache"), - filepath.Join(localAppData, "BraveSoftware/Brave-Browser/User Data/Default/Media Cache"), - }, false}, - {"Opera Cache", []string{ - filepath.Join(localAppData, "Opera Software/Opera Stable/Cache"), - filepath.Join(localAppData, "Opera Software/Opera Stable/Code Cache"), - }, false}, - {"Thunderbird Cache", []string{ - filepath.Join(localAppData, "Thunderbird/Profiles/*/cache2"), - }, false}, - {"Steam Cache", []string{ - filepath.Join(programFilesX86, "Steam/appcache"), - filepath.Join(programFiles, "Steam/appcache"), - filepath.Join(localAppData, "Steam/htmlcache"), - }, false}, - {"Epic Games Cache", []string{filepath.Join(localAppData, "EpicGamesLauncher/Saved/webcache")}, false}, - {"Discord Cache", []string{ - filepath.Join(appData, "discord/Cache"), - filepath.Join(appData, "discord/Code Cache"), - filepath.Join(appData, "discord/GPUCache"), - }, false}, - {"Telegram Cache", []string{filepath.Join(appData, "Telegram Desktop/tdata/user_data/cache")}, false}, - {"Spotify Cache", []string{filepath.Join(localAppData, "Spotify/Storage")}, false}, - {"VS Code Cache", []string{ - filepath.Join(appData, "Code/Cache"), - filepath.Join(appData, "Code/CachedData"), - filepath.Join(appData, "Code/CachedExtensionVSIXs"), - filepath.Join(appData, "Code/User/workspaceStorage"), - filepath.Join(appData, "Code/GPUCache"), - }, false}, - {"JetBrains IDE Cache", []string{filepath.Join(localAppData, "JetBrains/*/system/caches")}, false}, - {"Slack Cache", []string{ - filepath.Join(appData, "Slack/Cache"), - filepath.Join(appData, "Slack/Code Cache"), - filepath.Join(appData, "Slack/GPUCache"), - }, false}, - {"Shader Cache", []string{ - filepath.Join(localAppData, "D3DSCache"), - filepath.Join(localAppData, "NVIDIA/GLCache"), - }, false}, - {"Go Build Cache", []string{filepath.Join(localAppData, "go-build")}, false}, - {"Pip Cache", []string{filepath.Join(localAppData, "pip/Cache")}, false}, - {"NPM Cache", []string{filepath.Join(appData, "npm-cache/_cacache")}, false}, - {"Yarn Cache", []string{ - filepath.Join(localAppData, "Yarn/Cache"), - filepath.Join(appData, "Yarn/Cache"), - }, false}, - {"Cargo Cache", []string{ - filepath.Join(home, ".cargo/registry/cache"), - filepath.Join(home, ".cargo/git/db"), - }, false}, - {"NuGet Cache", []string{filepath.Join(home, ".nuget/packages")}, false}, - {"Gradle Cache", []string{filepath.Join(home, ".gradle/caches")}, false}, - } - } else { - // Linux - home, _ := os.UserHomeDir() - cache := ".cache/" - flatpak := ".var/app/" - return []Program{ - {"System Logs (Root)", []string{"/var/log/*.log"}, false}, - {"System Temp Folders (Root)", []string{"/tmp"}, false}, - {"Thumbnail Cache", []string{filepath.Join(home, cache, "thumbnails")}, false}, - {"Firefox Cache", []string{ - filepath.Join(home, cache, "mozilla/firefox/*/cache2"), - filepath.Join(home, flatpak, "org.mozilla.firefox/cache/mozilla/firefox/*/cache2"), - }, false}, - {"Chromium Cache", []string{ - filepath.Join(home, cache, "chromium/*/Cache"), - filepath.Join(home, cache, "chromium/*/Code Cache"), - filepath.Join(home, flatpak, "com.google.Chrome/cache/chromium/*/Cache"), - filepath.Join(home, flatpak, "com.google.Chrome/cache/chromium/*/Code Cache"), - }, false}, - {"Edge Cache", []string{ - filepath.Join(home, cache, "microsoft-edge/*/Cache"), - filepath.Join(home, cache, "microsoft-edge/*/Code Cache"), - filepath.Join(home, flatpak, "com.microsoft.Edge/cache/microsoft-edge/*/Cache"), - filepath.Join(home, flatpak, "com.microsoft.Edge/cache/microsoft-edge/*/Code Cache"), - }, false}, - {"Brave Cache", []string{ - filepath.Join(home, cache, "BraveSoftware/Brave-Browser/*/Cache"), - filepath.Join(home, cache, "BraveSoftware/Brave-Browser/*/Code Cache"), - filepath.Join(home, flatpak, "com.brave.Browser/cache/Brave-Browser/*/Cache"), - filepath.Join(home, flatpak, "com.brave.Browser/cache/Brave-Browser/*/Code Cache"), - }, false}, - {"Opera Cache", []string{ - filepath.Join(home, cache, "opera/Cache"), - filepath.Join(home, ".config/opera/Cache"), - filepath.Join(home, flatpak, "com.opera.Opera/cache/opera/Cache"), - filepath.Join(home, flatpak, "com.opera.Opera/config/opera/Cache"), - }, false}, - {"Thunderbird Cache", []string{ - filepath.Join(home, cache, "thunderbird/*/cache2"), - filepath.Join(home, flatpak, "org.mozilla.Thunderbird/cache/mozilla/Thunderbird/*/cache2"), - }, false}, - {"Steam Cache", []string{ - filepath.Join(home, ".steam/steam/appcache"), - filepath.Join(home, ".local/share/Steam/appcache"), - filepath.Join(home, ".local/share/Steam/config/htmlcache"), - filepath.Join(home, flatpak, "com.valvesoftware.Steam/steam/steam/appcache"), - filepath.Join(home, flatpak, "com.valvesoftware.Steam/.local/share/Steam/appcache"), - filepath.Join(home, flatpak, "com.valvesoftware.Steam/.local/share/Steam/config/htmlcache"), - }, false}, - {"Epic Games (Heroic/Lutris) Cache", []string{ - filepath.Join(home, ".config/heroic/WebCache"), - filepath.Join(home, ".local/share/lutris/runtime"), - filepath.Join(home, flatpak, "com.heroicgameslauncher.hgl/config/heroic/WebCache"), - filepath.Join(home, flatpak, "com.heroicgameslauncher.hgl/.local/share/lutris/runtime"), - }, false}, - {"Discord Cache", []string{ - filepath.Join(home, ".config/discord/Cache"), - filepath.Join(home, ".config/discord/Code Cache"), - filepath.Join(home, ".config/discord/GPUCache"), - filepath.Join(home, flatpak, "com.discordapp.Discord/config/discord/Cache"), - filepath.Join(home, flatpak, "com.discordapp.Discord/config/discord/Code Cache"), - filepath.Join(home, flatpak, "com.discordapp.Discord/config/discord/GPUCache"), - }, false}, - {"Telegram Cache", []string{filepath.Join( - home, ".local/share/TelegramDesktop/tdata/user_data/cache"), - filepath.Join(home, flatpak, "org.telegram.desktop/data/TelegramDesktop/tdata/user_data/cache"), - }, false}, - {"Spotify Cache", []string{ - filepath.Join(home, cache, "spotify"), - filepath.Join(home, flatpak, "com.spotify.Client/cache/spotify"), - }, false}, - {"VS Code Cache", []string{ - filepath.Join(home, ".config/Code/Cache"), - filepath.Join(home, ".config/Code/Code Cache"), - filepath.Join(home, ".config/Code/CachedData"), - filepath.Join(home, ".config/Code/GPUCache"), - filepath.Join(home, ".config/Code/User/workspaceStorage"), - filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/Cache"), - filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/Code Cache"), - filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/CachedData"), - filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/GPUCache"), - filepath.Join(home, flatpak, "com.visualstudio.code/config/Code/User/workspaceStorage"), - }, false}, - {"JetBrains IDE Cache", []string{filepath.Join(home, cache, "JetBrains/*/caches")}, false}, - {"Slack Cache", []string{ - filepath.Join(home, ".config/Slack/Cache"), - filepath.Join(home, ".config/Slack/Code Cache"), - filepath.Join(home, ".config/Slack/GPUCache"), - filepath.Join(home, flatpak, "com.slack.Slack/config/Slack/Cache"), - filepath.Join(home, flatpak, "com.slack.Slack/config/Slack/Code Cache"), - filepath.Join(home, flatpak, "com.slack.Slack/config/Slack/GPUCache"), - }, false}, - {"Shader Cache", []string{ - filepath.Join(home, cache, "mesa_shader_cache"), - filepath.Join(home, cache, "nvidia/GLCache"), - }, false}, - {"Go Build Cache", []string{filepath.Join(home, cache, "go-build")}, false}, - {"Pip Cache", []string{filepath.Join(home, cache, "pip")}, false}, - {"NPM Cache", []string{filepath.Join(home, ".npm/_cacache")}, false}, - {"Yarn Cache", []string{filepath.Join(home, cache, "yarn")}, false}, - {"Cargo Cache", []string{filepath.Join(home, ".cargo/registry/cache")}, false}, - {"NuGet Cache", []string{filepath.Join(home, ".nuget/packages")}, false}, - {"Gradle Cache", []string{filepath.Join(home, ".gradle/caches")}, false}, - } - } -} - -// getDiskMetrics uses gopsutil to fetch precise, platform-independent disk data. -func getDiskMetrics() (freeGB float64, totalStr, freeStr string) { - const GB = 1024 * 1024 * 1024 - - // On Linux, "/" is the root. On Windows, gopsutil handles the mapping to the system drive. - usage, err := disk.Usage("/") - if err != nil { - // Fallback for specific Windows environments if "/" fails - usage, err = disk.Usage("C:") - if err != nil { - return 0, "N/A", "N/A" - } - } - - freeGB = float64(usage.Free) / GB - totalStr = fmt.Sprintf("%.2f GB", float64(usage.Total)/GB) - freeStr = fmt.Sprintf("%.2f GB", freeGB) - return -} - -// formatMB converts bytes to a string representing Megabytes -func formatMB(bytes int64) string { - mb := float64(bytes) / 1024 / 1024 - return fmt.Sprintf("%.2f MB", mb) -} - -// getDirSize utilizes WalkDir (introduced in Go 1.16), which is significantly -// faster than the older filepath.Walk because it avoids unnecessary Lstat calls. -func getDirSize(path string) int64 { - var size int64 - // Resolve glob patterns (e.g., paths containing '*') - matches, _ := filepath.Glob(expandHome(path)) - - for _, m := range matches { - // WalkDir is the high-performance standard for scanning directories - _ = filepath.WalkDir(m, func(_ string, d os.DirEntry, err error) error { - if err != nil { - // If a folder is restricted (e.g. System Cache), we just skip it - return nil - } - if !d.IsDir() { - info, err := d.Info() - if err == nil { - size += info.Size() - } - } - return nil - }) - } - return size -} - -// expandHome resolves the shorthand '~/ ' to the absolute user home directory -func expandHome(path string) string { - if strings.HasPrefix(path, "~/") { - home, _ := os.UserHomeDir() - return filepath.Join(home, path[2:]) - } - return path -} - // ========================= MENU UI LOGIC ========================= func showBanner() { @@ -466,8 +136,8 @@ func logWarn(msg string) { fmt.Printf("%s[!] %s%s\n", YELLOW, msg, RC) } func renderMenu(existing []Program, idx int, fullRedraw bool) { if fullRedraw { showBanner() - fmt.Printf("Use ↑/↓ or W/S to navigate | [ENTER] to select | [C] to clean\n") - fmt.Printf("Folders found: [%d]\n", len(existing)) + fmt.Printf("↑/↓ or W/S to navigate | [ENTER] to select | [C] to clean\n") + fmt.Printf("Folders found: %d\n", len(existing)) } // Render each detected program entry @@ -487,29 +157,6 @@ func renderMenu(existing []Program, idx int, fullRedraw bool) { } } -// function to scan which programs actually exist on the disk -func scanForExisting() []Program { - allPrograms := getPrograms() - existing := []Program{} - for _, p := range allPrograms { - found := false - var totalSize int64 - for _, path := range p.Paths { - matches, _ := filepath.Glob(expandHome(path)) - if len(matches) > 0 { - found = true - totalSize += getDirSize(path) - } - } - if found { - // We format the name here so it's ready for both UI and Logs - p.Name = fmt.Sprintf("%-30s %s(%s)%s", p.Name, YELLOW, formatMB(totalSize), RC) - existing = append(existing, p) - } - } - return existing -} - // handleMenu manages user input for navigation and selection func handleMenu() { // Initial scan of the filesystem to find existing directories @@ -680,33 +327,6 @@ func runCleanup(programs []Program) { cc_exit() } -func deletePath(path string) { - info, err := os.Stat(path) - if err != nil { - return - } - - if !info.IsDir() { - _ = os.Remove(path) - return - } - - entries, err := os.ReadDir(path) - if err != nil { - fmt.Printf(CLEARLINE) - logWarn("Cannot read " + path + ": " + err.Error()) - return - } - - for _, e := range entries { - full := filepath.Join(path, e.Name()) - if err := os.RemoveAll(full); err != nil { - fmt.Printf(CLEARLINE) - logWarn("Skipped " + e.Name() + ": " + err.Error()) - } - } -} - func main() { flag.Parse() @@ -723,10 +343,6 @@ func main() { return } - if !*Flagnoinit && !*Flagauto { - initApp() - } - // AUTOMATION LOGIC if *Flagauto { showBanner() diff --git a/go.mod b/go.mod index 900cad5..4525eec 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,13 @@ go 1.25.0 require ( github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 - github.com/shirou/gopsutil/v3 v3.24.5 + github.com/shirou/gopsutil/v4 v4.26.6 ) require ( + github.com/ebitengine/purego v0.10.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.46.0 // indirect ) diff --git a/go.sum b/go.sum index ac0d256..76c6a9e 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,26 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= +github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= -github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs= +github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scripts/lin_build.sh b/scripts/lin_build.sh index 45dce49..d48a6a5 100644 --- a/scripts/lin_build.sh +++ b/scripts/lin_build.sh @@ -13,7 +13,7 @@ echo "Formatting Go source files..." gofmt -s -w ../. echo "Formatting done." -mkdir ../bin/ +mkdir -p ../bin/ echo "Building CrunchyCleaner for Windows amd64..." CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ../bin/crunchycleaner.exe ../.