fdu scans one path and can answer several questions about it: directory sizes, totals, recent or large files, file kinds and languages, physical lines, standard lines of code, and prose volume. The command keeps the scan, analysis, selection, and presentation choices separate so each option has one meaning.
fdu .This is the default report:
listview intreeformat- allocated filesystem bytes
- largest entries first
- up to five entry levels, including significant file leaves
- entries contributing at least 1% of the selected root size
- no breadth or section-row cap
- metadata only; regular file bodies are not opened
- hidden and ignored entries included
fdu reads per-directory .gitignore files by default so it can annotate ignored byte
shares. Reading the rules does not exclude the entries they match.
For lines of code by language and words by document type, from one scan:
fdu . --view=code,documentscode and documents are the views that read file contents: naming either runs the
analyzer it shows, so --view=code or --view=documents gives one report alone.
Every other view is metadata-only.
These commands cover the other common questions:
fdu . --ignored=exclude
fdu . --view=summary
fdu . --view=languages
fdu . --view=families,types,extensions
fdu . --view=recent --limit=10--analyze is the extra control for analysis a view does not imply, such as code lines
in the language rows or physical lines across every language:
fdu . --analyze=code --view=languages
fdu . --analyze=lines --view=languagesfdu docs srcSeveral paths are one report: what fdu docs and fdu src would answer, added.
Sizes, counts, rows, shares, and display bounds are taken over every root together,
once, so --limit 20 is twenty rows of the union and a 1% share is 1% of the total.
$ fdu docs src
██████████ 100% 40 MiB 2m (total) 1,212 files
██████░░░░ 62% 25 MiB 2m docs/ 496 files
███░░░░░░░ 28% 11 MiB 4d experiments/ 312 files
████░░░░░░ 38% 15 MiB 2m src/ 716 files- The tree starts with a
(total)row, then each root as a row named by how it was given, whatever its share;--depthcounts below each root, and--breadthnever hides one. Roots are ordered as rows are, by size unless--sortsays otherwise. - A root row’s age never counts the root’s own time, as one root’s never does, so the
docsrow reads whatfdu docsshows at its root;fdu .can show a youngerdocs/, whose own time it counts. The total’s age is the newest of the roots’. - Flat listings print each path after its root’s label, as
find docs srcdoes:docs/guide.md. - Each root reads its own
.gitignorefiles, as it would alone; a rule in a shared parent applies to neither. - Globs containing
/, and the documentation and vendored classification, read each root’s own relative paths, not the labelled text: overfdu docs src,--exclude 'project/**'excludesdocs/project, while--exclude 'docs/**'excludes nothing, since neither root holds adocsdirectory. A file at the top ofdocsis not classified as documentation, as it is not whendocsis scanned alone. - Every PATH is a directory:
fdu *stops at the first file it meets, andfdu */names only the directories here.fdu */is refused in its turn where one directory here is a symlink to another, as a Linux virtualenv’slib64 -> libis (lib64 is the same directory as lib): name the directories without the link,fdu bin include lib, or walk their parent once, as below. - A root equal to or inside another is refused, naming both, since its paths would count
twice:
fdu src src/coreexits 2. Before any walk the check compares canonical paths, which sees through a symlink, and on Unix device and inode numbers, which sees a root whose own path runs through an alias of another (/Users/mebeside/System/Volumes/Data/Userson macOS). A root that is itself an alias into another root’s tree, such as a bind mount, or/usr/localbeside/System/Volumes/Dataon macOS, is found on Unix when the walk of the other root enters it, and the report is refused then, also with exit status 2; a walk that stops above it, at--scan-depthor at a filesystem boundary under--one-filesystem, counted nothing twice and is not refused. Windows compares canonical paths only, so an alias such as asubstdrive goes unnoticed there. The check before the walk is conservative:fdu / /mnt/usb --one-filesystemis refused although the walk would not have entered the second. - Each root is walked separately, one after another, and every walk pays a fixed cost
however little it holds, so many small roots are much slower than one walk of their
parent. Over 625 small directories as roots,
fdu */took 1.72 s where one walk of their parent took 0.23 s, about 7 times as long (exp-216, measured on macOS on a busy, uncontrolled host). To see every entry here one level down in one walk, usefdu --depth 1 --min-share 0% ., which also counts hidden entries and the files here, both of which*/leaves out, and does not walk the target of a symlink here, which*/names as a root. - Each root’s snapshot lives where that root’s own would, under one cache directory, and none is written until every root has been walked.
--watch,--cache-status, and--cache-cleartake one PATH.
Machine formats keep every path relative to its root and say which root it is under
(machine output). Python takes a sequence:
fdu.report(["docs", "src"]).
--view is a comma-separated list.
Multiple views share one scan and one requested content analysis; they do not cause
repeated filesystem walks.
| View | Answer |
|---|---|
list |
Matching entries; directory tree by default, flat paths or details on request |
tree |
The directory hierarchy, in structured output too |
summary |
One total for the selected tree |
families |
Broad code, prose, markup, data, binary, and unknown groups |
types |
Detected file types |
extensions |
Raw filename extensions; extensionless names use (none) |
languages |
Programming languages, metadata-only unless code analysis is enabled |
code |
Source-line overview, language and population totals, and coverage; runs code analysis |
documents |
Words and pages by document format; runs words analysis |
largest |
Twenty largest regular files by default |
recent |
Twenty most recently modified regular files by default |
files |
Every selected entry in name order |
full |
Every applicable view but list and files |
largest and recent are presets over files. --sort and --limit override their
defaults. Use --limit=all where a bounded view should print every row.
--depth limits reported tree levels, not scan depth; --scan-depth limits what can be
scanned and therefore changes the cache scope.
Numeric content sorts such as --sort=code_lines require their analyzer.
Use files, list, tree, or a metric grouping such as languages or code for
these rankings. extensions groups metadata only and rejects content-metric sorts.
The Code overview shows combined language totals; each row’s gitignored contribution
follows in gray parentheses, such as (0 gitignored), with an unknown contribution
added only when some .gitignore could not be read or applied.
Sizes use allocated bytes by default.
Add --size=apparent for logical file lengths.
Each row of a grouped view (families, types, languages, or documents) shows a
size, a percentage, a label, and a file count.
Any further measure, such as lines, words, files detected as generated or vendored, or
files not analyzed and why, is a line of its own indented under the file count.
Structured formats also count each row’s documentation files: those under a doc,
docs, or documentation directory, or named like a README, CHANGELOG, or CONTRIBUTING
file.
The percentage column in grouped views normally shows byte share.
When code analysis is shown by language, it shows code-line share instead.
In documents, it shows document-word share.
Text output labels those two non-byte denominators; machine output always carries the
exact share_metric, numerator, and denominator.
The denominator is the sum of every row’s value before display bounds, so unbounded rows
add up to 100%, within display rounding.
The default output is the directory tree: fdu ., fdu . --view list, and
fdu . --format tree print the same bounded directory roll-ups.
Significant files appear as leaves alongside directory totals.
Each tree row shows a share bar, percentage of the selected root, size, age, then its
indented name. The single remainder row, when present, uses those same columns for
unlisted root branches, with a blank age, and ends … and N more files. A listed
directory already represents its entire subtree, even when descendants are not expanded.
Listed root branches plus the remainder account for the root total.
Unknown quantities are labeled unknown rather than estimated.
--bar-size=20 widens the bar; --bar-size=0 or a negative value hides it.
The default is 10 characters.
This affects human trees only.
A row’s age is how long ago anything it counts last changed: the newest modification
among its own entry and every entry beneath it that it counts, files, directories, and
symlinks alike, so deleting or renaming a file makes its directory young and a freshly
installed tree reads as new even when its files keep archive times.
The scan root’s own time does not count.
With no filter, a directory’s age in the tree is its age in --long; under a filter,
each row ages only what it counts, so --kind file --modified-before 365d never shows a
row aged 3d. An age is unknown where part of the subtree was not listed, and — on
a row that counts nothing.
--sort mtime orders tree rows by this age.
Ages read in seconds, minutes, hours, days, months of 30.44 days (mo), and years of
365.25 days (y), floored, so 11mo is under a year.
Time bounds take days, since a month has no fixed length: --modified-before 60d, not
2mo.
| Format | List output |
|---|---|
tree or --tree |
Directories and significant files; default depth 5 and 1% of selected root size |
paths |
Every matching path, safely escaped, one per line |
long or --long |
Every match with its size (allocated unless --size says otherwise), modification age, and path |
json, jsonl, yaml |
Structured matching entries with exact metrics and bounds |
text |
Automatic human presentation: tree for List, tables for grouped views |
Flat lists use size-descending order with deterministic path ties; --sort name gives
an alphabetic inventory.
--sort mtime --reverse puts oldest entries first.
--limit N caps data rows per section, including the root in a tree.
--breadth N caps immediate children per directory.
Both default to all, except the 20-row largest/recent presets.
--depth all expands all levels and --min-share 0% admits all sizes.
Display bounds compose and leave aggregate measurements unchanged.
On a partial scan, verified small files and complete subtrees still obey the share
threshold against the observed root total.
Incomplete subtrees remain visible because their unseen contents could be significant; a
note explains this exception.
An unrelated scan error does not disable pruning.
Shares compare exact values against the selected root total, including threshold
equality. A child at 1% of its parent but below 1% of the root is hidden by the default.
Omission rows name share, breadth, depth, or row limits; they do not consume data rows.
--limit 0 shows no data rows, while --depth 0 shows only the root.
Explicit hierarchy controls require a hierarchical view; --scan-depth independently
limits discovery. Paths and Long omit the performance footer; bounds, rule coverage,
stale-answer status, and watch invalidations are reported on stderr.
Paths is a lossy line-oriented listing: control characters are escaped so one row stays
one line, undecodable bytes become U+FFFD, and every other character, the platform
separator and a literal backslash included, is written verbatim.
Use machine output for exact native path identity when names contain undecodable bytes.
Tree, Paths, and Long require one compatible list view; grouped/mixed views and Full accept automatic Text and machine formats. Largest/recent keep regular-file ranking and support Paths and Long; use List with Tree for directory roll-ups. Conflicting format flags are usage errors. Files keeps name ordering in flat formats; explicit Tree uses tree ordering. The Tree view keeps its structured hierarchy output; explicit Paths/Long overrides it. Full keeps its bounded digest and does not acquire an unbounded listing.
Selection changes which retained entries contribute to a report. It does not narrow the scan or cache scope.
Useful selections include:
fdu . --include='*.{rs,toml}'
fdu . --exclude='target/**'
fdu . --min-size=10MiB
fdu . --kind=file --modified-since=1h --view=files --sort=mtime
fdu . --kind=file --view=largestQuote glob patterns so the shell does not expand them before fdu sees them.
Size and time bounds test a directory’s eligible subtree, not its inode, and a directory
they match covers its contents in aggregate views.
Without --kind, fdu . --modified-since 7d therefore lists every directory with
activity this week at its full size beside the files that changed, and --view summary
counts everything inside those directories; --kind file asks only for the files.
Directory kind is a filter, just like name/path and modification time:
fdu ~/projects --kind dir --include .venv --modified-before 7d
fdu ~/projects --kind dir --include node_modules --modified-before 30d --long
fdu ~/projects --kind dir --include target --modified-before 30d --format paths
fdu ~/projects --kind dir --include .venv --include venv \
--include node_modules --include target --modified-before 30d \
--format long --sort mtime --reverse
fdu ~/projects --kind dir --include .venv --modified-before 30d --format jsonRepeated includes form a union.
A pattern without a slash matches a basename; one with a slash matches the path relative
to the scan root. These names are conventions: target is Cargo’s default build
directory, but its name alone does not prove ownership.
Directory size sums eligible regular-file contents, excluding directory-inode and
symlink bytes. Its modification time is the newest timestamp on the root or an eligible
descendant, including directories and symlinks; an empty directory uses its own time.
Long displays age relative to one request instant (30d, 2h, 3y, or a negative age
for a future timestamp), on the same ladder as the tree.
A watch measures each repaint from its own instant.
This measures modification activity, not access time or last use.
--size apparent switches to logical file lengths without changing matching semantics.
Exclusions apply throughout a matching directory’s subtree before size/age predicates.
Excluding a directory excludes its contents; a matching parent cannot bring them back.
--ignored=exclude subtracts ignored contents, while --ignored=only still traverses
structural unignored ancestors to discover ignored matches.
Matching a directory covers its eligible contents in summary and grouped views without
requiring descendant names to match.
Flat output emits matching entries only.
Nested matching roots are all shown, so their sizes can overlap; summary/grouped totals
count the covered union once.
The scan root provides context and is not itself a selectable descendant.
Hard links and shared extents are counted once for each path; sizes do not promise
uniquely reclaimable space.
Symlinks are never followed.
A directory whose subtree was not listed in full, at a --scan-depth boundary or in a
scan that finished with errors, reports a lower-bound size and an unknown age in
long, carries complete: false in machine output, and matches no modification bound.
See the machine-output reference for exact size, age, timestamp,
and completeness fields.
A retained index can answer narrower selections when its scope contains the needed
facts. One-shot ignored-population choices determine discovery and cache identity.
Allocated size is a sum over eligible regular-file paths. On Unix it uses each file’s reported allocated blocks; Windows currently falls back to apparent bytes. A hard-linked file contributes its reported allocation at every selected path. For example, two paths to one 16 KiB allocated file produce a 32 KiB total. Summary avoids counting overlapping selected directories twice, but does not merge separate paths that refer to the same file.
Copy-on-write clones have different file identities while potentially sharing physical blocks. Neither a sum of per-path allocations nor a future hard-link-aware total is a promise of space freed by deletion. Links outside the scan and shared extents can keep storage in use.
This matters for environments installed by uv: its current default link mode is clone on macOS and Linux, and hardlink on Windows; the mode is configurable. Cache and environment placement can also force copying across filesystems. See uv’s link-mode setting and cache placement.
A unique-file allocation measure is planned separately in the checkpoint design. It is not an available size mode today. The directory inventory assessment records current evidence and the remaining design work.
fdu . --ignored=exclude
fdu . --view=files --ignored=only --format=jsonl--ignored=include is the default and reports both populations.
exclude prunes safely ignored subtrees and avoids reading ignored bodies.
only discovers ignored matches through non-ignored ancestors and reads only ignored
bodies for analysis.
Unknown classification cannot justify pruning.
These choices change totals, ordering, and --min-size together.
Unignored means what the observed rules leave.
fdu does not read trackedness, .git/info/exclude, core.excludesFile, or a global
ignore file. .git itself is unignored unless a rule names it.
Each directory’s rules come from <dir>/.gitignore as git opens it.
On a case-insensitive volume, which macOS and Windows use by default, a .GITIGNORE is
that file and its rules apply, as they do for git there; on a case-sensitive volume only
the exact name counts.
fdu names such a file .gitignore wherever it reports one, as git check-ignore -v
does. Pattern matching itself stays case-sensitive.
For the most recent working files without ignored entries or repository internals:
fdu . --view=recent --limit=10 --ignored=exclude --exclude='.git/**'Human rows label this subset as (73 MiB gitignored): the amount is already included in
the row total, not additional usage.
--no-gitignore disables reading and applying the rules, so ignored shares are unknown.
It cannot be combined with either ignored-state selection.
fdu opens regular file bodies only when the request asks for content analysis, in one of
two ways. A content view asks for the analysis it shows: --view=code runs the code
analyzer and --view=documents the words analyzer, because neither view has anything to
show without them.
--analyze names analyzers beyond what the views imply: code lines in
the languages rows, physical lines in families or types, or a run that only warms
the content cache.
Every other view, full included, is metadata-only unless --analyze
says otherwise, so choosing how to look at a tree never quietly turns into a read of
every file in it.
| Analyzer | Metrics |
|---|---|
none |
No analyzer beyond what the views imply; the default |
lines |
Physical, blank, and nonblank lines plus raw words |
code |
lines plus standard code, comment, and code-blank lines for supported languages |
words |
lines plus normalized and reader-visible prose words, paragraphs, and pages |
all |
Every shipped analyzer |
code,words runs both deeper analyzers in one streaming pass.
Both already include lines, so code,words,lines is equivalent and adds no work.
Use lines alone for physical text volume without language-specific code counting or
word normalization. none and all name the whole axis and cannot be combined with
another value. Files are analyzed through EOF, not truncated by size.
--workers bounds concurrent content-analysis reads.
Under code, a code file in a language without a line-of-code counter is reported as
unsupported code coverage.
Its physical-line metrics remain available; code, comment, and code-blank counts are
unavailable.
The two directions are one table read both ways.
Naming analysis without a view chooses one that displays it: code selects code,
words selects documents, lines selects families, and code,words or all
select both code and documents. Naming code or documents as a view runs the
analyzer that selects it, so fdu . --view=code and fdu . --analyze=code are one
request, with one cache sidecar and one answer.
An explicit --view always wins.
If it displays no content metric, fdu still performs the requested analysis and names
the view that would show it: fdu . --analyze=code --view=summary ends with
note: code analysis not shown by summary and tip: show it: --view code. If it
displays some of the analysis and not the rest, the note names the rest, and the tip
keeps your views: fdu . --analyze=code --view=documents ends with
note: code analysis not shown by documents and tip: show it: --view documents,code.
Analyzer names describe measurements; view names describe populations or groupings. Section headers use the canonical view name, uppercased, whenever multiple views are shown. Table column labels name the measurements. A standalone report omits the redundant section header.
| Analysis | Default view and header | Population |
|---|---|---|
lines |
families / FAMILIES |
All selected files, grouped by detected family; readable text contributes lines |
code |
code / CODE |
Selected code files, grouped by language; unsupported counters remain explicit |
words |
documents / DOCUMENTS |
Selected prose and markup, grouped by type |
code,words or all |
code,documents / CODE, DOCUMENTS |
Both populations, from one scan and analysis pass |
The code and documents rows read in both directions: those views imply their
analyzers.
families does not imply lines, because it is a metadata report without it.
The words analyzer also measures accepted text outside prose and markup.
Select --analyze=words --view=types to see those metrics across all detected types.
--analyze=lines --view=languages compares physical lines across code languages,
including languages without a code counter.
Code lines exclude comments and blanks, so they are a different metric from physical
lines.
For a word report, use fdu . --view=documents; fdu . --analyze=words is the same
request. documents is the population, not another name for the words analyzer, so
--view=words is refused with the view to use instead.
An index opened from Rust or Python holds exactly the analyzers it was opened with: a
read of it never adds one, and a content view it cannot answer is refused with the
analyzer named.
No ordinary view requires a cache. The first command on a root can always scan it.
Metadata-only one-shot reports still have to inspect current metadata: an in-place file
edit changes no parent-directory timestamp.
Under --cache=auto, the default, such a report therefore neither loads a metadata
snapshot, which cannot make it cheaper, nor writes one, which no later report reads.
Content analysis, --watch, and a retained index from the Rust or Python open do
read, revalidate, and write it.
Use --cache=on to leave a snapshot after a one-shot report, for a later --stale-ok
answer or a warm watch.
Content results are different. They live in a sidecar keyed by analyzer identities and semantic options. After the metadata check, fdu restores compatible results for unchanged files and opens only changed or newly eligible bodies. The sidecar answers only the analyzer set that wrote it: a different set, wider or narrower, reads the files again and replaces it.
Run the same analysis twice to see the distinction:
fdu . --view=code
fdu . --view=codeThe gray performance footer reports metadata source, content bytes read, fresh and cached analysis counts, total ignore files, and accepted rules. Rules count each governing location, including repeated rule sources and negations, and exclude comments, blank lines, and rejected patterns. Refused files are named separately.
Total files/s and binary GiB/s divide walked files and represented size by the same
elapsed wall-clock duration shown first in the perf: line.
Represented size uses the selected apparent or allocated measure; it is not disk-read
bandwidth. Actual content-read throughput uses bytes read and the content-analysis
duration. On an unchanged tree the second run can show zero content bytes read and every
analysis record cached, while metadata verification still occurs.
| Policy | Behavior |
|---|---|
auto |
Read and write where this kind of run gains from it: analysis, --watch, and retained indexes from open, not one-shot metadata reports |
on |
Read and write where auto does, and also write after one-shot metadata reports |
off |
Neither read nor write fdu cache data |
--stale-ok answers from the snapshot without touching the source tree, and the run
fails on a miss rather than silently falling back to scanning.
The answer is labelled stale: machine formats report provenance.source: cache_only and
freshness: stale, and every format, plain text included, adds one line to stderr that
--quiet keeps:
warn: stale answer: served from the snapshot without filesystem verification; drop --stale-ok for a fresh answer
A verified answer, cold or warm, never carries it.
--stale-ok also requires compatible content data when analysis is requested, and it
cannot be combined with --cache=off or --watch. Earlier releases accepted
--cache=only, refresh, and read-only; each is refused with its replacement named.
Use fdu --cache-status=all to inspect cache files and fdu --cache-clear=all to
remove current, stale, and recognized leftover fdu data.
Unrecognized files are never removed.
The cache defaults to ~/.cache/fdu on macOS and Linux, and %LOCALAPPDATA%/fdu on
Windows. --cache-dir DIR wins over FDU_CACHE_DIR; otherwise XDG_CACHE_HOME/fdu
overrides the platform default.
Overrides name the exact directory.
Status and clear use this same resolution.
Each canonical root has <16-hex-key>.metadata.bin, holding filesystem facts and ignore
controls, and optional <16-hex-key>.analysis.bin, holding derived counts and
classifications. These disposable binary files do not store source-file bodies.
Use cache status to inspect their roots, scope, and freshness.
Human cache status uses the same binary size units as reports, such as
57 MiB metadata, 115 MiB content; JSON and YAML retain exact byte counts.
The cache design explains snapshot scopes, verification costs, atomic writes, and cleanup rules.
fdu . --view=summary,types --format=json
fdu . --view=files --format=jsonl
fdu . --watch --view=files --format=jsonlText is for people. JSON, JSON Lines, and YAML carry versioned schemas and omit the
text-only performance footer.
Integer fields that exceed 2^53 — fingerprints, option hashes, and nanosecond timestamps
— lose precision in JavaScript JSON.parse and any other IEEE 754 binary64 consumer.
Read them as strings, or use a parser that preserves integers, if exact identity
matters. Results go to stdout; diagnostics go to stderr.
The command never prompts or pages.
A run that takes longer than half a second shows one progress line on stderr while it works, and erases it before anything else is written:
⠼ ~/wrk/github Scanning 412,309 files · 12,041 dirs · 38 GiB 3.1 s
⠧ ~/wrk/github Analyzing 24% 12,044 / 50,110 files 7.9 s
Counts are right-aligned in columns wide enough for seven figures and sizes in columns
wide enough for 1023 GiB, so the line holds still as they grow.
Its bytes are measured as the answer’s are, allocated unless --size apparent: a sparse
disk image can be terabytes apparent and megabytes allocated.
It is drawn only for a person at an interactive terminal: stderr must be a terminal,
TERM must be set and not dumb (Windows consoles set no TERM, so there an unset or
empty one is accepted and the console’s escape-sequence support is the test), and CI
must be unset or empty.
Otherwise nothing is drawn, whatever the flag says, so pipes, files, logs, CI, and
agents never see it and need no flag.
--progress=auto (the default) also skips it for JSON, JSON Lines, and YAML;
--progress=always draws it for those too, and --progress=never turns it off.
--color and NO_COLOR decide only whether it is colored.
Ctrl-C while it is drawn erases the line, prints fdu: interrupted, and ends the
process by the interrupt signal, so the shell reports status 130 and a calling script
stops.
Check complete and errors before trusting totals, freshness and source before
presenting them as current, row or section bounds before assuming exhaustiveness, and
metric coverage before presenting analysis as complete.
--stale-ok is the only mode that can return stale freshness.
Exit status 0 is complete success.
Status 1 is a fatal filesystem or cache failure.
Status 2 is invalid usage or a partial result; useful partial output remains on stdout.
Every request fdu refuses is invalid usage, whatever the reason: a value no grammar
accepts, a rule between two flags, a scan scope this build cannot honour, such as
--one-filesystem where the platform has no device identity, and roots that overlap,
whether the paths show it before any walk or a walk finds it.
--allow-partial accepts an operationally partial result and returns 0.
--watch streams changes from a retained index.
Creating, removing, or renaming an entry also changes its directory’s own modification
time, so under --view files the record for that change is followed by an upsert for
the directory, unless it is the scan root or the selection leaves directories out, and a
watched tree ages the directory as a one-shot report does.
--interval throttles rendering, not change detection; an idle tree performs no polling
scan. On macOS, the kernel reports writes to a file only when it is closed, so watch
shows a file held open for writing as of its last close; a one-shot report reads its
current size. The duration uses the same age grammar as --modified-since: 2s,
200ms, 1h30m. Fractional ages such as 0.2s are still rejected.
Content analysis is one-shot and cannot be combined with watch mode.
Run fdu --docs for the offline guide and fdu --help for every flag.
From the project that should use the skill, run:
uvx --no-build fdu@latest --install-skillThis needs uv and a compatible prebuilt wheel, but no
persistent fdu command or Rust compilation.
It writes .agents/skills/fdu/SKILL.md and .claude/skills/fdu/SKILL.md under the git
root of the current directory, or under the current directory when it is not in a
repository; --agent-base DIR writes DIR/skills/fdu/SKILL.md instead, for one agent’s
user scope such as ~/.claude. It reports each file as installed, updated, or
unchanged, replaces only files it generated, and refuses a SKILL.md written by hand
with exit 2. Deleting those directories uninstalls it.
The generated file is the complete agent-facing usage contract and does not depend on
the installing session’s prompt or memory.
The skill prefers an fdu on PATH whose version matches its generation stamp.
Its fallback pins the package release version with uvx --isolated --no-build, and may
be used only after that release is published.
A development-generated skill requires its matching local executable until publication;
it never substitutes an older version or @latest. Installing the skill does not
install the command.
The zero-install fallback follows uv’s exclude-newer policy; see the
installation note if a just-published release is
filtered. To keep the command on PATH, run uv tool install --no-build fdu and later
uv tool upgrade --no-build fdu. fdu --skill prints the portable agent-facing
contract. The skill names the build that wrote it, so re-run the installer after
upgrading fdu.
Use --quiet (or -q) to suppress notes, tips, performance lines, and progress while
keeping the same result output.
Warnings and errors remain visible on stderr, including the one that marks a
--stale-ok answer stale, and exit status still reports incomplete or failed results.
Structured output retains its facts.
fdu . --quiet
fdu . --view tree --full --format json --quiet--full lifts all display bounds.
Use it for a complete recursive hierarchy or a flat inventory with directory roll-ups:
# Every directory and regular file, with recursive directory totals.
fdu . --view tree --full --format json
# Every directory as one row with recursive bytes, allocation, and file counts.
fdu . --view list --kind dir --full --sort name --format json
# Every regular file as one row with its own size and modification time.
fdu . --view files --kind file --full --format json
# Paths only, like a recursive find/fd search.
fdu . --kind file --include '*.rs' --full --sort name --format paths
fdu . --kind dir --include node_modules --full --sort name --format paths
# The same directory search, with recursive usage and age.
fdu . --kind dir --include node_modules --full --longFor these inventory and search tasks, fdu can replace a find or fd invocation while
also providing usage totals:
| Search | find |
fd |
fdu |
|---|---|---|---|
| All regular files | find . -type f |
fd --unrestricted --type f . . |
fdu . --kind file --full --format paths |
| Rust filenames | find . -type f -name '*.rs' |
fd --unrestricted --case-sensitive --glob '*.rs' . |
fdu . --kind file --include '*.rs' --full --format paths |
| Dependency directories | find . -type d -name node_modules |
fd --unrestricted --type d --glob node_modules . |
fdu . --kind dir --include node_modules --full --format paths |
These examples compare matched paths, not ordering or byte-for-byte path spelling.
fdu includes hidden and ignored content by default; fd --unrestricted makes that
population explicit.
fdu’s --ignored exclude applies its per-directory .gitignore contract, not every
ignore source supported by fd.
Selection uses globs, not fd’s default regular expressions or find’s expression
language. fdu does not provide find’s -exec actions.
Paths output escapes control characters for display; use JSON/JSONL/YAML with native
path identity for robust programmatic consumption, not a newline pipeline for arbitrary
filenames.
Nested directory rows overlap: a parent’s recursive usage includes its descendants.
Do not sum those rows; add --view list,summary for a path-union total.
A file row has its own usage, not a recursive tally.
--full does not enable file-body analysis, change ignored population, or override
--scan-depth. Inspect completeness independently of display bounds.
Explicit flags override the shorthand, for example --full --depth=3.
Use fdu . --view tree --format json --full for a full recursive roll-up.
--full is shorthand for --depth=all --breadth=all --limit=all --min-share=0%.
Explicit limits override it regardless of flag order: --full --depth=3 expands all
rows through depth three.
The view selection is unchanged (--view full selects multiple views and has a
different purpose). Scan scope, ignored population, and analysis are also unchanged;
unreadable directories still make coverage incomplete.
With complete discovery and no exclusions, every directory and regular file appears,
remainder is null, and there are no omission notes or tips.
Formatted results go to stdout.
Notes, warnings, and suggestions go to stderr as note:, warn:, and tip: lines; a
human text/tree report ends stderr with perf:. JSON, JSONL, YAML, paths, and long rows
remain cleanly consumable by other programs.
Warnings retain their cause and affected path, and partial results retain their exit
status. Flag suggestions appear once at the end rather than beside every omitted row.
Omitted sizes name the omitted contents and are already included in directory totals.
See the output design for category
ordering, colors, omission alignment, and diagnostic detail.