Repository navigation
Conversation
…the window AI agents and scripts can now ask disktree where the space went, through the scanner the window already uses, with no way to change anything. Headless, read-only: - `disktree scan [PATH] [--json]` prints a documented report (docs/agent-json.md, versioned by `schema_version`) built for agents: every size as bytes, human size and percent of the total, every time also as `age_days`; the volume's free space; three breakdowns (top-level folders, kind of data, age) that each sum to the total exactly; a reclaimable summary counted once at the outermost folder, by reason, with the free space it would give back; and the largest directories (marked `nested_in` when already counted) and files. Options for top-N, listing depth, apparent size, excludes, one filesystem and `--only` sections; filters for minimum size, extension, age, name, category and reclaim reason. - `disktree summary [PATH]` gives a volume's capacity and free space. - `disktree mcp` serves six read-only tools over stdio (scan_path, reclaimable_space, largest_dirs, largest_files, list_files, disk_summary) returning the same JSON, with a five-minute scan cache, progress notifications and cancellation. The new crate `disktree-agent` never names `removal` or `export`; a test reads its source and fails on any call that could write, move or delete a file. Report categories leave out the window's guess for unrecognised top-level folders, so `~/Library` is not counted as cache. In the window: - An "AI agent…" button, `a` and File > Use with an AI Agent open a panel with a prompt to paste into Claude, Codex or Cursor, written for this machine, plus the MCP settings and the Claude Code command. Paths are single-quoted so a folder named `$(…)` stays a name. - "Export JSON…", `e`/Cmd-E and File > Export Report as JSON save the scan on screen as the same report, built off the UI thread. - Both sit in the top bar and step aside at narrow widths so the view settings never move; About disktree, `--version` and the keyboard overlay show the version. In disktree-core, `ScanOptions::exclude` leaves matching names or paths out of the walk; duplicate-mount suppression and the MFT reader stand aside when patterns are set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rsonnad
force-pushed
the
headless-scan-mcp
branch
from
October 11, 2026 06:44
516e83d to
8136510
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Agents like Claude Code and Cursor are increasingly asked to "free up some disk space". Today their only options are slow
dupipelines or guesswork. This PR lets them ask disktree instead: with no window, through the scanner the app already uses, and with no way to change anything. Every command and tool in this PR only reads and reports.It also tells people who download the app that this exists: the window gets an AI agent… button that hands over a ready-to-paste prompt, and an Export JSON… button.
What it adds
disktree scan [PATH] [--json]~) and prints a report: plain text, or documented JSON with--jsondisktree summary [PATH] [--json]disktree mcpThe MCP tools:
scan_pathreclaimable_spacelargest_dirslargest_fileslist_filesdisk_summaryOptions and filters (CLI flags; MCP arguments are the same names)
--apparent-size(vs on-disk),-x/-X(stay on one filesystem, the default, or cross),--exclude PATTERN(repeatable; excluded folders are never walked),--no-hidden,--follow-links.--top N,--depth N, and--only overview,reclaim,dirs,files.--depthonly limits what is listed; sizes always count everything beneath.--onlykeeps a report to the parts that were asked for.--min-size 1G,--ext iso,dmg,--older-than 6moor a date,--name '*backup*',--category cache,--reclaim any|build-output|….--category cachegives~/.cache, not every folder inside it.The report is built for agents
An agent shouldn't spend tokens adding up numbers, or risk adding up numbers that overlap, so the report does that work. Abridged, from a real home directory:
{ "total": { "bytes": 417559109632, "size": "389 GiB", "files": 7614519, "directories": 718228 }, "volume": { "available": { "size": "359 GiB" }, "used_percent": 61.2, "root_percent_of_used": 68.6 }, "breakdown": { "children": [ { "path": "/Users/me/Library", "size": "172 GiB", "percent": 41.2, "age_days": 0 } ], "by_category": [ { "category": "cache", "size": "167 GiB", "percent": 42.9, "files": 3901233 } ], "by_age": [ { "age": "under_30_days", "size": "196 GiB", "percent": 50.4 } ] }, "reclaimable": { "size": "186 GiB", "percent": 47.9, "places": 10580, "available_after": { "size": "545 GiB" }, "by_reason": [ { "reason": "reinstallable", "size": "83 GiB", "places": 155, "description": "Installed dependencies beside their manifest; reinstall with the project's package manager." } ], "largest": [ { "path": "/Users/me/Library/Developer/Xcode/DerivedData", "size": "52 GiB", "reclaim": "build-output" } ] }, "largest_dirs": [ { "path": "/Users/me/Documents/codingprojects", "size": "128 GiB", "nested_in": "/Users/me/Documents" } ] }bytes, humansize, andpercentof the total. Every time is also given asage_days; for a folder, that's the days since anything inside it changed.breakdownsplits the total three ways (top-level folders plus a remainder row, kind of data, age). In each, the parts don't overlap and sum to the total to the byte.reclaimablecovers caches, build output,node_modules, trash and temp files, counted once at the outermost folder. Totals come by reason, with one plain-language description per reason, plus the volume's free space before and after.nested_inmarks entries inlargest_dirsthat are already inside another listed entry.docs/agent-json.md, and the format carries aschema_version.One deliberate difference from the window. For an unrecognised top-level folder, the window borrows the kind of its largest recognisable child, so
~/Libraryis coloured as cache because it holdsCaches. That makes a good colour but a wrong total: it would put all 172 GiB of~/Libraryunder "cache". Reports leave such folders asother. The window's colouring is unchanged.In the window
Someone who downloads the app won't know it can be driven from an agent, so the window says so:
a, or File › Use with an AI Agent…) opens a panel with:$(…)stays a name;e/ ⌘E, or File › Export Report as JSON…) saves the scan on screen as the same reportdisktree scan --jsonprints.disktree --version, and the?overlay's footer all show it.Read-only, and how that's enforced
crates/disktree-agent. It usesdisktree-coreonly for scanning and never namesremovalorexport.tests/read_only.rsreads the crate's own source and fails the build if anything that could delete, move, trash, rename or write a file appears in it (remove_file,fs::write,OpenOptions,trash::,Command::new, …).readOnlyHint: true, destructiveHint: false, and a test checks no tool name suggests otherwise.main. That's before the Power Efficiency settings are loaded or saved, and before the cmux re-spawn, which would point stdout at/dev/null.Changes outside the new crate
disktree-core: a newScanOptions::exclude(exclude.rs)./matches a name, and one with/matches a path.*stays within one path component,**crosses components, and?matches one character.follow_links.disktree-app:main.rshands off the headless subcommands as described above, and adds--version. A folder literally namedscanstill opens asdisktree ./scan.serde_json(withpreserve_order),dunceandchrono. All are already in the lockfile, so nothing new is downloaded.Try it
MCP client config (Claude Desktop, Cursor):
{ "mcpServers": { "disktree": { "command": "/absolute/path/to/disktree", "args": ["mcp"] } } }Testing
cargo xtask lint(fmt check, then clippy with-D warnings) andcargo xtask testpass. That covers 39 new agent tests, 5 new core tests, and 75 window-harness tests (71 existing, 4 new).Cachesisn't itself counted as cache.cargo clippy -p disktree-core -p disktree-agent --all-targets -- -D warningsis clean forx86_64-pc-windows-msvcandx86_64-unknown-linux-gnu.disktree-appcan't be cross-checked for Windows from a Mac (GPUI's build script needs the Windows SDK), so CI is the first Windows build ofmain.rs.scan_pathoverview is about 6k tokens.initialize, thentools/list;scan_path, with progress notifications;reclaimable_space,largest_dirsanddisk_summary;Review
A Codex (gpt-6.1-sol) read-only review found no path from the CLI or MCP into removal, export or file-writing code. It did find six issues, all fixed, five of them with regression tests:
shwith a name containing$(…), backticks, quotes,$HOMEand a newline.available_afterprojected apparent sizes and followed links as freed space. It's nownullin both modes.--excludecould hide a bind-mount alias of an excluded mount on Linux. Excludes now join the hidden/depth guard on duplicate-mount suppression.-32600.Not covered by tests
repeated_mounts_forreads the real mount table, so it needs Linux; the fix extends the existing guard.& '…',''escaping) is compiled by the cross-target clippy run but never executed.json_textthe CLI uses and thattests/report.rscovers.One behaviour choice to note
When stdin closes, the MCP server finishes and answers the requests it already has instead of abandoning them, so
echo '<request>' | disktree mcpgets its answer.🤖 Generated with Claude Code