Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e1acbac
chore: Update displayed version from v2.0 to v0.1.0 in Hero component.
Augani Jan 5, 2026
fdd060c
chore: Update Zig version to master in release workflow.
Augani Jan 5, 2026
82a8c18
chore: replace generic Zig setup action with platform-specific manual…
Augani Jan 5, 2026
1fbd967
build: Update Zig to stable 0.15.2 and simplify path setup in release…
Augani Jan 5, 2026
e2e3dba
ci: Refactor release workflow to build CLI and GUI separately, and fi…
Augani Jan 5, 2026
42c09ca
chore: Consolidate separate CLI and GUI build steps into a single `zi…
Augani Jan 5, 2026
032fb37
refactor: Simplify release workflow by removing explicit build target…
Augani Jan 5, 2026
10bf212
feat: Improve CI build performance with Zig caching and add an option…
Augani Jan 5, 2026
9ec801b
feat: resolve resource paths relative to the executable and install t…
Augani Jan 5, 2026
7d034d1
chore: remove API_KEY and GEMINI_API_KEY environment variable definit…
Augani Jan 5, 2026
87b4c98
Show status messages after deletion, update web binary sizes
Augani Jan 5, 2026
412d8c1
Fix deletion failing for directories like node_modules
Augani Jan 5, 2026
dcfdfa0
Fix button rendering and improve release packaging
Augani Jan 5, 2026
25b5a25
Fix deletion feedback and remove protected system folders from scan
Augani Jan 5, 2026
bbe7199
Fix memory leaks in batch deletion
Augani Jan 5, 2026
4d5c5b2
Fix memory leak in moveToTrash - free original_path
Augani Jan 5, 2026
441ed0b
Fix memory leak in Paths.deinit - free home and temp
Augani Jan 5, 2026
0bf2515
feat: Implement macOS ad-hoc binary signing and first-time launch ins…
Augani Jan 5, 2026
f84413d
docs: Update website content with Zig/raylib details, new features, a…
Augani Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 88 additions & 26 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,121 @@ permissions:
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: aarch64-macos
name: macos-arm64
ext: tar.gz
- os: macos-15
target: x86_64-macos
name: macos-x64
ext: tar.gz
zig_url: https://ziglang.org/download/0.15.2/zig-aarch64-macos-0.15.2.tar.xz
zig_dir: zig-aarch64-macos-0.15.2
- os: ubuntu-latest
target: x86_64-linux-gnu
name: linux-x64
ext: tar.gz
zig_url: https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz
zig_dir: zig-x86_64-linux-0.15.2
- os: windows-latest
target: x86_64-windows-gnu
name: windows-x64
ext: zip
zig_url: https://ziglang.org/download/0.15.2/zig-x86_64-windows-0.15.2.zip
zig_dir: zig-x86_64-windows-0.15.2

runs-on: ${{ matrix.os }}

timeout-minutes: 90

steps:
- uses: actions/checkout@v4

- name: Setup Zig
uses: mlugg/setup-zig@v1

- name: Cache Zig
uses: actions/cache@v4
with:
path: |
${{ matrix.zig_dir }}
key: zig-0.15.2-${{ matrix.os }}

- name: Cache Zig build
uses: actions/cache@v4
with:
version: 0.15.0

path: |
.zig-cache
~/.cache/zig
key: zig-build-${{ matrix.os }}-${{ hashFiles('build.zig', 'build.zig.zon') }}
restore-keys: |
zig-build-${{ matrix.os }}-

- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev libgl1-mesa-dev

- name: Setup Zig
shell: bash
run: |
if [ ! -d "${{ matrix.zig_dir }}" ]; then
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
curl -L -o zig.zip "${{ matrix.zig_url }}"
unzip -q zig.zip
else
curl -L "${{ matrix.zig_url }}" | tar -xJ
fi
fi
echo "$PWD/${{ matrix.zig_dir }}" >> $GITHUB_PATH

- name: Build
run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseFast

shell: bash
run: zig build -Doptimize=ReleaseFast

- name: Sign macOS binaries (ad-hoc)
if: runner.os == 'macOS'
run: |
# Ad-hoc sign the binaries for macOS
# This helps with Gatekeeper on ARM64 Macs
codesign --force --deep --sign - zig-out/bin/sweeper || true
codesign --force --deep --sign - zig-out/bin/sweeper-gui || true
# Verify signatures
codesign -v zig-out/bin/sweeper || true
codesign -v zig-out/bin/sweeper-gui || true

- name: Package (Unix)
if: matrix.os != 'windows-latest'
if: runner.os != 'Windows'
run: |
mkdir -p sweeper-${{ matrix.name }}
cp zig-out/bin/sweeper-gui sweeper-${{ matrix.name }}/ || true
cp zig-out/bin/sweeper sweeper-${{ matrix.name }}/ || true
cp -r resources sweeper-${{ matrix.name }}/ || true
cp zig-out/bin/sweeper-gui sweeper-${{ matrix.name }}/ || true
# Copy resources from build output (preferred) or project root
if [ -d "zig-out/bin/resources" ]; then
cp -r zig-out/bin/resources sweeper-${{ matrix.name }}/
else
cp -r resources sweeper-${{ matrix.name }}/
fi
cp README.md LICENSE sweeper-${{ matrix.name }}/
# Verify package contents
echo "Package contents:"
ls -la sweeper-${{ matrix.name }}/
ls -la sweeper-${{ matrix.name }}/resources/ || echo "No resources folder!"
tar -czvf sweeper-${{ matrix.name }}.${{ matrix.ext }} sweeper-${{ matrix.name }}

- name: Package (Windows)
if: matrix.os == 'windows-latest'
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path sweeper-${{ matrix.name }}
Copy-Item zig-out/bin/sweeper-gui.exe sweeper-${{ matrix.name }}/ -ErrorAction SilentlyContinue
Copy-Item zig-out/bin/sweeper.exe sweeper-${{ matrix.name }}/ -ErrorAction SilentlyContinue
Copy-Item -Recurse resources sweeper-${{ matrix.name }}/ -ErrorAction SilentlyContinue
Copy-Item zig-out/bin/sweeper-gui.exe sweeper-${{ matrix.name }}/ -ErrorAction SilentlyContinue
# Copy resources from build output (preferred) or project root
if (Test-Path "zig-out/bin/resources") {
Copy-Item -Recurse zig-out/bin/resources sweeper-${{ matrix.name }}/
} else {
Copy-Item -Recurse resources sweeper-${{ matrix.name }}/
}
Copy-Item README.md,LICENSE sweeper-${{ matrix.name }}/
# Verify package contents
Write-Host "Package contents:"
Get-ChildItem sweeper-${{ matrix.name }}/
Get-ChildItem sweeper-${{ matrix.name }}/resources/
Compress-Archive -Path sweeper-${{ matrix.name }} -DestinationPath sweeper-${{ matrix.name }}.${{ matrix.ext }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand All @@ -73,15 +135,15 @@ jobs:
release:
needs: build
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts

- name: Create Release
uses: softprops/action-gh-release@v2
with:
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,29 @@ Download from the [Releases](https://github.com/augani/sweeper/releases) page.
| Platform | Download |
|----------|----------|
| macOS (Apple Silicon) | `sweeper-macos-arm64.tar.gz` |
| macOS (Intel) | `sweeper-macos-x64.tar.gz` |
| Linux (x64) | `sweeper-linux-x64.tar.gz` |
| Windows (x64) | `sweeper-windows-x64.zip` |

#### macOS: First-time Launch

macOS may block the app because it's not from the App Store. To run Sweeper:

**Option 1: Right-click to Open**
1. Right-click (or Control-click) on `sweeper-gui`
2. Select "Open" from the menu
3. Click "Open" in the dialog

**Option 2: Remove quarantine attribute**
```bash
# After extracting, run:
xattr -cr sweeper-macos-arm64/
```

**Option 3: System Settings**
1. Go to System Settings > Privacy & Security
2. Scroll down to find "sweeper-gui was blocked"
3. Click "Open Anyway"

## Usage

### GUI Mode (Recommended)
Expand Down
110 changes: 61 additions & 49 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub fn build(b: *std.Build) void {
// Standard optimization options
const optimize = b.standardOptimizeOption(.{});

// Option to skip GUI build (useful for CI where raylib takes too long)
const build_gui = b.option(bool, "gui", "Build the GUI application (default: true)") orelse true;

// Main executable (TUI/CLI only - no raylib dependency)
const exe = b.addExecutable(.{
.name = "sweeper",
Expand Down Expand Up @@ -45,64 +48,73 @@ pub fn build(b: *std.Build) void {
const run_step = b.step("run", "Run the Sweeper CLI tool");
run_step.dependOn(&run_cmd.step);

// GUI executable with raylib
const raylib_dep = b.dependency("raylib_zig", .{
.target = target,
.optimize = optimize,
});

const gui_exe = b.addExecutable(.{
.name = "sweeper-gui",
.root_module = b.createModule(.{
.root_source_file = b.path("src/gui_main.zig"),
// GUI executable with raylib (optional)
if (build_gui) {
const raylib_dep = b.dependency("raylib_zig", .{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
});

const gui_exe = b.addExecutable(.{
.name = "sweeper-gui",
.root_module = b.createModule(.{
.root_source_file = b.path("src/gui_main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});

gui_exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
raylib_dep.module("raylib").linkLibrary(raylib_dep.artifact("raylib"));
gui_exe.linkLibrary(raylib_dep.artifact("raylib"));

// Platform-specific linking for GUI
if (target_info.os_tag) |os| {
switch (os) {
.macos => {
gui_exe.root_module.linkFramework("Cocoa", .{});
gui_exe.root_module.linkFramework("IOKit", .{});
gui_exe.root_module.linkFramework("CoreFoundation", .{});
gui_exe.root_module.linkFramework("CoreGraphics", .{});
gui_exe.root_module.linkFramework("CoreVideo", .{});
},
.windows => {
gui_exe.root_module.linkSystemLibrary("kernel32", .{});
gui_exe.root_module.linkSystemLibrary("shell32", .{});
gui_exe.root_module.linkSystemLibrary("gdi32", .{});
gui_exe.root_module.linkSystemLibrary("user32", .{});
gui_exe.root_module.linkSystemLibrary("opengl32", .{});
},
.linux => {
gui_exe.root_module.linkSystemLibrary("GL", .{});
gui_exe.root_module.linkSystemLibrary("X11", .{});
},
else => {},
}
}

gui_exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
raylib_dep.module("raylib").linkLibrary(raylib_dep.artifact("raylib"));
gui_exe.linkLibrary(raylib_dep.artifact("raylib"));
b.installArtifact(gui_exe);

// Platform-specific linking for GUI
if (target_info.os_tag) |os| {
switch (os) {
.macos => {
gui_exe.root_module.linkFramework("Cocoa", .{});
gui_exe.root_module.linkFramework("IOKit", .{});
gui_exe.root_module.linkFramework("CoreFoundation", .{});
gui_exe.root_module.linkFramework("CoreGraphics", .{});
gui_exe.root_module.linkFramework("CoreVideo", .{});
},
.windows => {
gui_exe.root_module.linkSystemLibrary("kernel32", .{});
gui_exe.root_module.linkSystemLibrary("shell32", .{});
gui_exe.root_module.linkSystemLibrary("gdi32", .{});
gui_exe.root_module.linkSystemLibrary("user32", .{});
gui_exe.root_module.linkSystemLibrary("opengl32", .{});
},
.linux => {
gui_exe.root_module.linkSystemLibrary("GL", .{});
gui_exe.root_module.linkSystemLibrary("X11", .{});
},
else => {},
}
}
// Install resources directory next to the executable
b.installDirectory(.{
.source_dir = b.path("resources"),
.install_dir = .bin,
.install_subdir = "resources",
});

b.installArtifact(gui_exe);
// Run GUI step
const run_gui_cmd = b.addRunArtifact(gui_exe);
run_gui_cmd.step.dependOn(b.getInstallStep());

// Run GUI step
const run_gui_cmd = b.addRunArtifact(gui_exe);
run_gui_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_gui_cmd.addArgs(args);
}

if (b.args) |args| {
run_gui_cmd.addArgs(args);
const run_gui_step = b.step("run-gui", "Run the desktop cleanup GUI");
run_gui_step.dependOn(&run_gui_cmd.step);
}

const run_gui_step = b.step("run-gui", "Run the desktop cleanup GUI");
run_gui_step.dependOn(&run_gui_cmd.step);

// Unit tests
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
Expand Down
10 changes: 5 additions & 5 deletions docs/web/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ const App: React.FC = () => {
Join thousands of developers who have cleared terabytes of unused data. Open source, fast, and safe.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center relative z-10">
<button className="px-8 py-4 bg-primary text-white font-bold rounded-full hover:bg-red-700 transition-colors flex items-center gap-2 group shadow-lg shadow-red-900/20">
<a href="https://github.com/Augani/sweeper/releases" className="px-8 py-4 bg-primary text-white font-bold rounded-full hover:bg-red-700 transition-colors flex items-center gap-2 group shadow-lg shadow-red-900/20">
<Download className="w-5 h-5 group-hover:translate-y-1 transition-transform" />
Download for Mac/Windows
</button>
<button className="px-8 py-4 bg-white/10 border border-white/20 text-white font-bold rounded-full hover:bg-white/20 transition-colors flex items-center gap-2">
Download for Mac/Windows/Linux
</a>
<a href="https://github.com/Augani/sweeper" className="px-8 py-4 bg-white/10 border border-white/20 text-white font-bold rounded-full hover:bg-white/20 transition-colors flex items-center gap-2">
<Github className="w-5 h-5" />
View Source
</button>
</a>
</div>
</div>
</section>
Expand Down
16 changes: 13 additions & 3 deletions docs/web/components/Features.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Layers, Zap, Clock, Code, Database, ShieldCheck } from 'lucide-react';
import { Layers, Zap, Clock, Code, Database, ShieldCheck, RefreshCw, Trash2 } from 'lucide-react';

const FEATURES = [
{
Expand All @@ -25,12 +25,22 @@ const FEATURES = [
{
icon: Zap,
title: 'Blazing Fast',
description: 'Written in Rust (the core engine) for maximum performance. Scans terabytes of storage in seconds, not minutes.'
description: 'Written in Zig with raylib for maximum performance. Tiny binaries (~1MB GUI, ~630KB TUI) that scan your storage in seconds.'
},
{
icon: ShieldCheck,
title: 'Safe Delete',
description: 'Items are moved to a staging area or Trash first. Review everything before permanent deletion. No accidents.'
description: 'Items are moved to your system Trash first. Review everything before permanent deletion. Empty your Trash to see the freed space.'
},
{
icon: RefreshCw,
title: 'Run Multiple Times',
description: 'Keep running Sweeper to discover new build artifacts as you work. Each scan finds freshly created node_modules and caches.'
},
{
icon: Trash2,
title: 'Empty Trash to Free Space',
description: 'Files are moved to Trash for safety. To actually reclaim disk space, empty your system Trash after cleaning with Sweeper.'
}
];

Expand Down
Loading