# Installation ## Quick install ```bash curl -fsSL https://raw.githubusercontent.com/krondor-corp/oops/main/install.sh | bash ``` The script detects your platform, downloads the matching release tarball from GitHub, and drops the `oops` binary into `~/.local/bin`. Override the destination with `INSTALL_DIR=...` if needed. Prebuilt binaries ship for `aarch64-darwin` (Apple Silicon) and `x86_64-linux`. Other platforms must build from source. ## Requirements - **macOS** (Apple Silicon) or **Linux** (x86_64) for prebuilt binaries - `curl` or `wget` (the installer uses whichever is available) - Rust 1.75+ only if you're building from source ## From source Only needed for unsupported architectures or if you want to hack on oops: ```bash git clone https://github.com/krondor-corp/oops cd oops make install ``` ## Verify ```bash oops --version oops --help ``` ## Updating ```bash oops update # upgrade to the latest release oops update --force # reinstall even if already current ``` `oops update` detects how it was installed and, for install-script setups, re-runs the installer in place. Cargo/source builds are prompted before switching to a release binary. ## Quick start Just run `oops` in any directory: ```bash oops ``` You'll get a size breakdown of the current directory. From there: ```bash # See where your space went oops ~/ # Shallow overview oops --depth 2 ~/ # One-liner: how full am I? oops free ``` ## Flags | Flag | Effect | |------|--------| | `-d` / `--depth` | Maximum depth levels to display (default 4) | | `-n` / `--show` | Max entries shown per directory level (default 5) | | `--expand` | Max children to recurse into per level (default 3) | | `--width` | Terminal width override (default: auto-detect) | | `--plain` | No colors, no decorations — for scripting | | `-v` / `--verbose` | Debug tracing output to stderr | `--plain` and `-v` are global — they work with any subcommand. The depth/show/expand/width flags apply to the default map view; alias a shorthand in your shell if you want a memorable name for a common combination (e.g. `alias oopsd='oops --depth 2'`). --- # How oops works ## Philosophy macOS doesn't ship with `gparted` or anything like it. When your disk fills up, you're stuck clicking through Finder or running `du -sh *` one directory at a time. `oops` fixes that. The design principles: - **One command.** `oops` does one thing: show you where your disk space went. No modes to learn, no cleanup engine to configure — just the map, plus `free` and `update`. - **Fast by default.** Parallel scanning across all CPU cores. Most directories render instantly. - **Honest sizes.** Reports on-disk block usage, not apparent file sizes. A 1 TiB sparse Docker image that only uses 20 GiB of blocks shows as 20 GiB. - **Terminal-native.** Colored output with proportional bars — all to stderr, so stdout stays clean for scripting. - **APFS-aware.** Handles macOS firmlinks correctly when scanning `/` to avoid double-counting. ## What you see Run `oops` in any directory and you get the map: every entry on its own line, with a bar sized proportionally to how much space it takes. Your eye lands on the biggest thing immediately — no sorting, no reading numbers. At the top, a volume context bar shows the bigger picture: how much of the disk you scanned, how much is elsewhere, and how much is free. If most of your usage lives outside the directory you scanned, oops tells you where to look next. ``` / (460.4 GiB total, 27.0 GiB free) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ scanned 270.6 GiB other 118.8 GiB free 71.0 GiB ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 270.6 GiB ~/ ━━━━━━━━━━━━━ 91.7 GiB Library/ ━━━━━ 32.8 GiB Application Support/ ━━━━━━ 55.6 GiB repos/ ``` ## Key commands | Command | Purpose | |---------|---------| | `oops` (default, no subcommand) | Visual disk usage map — proportional bars, one line per entry | | `oops free` | One-liner: how much space is left? | | `oops update` | Self-update to the latest release | See [Commands]({{ '/docs/commands/' | relative_url }}) for flags and details. ## On-disk sizing Most disk usage tools report **apparent size** — what the file claims to be. For **sparse files** (like Docker.raw on macOS), the apparent size can be wildly larger than the actual disk blocks allocated. oops reports allocated blocks — the same metric `du` uses. This gives you the real on-disk footprint. > A Docker.raw file might report 1 TiB apparent size but only consume 20 GiB of actual disk blocks. oops shows you the 20 GiB. More detail in [Sparse files]({{ '/docs/sparse-files/' | relative_url }}). ## APFS handling On macOS, `oops /` redirects to `/System/Volumes/Data` and skips child mount points to avoid double-counting via APFS firmlinks. The volume context bar at the top shows the breakdown: scanned data, system/other, and free space. ## Contributing oops is open source — the developer docs (architecture, coding patterns, contribution guide) live in the [GitHub repo](https://github.com/krondor-corp/oops/tree/main/docs). --- # Commands ## oops (default) With no subcommand, `oops` shows the visual disk map of the current (or given) directory — proportional bars, one entry per line. This is the whole point of the tool; there is no separate `map` subcommand. ```bash oops # Where's my space going? oops /some/path # Map of a specific path oops / # Full disk breakdown (APFS-aware) oops --depth 2 ~/ # Shallow overview oops -n 10 --expand 10 ~/ # Show everything ``` | Flag | Default | Effect | |------|---------|--------| | `-d` / `--depth` | 4 | Maximum depth levels | | `-n` / `--show` | 5 | Max entries shown per directory level | | `--expand` | 3 | Max children to recurse into per level | | `--width` | auto | Terminal width override | On macOS, `oops /` automatically redirects to the APFS data volume to avoid double-counting via firmlinks. Shows a volume context bar at the top with scanned/other/free breakdown. Want a shorthand for a common invocation? Alias it in your shell: ```bash alias oopsd='oops --depth 2' ``` --- ## free One-liner: how much space is left on the volume containing the current (or specified) path. ```bash oops free oops free /Volumes/External ``` --- ## update Self-update to the latest release. ```bash oops update ``` --- # How scanning works This page explains why oops is fast and why its numbers are trustworthy. You don't need any of it to use the tool — it's here for the curious. ## Parallel scanning oops sizes directories in parallel across all your CPU cores. For a directory with 100 children, all 100 size calculations happen concurrently, and the traversal inside each child parallelizes too. This is why scanning your entire home directory takes seconds, where serial tools like `du` can take minutes on the same hardware. ## Block-level sizing Every file is measured by the disk blocks it actually occupies, not the size it claims to be: ``` apparent size → what the file "says" it is (what ls -l shows) allocated size → what the disk actually stores (what oops shows) ``` For regular files these are nearly identical. For sparse files, compressed files, or files with holes, they can differ dramatically — see [Sparse files]({{ '/docs/sparse-files/' | relative_url }}) for the classic Docker.raw example. ## Volume detection oops asks the system (`df`) which volumes are mounted and how full they are. That powers two things you see: - the context bar at the top of the map — total / free / scanned breakdown for the volume you're on - `oops free` — the one-liner free-space summary The mount-point list is also what lets the map skip other volumes mid-scan, so scanning `/` on a Mac gives honest numbers instead of double-counting APFS firmlinks. --- # Sparse files ## The problem Run `ls -lh` on Docker's virtual disk: ``` -rw-r--r-- 1 al staff 1.0T Docker.raw ``` One terabyte? On a 460 GB drive? Something doesn't add up. `Docker.raw` is a **sparse file**. It has a logical size of 1 TiB (the maximum size Docker Desktop allocated for its virtual disk), but most of that space is empty — never written to. The filesystem only allocates blocks for the parts that actually contain data. ## Apparent vs. on-disk size ``` apparent size: 1,099,511,627,776 bytes (1.0 TiB) on-disk size: 22,388,580,352 bytes (20.9 GiB) ``` Most disk usage tools report the **apparent size**. This is misleading because it counts space that was never allocated. The actual disk usage comes from the filesystem's block allocation count: ```bash stat -f "blocks=%b" Docker.raw # blocks=43727696 # Actual size: 43727696 * 512 = 22,388,580,352 bytes = 20.9 GiB ``` ## How oops handles this oops uses `stat.blocks * 512` for every file, matching what `du` reports by default. This means: - **Sparse files** show their real footprint, not their inflated logical size - **Compressed files** (on APFS) may show smaller than their logical size - **Regular files** show the same size either way (block allocation ≈ logical size) As a fallback, if `blocks` is zero (some virtual filesystems), oops falls back to the logical size. ## Common sparse files on macOS | File | Location | Apparent | Typical actual | |------|----------|----------|----------------| | Docker.raw | `~/Library/Containers/com.docker.docker/Data/vms/0/data/` | 1+ TiB | 5–60 GiB | | Time Machine snapshots | Various | Varies | Much smaller | | APFS sparse disk images | `.sparseimage` files | Configured max | Actual content | ## Why this matters Without block-level sizing, a tool can report your home directory as 1.3 TiB on a 460 GiB drive. With it, you get the real number — typically 200–300 GiB for a developer workstation. The inflated number makes every other size look proportionally tiny, hiding the directories that are actually consuming your disk. --- # Recipes Real-world scenarios and the fastest way to diagnose them with oops. ## "My disk is full and I don't know why" ```bash oops ~/ ``` One command. Shows your full disk breakdown with volume context at the top (scanned / other / free), then drills into the biggest space consumers with proportional bars. You'll immediately see whether it's repos, Library, Docker, or something unexpected. To see the full disk including system files: ```bash oops / ``` ## "What's taking up space in this repo?" ```bash cd ~/repos/my-project oops . ``` Usually it's `target/`, `node_modules/`, or `.git/`. Clean up with the tool that knows how to regenerate it: ```bash cargo clean # target/ rm -rf node_modules # re-fetched by npm/yarn/pnpm install ``` ## "Where are all my node_modules?" ```bash oops ~/ --depth 8 -n 20 ``` Widen the map until the `node_modules/` directories show up as their own line, or just target a known repos root directly: ```bash oops ~/repos --depth 3 ``` ## "Is Docker eating my disk?" ```bash oops ~/Library/Containers/com.docker.docker ``` Shows you Docker.raw's actual on-disk size (not the inflated apparent size). If it's huge: ```bash docker system prune -a ``` ## "Xcode is eating 50 GB again" ```bash oops ~/Library/Developer ``` Clean up: ```bash rm -rf ~/Library/Developer/Xcode/DerivedData xcrun simctl delete unavailable xcrun simctl runtime delete all ``` ## "What's in ~/Library?" `~/Library` is a black box. Map it: ```bash oops ~/Library --depth 3 ``` Common offenders: - `Application Support/` — app data (Claude VMs, Steam games, Slack) - `Caches/` — safe to delete, apps rebuild them - `Containers/` — sandboxed app data (Docker lives here) - `Developer/` — Xcode caches and simulators - `pnpm/` — pnpm global store ## "Cargo registry is huge" ```bash oops ~/.cargo/registry ``` The registry cache grows with every unique dependency version. Clean old versions: ```bash cargo cache --autoclean # or rm -rf ~/.cargo/registry/cache ``` ## "I freed space but disk still shows full" macOS uses APFS purgeable space. Check with: ```bash oops free ``` If the volume still shows full after deleting files, Time Machine snapshots may be holding references. macOS will purge these under pressure, or force it: ```bash tmutil listlocalsnapshots / tmutil deletelocalsnapshots ``` ## "Where do I start on a new machine?" ```bash oops ~/ ``` One command. You immediately see your disk layout and where to focus cleanup — repos, caches, or Docker. ## "Automated disk monitoring" Use `--plain` mode for scripts: ```bash oops free --plain 2>&1 | head -1 ```