volume-atlas is the terminal companion to the Volume Atlas app. It uses the exact same scanning
engine, so you get the same numbers — just somewhere you can script them, pipe them, or pull them
from a machine over SSH.
⚠️ The commands below only work once the CLI is installed. Out of the box,
volume-atlasisn’t on yourPATH— nothing here will run until you install it one of these ways:
- Install the
.pkg(the Developer ID build) — this puts it system-wide at/usr/local/bin/volume-atlas, ready for any shell, including remote SSH sessions. (Recommended.)- Settings → CLI Tool in the app — on the Developer ID build this links it into
~/.local/bin(make sure that’s on yourPATH); on the Mac App Store build it copies it into a folder you pick.Until then, a shell will just say
command not found: volume-atlas. Also note: the sandboxed App Store version can only see folders you grant it — whole-volume scans and Full Disk Access belong to the Developer ID /.pkgbuild.
The shape of it
volume-atlas <subcommand> [options]
info List your drives and volumes with their capacity. (default)
scan [path] Scan a path — progress, a category breakdown, and the biggest items.
list [path] Just the largest files and folders under a path.
volume-atlas --version and --help (or <subcommand> --help) are always there when you need a
refresher. Run it with no subcommand and you get info.
A few things that apply everywhere
-
Where output goes. The actual report prints to stdout; progress, the wrap-up summary, and any warnings go to stderr. That separation means
… --format json > out.jsongives you a clean, parseable file with none of the chatter mixed in. -
Color. You get color when you’re at a real terminal. Two standard environment variables let you override that without any flags: set
NO_COLOR=1to turn it off entirely, orCLICOLOR_FORCE=1to keep it on even through a pipe (handy withless -R). -
Exit codes.
0means success,64means you got an option or path wrong, and1means the scan itself failed. -
About the sizes. They’re actual on-disk bytes, so totals track
du. App bundles count as one item, aliases and symlinks are never followed, scans stop at volume boundaries, and clones/hardlinks aren’t double-counted. -
When it can’t read something. Unreadable files don’t stop the scan — you still get a partial report, plus a clear ⚠️ warning (and a
skippedItemscount in the JSON) so you know the totals are light. If a whole item was refused outright — a Photos or Music library, an app bundle — it gets named, because one of those can hide far more space than the count suggests:⚠️ 1 item could not be read (permission denied) — reported sizes undercount actual usage. Not measured at all: Photos Library.photoslibrary — nothing inside it is counted. Grant Full Disk Access to scan everything (see the PPPC profile in Packaging/README.md).The same names come through as
unmeasuredItemsin the JSON. Over SSH this usually means the tool needs Full Disk Access; see the SSH section. -
The CLI has its own permissions, separate from the app.
volume-atlasis a different binary with its own identity, so granting the app Full Disk Access does not cover it. It’s normal to see the app measure your Photos library happily while the CLI reports it as unmeasurable — they’re two different grants. The CLI needs its own (via the PPPC profile, or by adding the binary under Privacy & Security → Full Disk Access). -
Photos and Music libraries are measured. They’re reported as one item each, sized by what’s actually inside. Without Full Disk Access, macOS may ask permission the first time — allow it, or the library counts as 0 and your total is short by however big it is.
volume-atlas info
A quick look at your drives and volumes and how full they are. No scanning involved.
volume-atlas info [--format <text|json>]
| Option | What it does |
|---|---|
--format <text|json> | Choose the output format (default text). |
In text mode each volume gets a capacity bar that’s colored by how full it is — green, yellow, red — next to the percentage and the used/total/free figures:
── APPLE SSD AP2048Z (boot)
Macintosh HD [APFS] [root]
█████░░░░░░░░░░░░░░░ 22.8% used · 453.98 GB of 1.99 TB · 1.54 TB free
volume-atlas scan [path]
The main event. Point it at a folder and it scans, shows you live progress, then prints a category breakdown and a list of the biggest files and folders. Leave the path off and it scans wherever you are.
volume-atlas scan [<path>] [-t <n>] [--tree] [-q] [--include-hidden]
[--show-packages] [--format <text|json>]
| Option | What it does |
|---|---|
<path> | The folder to scan (defaults to .). |
-t, --top <n> | How many top items to list, 1–10000 (default 20). |
--tree | Print the full indented tree instead of the summary. |
-q, --quiet | Hush the live progress line and the wrap-up summary. |
--include-hidden | Count hidden/dot files too. |
--show-packages | Open up bundles (.app, .framework, …) and break them down inside, rather than treating each as one item. |
--format <text|json> | Output format (default text). |
A couple of notes:
- Point it straight at an
.appand you’ll get the whole bundle’s size as a single number. Want to see what’s big inside it? Add--show-packages. - The permission ⚠️ warning shows even with
--quiet. That’s on purpose — a quietly undercounted total is exactly the kind of thing you don’t want to miss.
Text output looks like this:
$ volume-atlas scan ~/Downloads --top 4
Downloads — 28.41 GB
CATEGORY BREAKDOWN
Other 19.77 GB ██████████████░░░░░░ 69.6%
Archives & Installers 8.64 GB ██████░░░░░░░░░░░░░░ 30.4%
…
TOP ITEMS
Name Size Category
─────────────────────────────────────────────────────────────────────────
UniversalMac_26.5_Restore.ipsw 19.77 GB ████████████ Archives & Installers
VSCode-darwin-universal.dmg 323.1 MB ░░░░░░░░░░░░ Archives & Installers
…
Ask for --format json and you get a single, self-describing object — built so you (or a script)
can save it now and make sense of it later:
{
"host": "machine.local",
"generatedAt": "2026-06-26T16:30:00Z",
"osVersion": "Version 26.5 (Build …)",
"scannedPath": "/Users/jdoe/Downloads",
"root": { "name": "Downloads", "path": "…", "sizeBytes": 30502000000 },
"volume": { "name": "Macintosh HD", "totalBytes": …, "usedBytes": …, "availableBytes": … },
"skippedItems": 0,
"unmeasuredItems": [], // names of whole items refused outright, e.g.
// ["Photos Library.photoslibrary"] — anything listed here
// contributes 0 bytes to the totals above
"categories": [ { "category": "archives", "sizeBytes": …, "fraction": 0.304 }, … ],
"topItems": [ { "name": "…", "path": "…", "category": "archives",
"sizeBytes": …, "isDirectory": false, "dateModified": "…" }, … ]
}
If you’d rather have the whole nested tree as JSON, scan --tree --format json gives you that
instead. The volume block appears whenever the path you scanned belongs to a known volume.
volume-atlas list [path]
When you just want the biggest things, this is the one. It scans, then hands you the largest items — no breakdown, no ceremony. Path defaults to the current directory.
volume-atlas list [<path>] [-t <n>] [--search <text>] [--category <cat>]
[--min-size <size>] [--sort <order>] [--include-hidden]
[--show-packages] [--include-directories]
[-q] [--format <text|json>]
| Option | What it does |
|---|---|
<path> | The folder to scan (defaults to .). |
-t, --top <n> | How many results, 1–10000 (default 20). |
--search <text> | Keep only names/paths containing this text (case-insensitive). |
--category <cat> | One of apps, docs, audio, photos, video, dev, archives, other. |
--min-size <size> | Drop anything smaller — 10MB, 1.5GB, or a plain byte count. |
--sort <order> | size-desc (default), size-asc, name, date. |
--include-hidden | Count hidden/dot files too. |
--show-packages | Open up bundles (.app, .framework, …) instead of treating each as one item. |
--include-directories | List folders too, not only files. |
-q, --quiet | Hush the progress line and summary. |
--format <text|json> | Output format (default text). |
Every row gets a bar sized against the biggest item in the list, plus its category in that category’s color when you’re at a terminal. Apps show up as single items.
$ volume-atlas list /Applications --top 3
Name Size Category
──────────────────────────────────────────────────────────
Xcode.app 5.43 GB ████████████ Applications
iMovie.app 3.96 GB █████████░░░ Applications
Microsoft Outlook.app 2.88 GB ██████░░░░░░ Applications
Pulling a report over SSH
This is where the CLI really earns its keep. Because the .pkg puts volume-atlas on every
shell’s PATH, once it’s deployed (with the matching PPPC profile) you can grab a space report
the moment someone says they’re running low:
# A clean, self-describing report you can save and dig into later:
ssh admin@host 'volume-atlas scan /Users/jdoe --format json --quiet' > jdoe-space.json
# Or just eyeball the 30 biggest things:
ssh admin@host 'volume-atlas scan /Users/jdoe --top 30'
# Or just the volume capacity:
ssh admin@host 'volume-atlas info --format json'
One thing to set up first: to read protected folders (Desktop, Documents, Downloads, other
people’s home folders) non-interactively over SSH, the CLI needs Full Disk Access. Push a PPPC
profile that grants SystemPolicyAllFiles to both com.OD.VolumeAtlas (the app) and
com.OD.VolumeAtlasCLI (the CLI binary), each matched by its Developer ID code requirement — and
the package has to be signed and notarized for that to stick. Skip this and the scan still
runs and still reports, it just warns you about everything it couldn’t read.
Packaging/README.md has the full deployment story.
Cheat sheet
volume-atlas # same as: info
volume-atlas info --format json
volume-atlas scan ~ # what's in my home folder?
volume-atlas scan / --top 30 # biggest things on the boot drive
volume-atlas scan ~/Downloads --format json --quiet > report.json
volume-atlas scan /Applications/Xcode.app # a whole app's size, as one number
volume-atlas list ~/Movies --category video --min-size 100MB --sort size-desc
NO_COLOR=1 volume-atlas scan ~ # plain text, no color
CLICOLOR_FORCE=1 volume-atlas scan ~ | less -R # keep the color through a pager