# AppJanitor (CLI prototype)
AppJanitor is a leftover/stale-file cleanup finder: point it at a directory
tree and it walks the tree looking for junk-pattern files (*.tmp, *.bak,
*.log, editor backups, .DS_Store, Thumbs.db, or any custom glob list you
supply), files whose modification time is older than a threshold you set,
and directories that contain no files anywhere in their subtree. It reports
candidates with the specific reason each one matched and, on request,
clears them out safely by moving matched files into a quarantine directory
(never deleting them outright) and removing matched empty directories.
The full "App Janitor" product concept also includes registry-aware
deep-uninstall leftover cleanup, startup-item control, System Restore
point management, and driver rollback -- none of that is implemented here.
Those features require privileged, OS-specific APIs (the Windows registry,
WMI, Task Scheduler, System Restore, Device Manager) with no portable
stdlib equivalent, so they are out of scope for this dependency-free,
cross-platform Go CLI and are left on the roadmap. See ../plan.md for the
full product plan.
## Build from source
Requires Go 1.24+, no external dependencies.
go build -o appjanitor .
Cross-compile for another platform:
GOOS=windows GOARCH=amd64 go build -o appjanitor.exe .
GOOS=darwin GOARCH=arm64 go build -o appjanitor .
## Usage
appjanitor scan
[--older-than 180d] [--pattern "*.tmp,*.bak,*.log,~*,.DS_Store,Thumbs.db"] [--empty-dirs] [--json]
Read-only, always a dry run (there is no --apply for scan). Walks
and flags candidate files matching ANY of:
- filename matches one of the comma-separated glob patterns in
--pattern (default: *.tmp,*.bak,*.log,*~,.DS_Store,Thumbs.db),
matched against the base filename with filepath.Match
- file's mtime is older than --older-than, a duration-like string of
digits plus a d/h/m suffix (e.g. 180d, 12h, 45m; 1d = 24h)
- (only if --empty-dirs is passed) the directory contains no files
anywhere in its subtree -- reported separately as an empty-dir
candidate, not counted toward reclaimable bytes
Each candidate is printed with the reason it matched (junk-pattern:*.tmp,
stale:212d old, empty-dir) and a running total of reclaimable bytes for
files. Output ends with a summary line: N files (SIZE reclaimable), M
empty dirs. Pass --json to get the same candidate list as JSON instead.
appjanitor clean --quarantine [--older-than ...] [--pattern ...] [--empty-dirs] [--apply]
Uses the same matching logic as scan. Without --apply it is a dry run: it
prints exactly what scan would print, plus a "(dry run -- re-run with
--apply)" footer, and touches nothing on disk. With --apply:
- every matched FILE is moved into , preserving its path relative
to (parent directories are created as needed). Files are always
quarantined via a move, never deleted -- a mistaken match is always
one `mv` away from undone, never gone.
- every matched EMPTY DIRECTORY is removed directly (not quarantined).
An empty directory has no content to lose by quarantining, and
removing one is reversible in spirit, so os.Remove is used instead of
a move. Directories are removed deepest-first so a parent that only
becomes empty once its listed empty child is removed still succeeds.
--quarantine is required for clean. A summary line reports how many files
were moved (and total bytes reclaimed) and how many empty directories were
removed.
## Prebuilt binaries
See ../downloads/ for prebuilt binaries (Windows/macOS/Linux) and
CHECKSUMS.txt for their SHA-256 hashes. These are unsigned indie builds --
Windows SmartScreen and macOS Gatekeeper will warn on first run, which is
expected until a code-signing certificate is in place.