A keyboard-driven file manager for the terminal that tells you what every folder actually weighs.
Three Miller columns — parent, current folder, preview — in the spirit of
ranger, nnn and lf. You navigate, search, select, create, rename, move
and delete without reaching for the mouse and without leaving the terminal.
╭ /tmp/fm-demo ───╮╭ /tmp/fm-demo/projects ──────────────────╮╭ photos-2025 ───────────────────╮
│ Documents ││ ▸ web-dashboard 1.3 GB ││ beach.jpg 1.7 MB │
│ Downloads ││ ▸ fm 780 MB ││ sunset.jpg 879 KB │
│ projects ││ ▸ photos-2025 2.8 MB ││ profile.png 244 KB │
│ ││ ▸ dotfiles 3.0 KB ││ │
│ ││ setup.sh 1.1 KB ││ │
│ ││ README.md 62 B ││ │
│ ││ ││ │
│ ││ ││ │
╰─────────────────╯╰─────────────────────────────────────────╯╰────────────────────────────────╯
6 items · 2.0 GB · size j/k move · / search · r rename · d trash · ? help
That is a real dump of the interface, rendered onto a 96×12 test backend and written out exactly as it sits in the buffer — which is why it has no color. In a real terminal, folders are bold blue, sizes shift hue with their magnitude, and selected entries are highlighted.
Every folder shows its true recursive size on disk, not just the size of the loose files inside it. That number is computed by walking the whole subtree on a background thread pool, so it never blocks navigation: while it is being computed the column shows a spinner and the running total, and the final result is cached until something inside that folder changes.
This turns sorting by size (s) into something you can actually use. The heavy
folders — the node_modules, the target, the photo folder nobody has opened
in three years — surface at a glance, instead of having to du your way down
one directory at a time.
One search key that scales. / filters the folder you are looking at. Put
a * at the front of what you type and the same key searches the entire
subtree below you, in the background. One key, two ranges — not two modes to
remember.
- Real folder sizes, computed in the background and cached, with color that tracks magnitude — gray under 100 MB, yellow past 1 GB, red past 10 GB.
- Fuzzy search, local or recursive, with results ranked as they arrive.
crgfindsCargo.toml. - Nothing touches the disk without a dialog. Create, rename, paste, Trash and permanent deletion all go through a confirmation you can back out of. Permanent deletion is not a keypress: you type the name of what you are destroying, and the dialog lists every item with its size first.
- Image previews — PNG, JPEG, GIF, WebP, BMP and SVG — drawn with the terminal's graphics protocol where one exists, and unicode halfblocks everywhere else.
- Create files and folders from one key.
aasks for a name, and the name decides:notes.mdmakes a file,docs/makes a folder,docs/notes.mdmakes both.Agoes straight to a folder.Tabcycles the common extensions. - Run a command without leaving.
tasks for one, runs it in the folder you are looking at and brings the output back into a scrollable box.Esckills it while it runs, and the listing is reread afterwards, so what the command changed is on screen the moment it finishes. - A terminal wherever you are, in a tab or beside you.
Topens one in the folder you are looking at andCopens one with Claude Code already running. Both ask where first: a new tab, a split to the right, or a split below. - Copies to the system clipboard.
yputs the selection on the pasteboard as real files, so ⌘V pastes them in Finder or anywhere else. - Sends deletions to the system Trash by default. Bypassing it is a separate, louder key.
- A preview column that reads what it finds: the first lines of a text file, the contents of a folder, metadata for a binary.
You need a Rust toolchain with edition 2024 support — Rust 1.85 or newer.
git clone https://github.com/Jsuaaa/FileManager.git
cd FileManager
cargo install --path .That installs the fm binary. (The package is named FileManager; the binary
and the library are both fm.)
Developed and tested on macOS. It should build on Linux — nothing here
depends on more than Unix permissions and symlinks — but it is not tested
there, and the terminal detection behind T and C knows only about macOS
terminals; elsewhere, set FM_TERMINAL (see below). Copying to the system
clipboard with y is macOS-only for the same reason. Windows is not
supported.
fm # open the current folder
fm ~/Downloads # open a specific one
fm --icons # use Nerd Font icons instead of unicode glyphs
fm --help # print the help and exit, without entering the interfaceWithout --icons, folders are marked with ▸ and files carry no glyph, so it
renders correctly in any font with no setup. With --icons, folders and the
common file types (Rust, TOML/YAML/JSON, Markdown, images, archives) use Nerd
Font glyphs — which means you need one installed and selected in your terminal.
Three environment variables cover what depends on which terminal you are running:
| Variable | What it does |
|---|---|
FM_TERMINAL |
The command T runs to open a terminal, with {} standing for the folder and {cmd} — optional — for what runs in it: e.g. FM_TERMINAL='wezterm start --cwd {} -- {cmd}'. Overrides the terminal detected from TERM_PROGRAM. |
FM_CLAUDE |
What C runs in the terminal it opens. Defaults to claude; set it to claude --continue, a wrapper, or a different tool entirely. |
FM_IMAGE_PROTOCOL |
Set to halfblocks to draw image previews with unicode blocks instead of the terminal's graphics protocol. |
Inside the app, ? opens the full list of shortcuts at any time.
^x means Ctrl+x.
MOVE
| Key | Action |
|---|---|
j k ↑ ↓ |
move the cursor |
h l ← → |
out of the folder / into it |
enter |
open (enters a folder, opens a file with the system app) |
g G |
first / last entry |
^d ^u |
half page down / up |
~ |
go to the home folder |
FILES
| Key | Action |
|---|---|
a |
new file or folder (Tab cycles extensions) |
A |
new folder (docs/2026/january makes the whole tree) |
r |
rename |
y |
copy to the system clipboard (⌘V pastes the files elsewhere) |
x |
cut (marks the source; nothing moves yet) |
p |
paste here |
d |
move to Trash |
D |
delete forever |
FIND
| Key | Action |
|---|---|
/ |
filter the folder you are looking at |
/* |
search the whole subtree (the * goes first) |
n N |
next / previous match of the last local search |
SELECT
| Key | Action |
|---|---|
space |
select or deselect the entry under the cursor |
^a |
select everything visible |
esc |
clear the selection |
VIEW
| Key | Action |
|---|---|
. |
show or hide hidden files |
s |
change the sort key: name → size → date → kind |
S |
reverse the current order |
R |
reload the folder from disk |
APP
| Key | Action |
|---|---|
t |
run a command here and show its output |
T |
open a terminal here — asks for a tab or a split |
C |
open one with Claude Code running — asks the same |
? |
this help |
q ^c |
quit; q answers even while an operation is running |
t opens a one-line dialog, and what you type runs in the folder you are
looking at. Enter starts it, the box turns into a spinner while it works, and
what it wrote comes back in a scrollable report — long output is cut, and the
box says how much it left out. A command that succeeds without writing anything
does not open a box at all: it just says so and rereads the folder.
Worth knowing before you reach for it:
Esckills the command, but it kills the shell it started — a command that had already handed off to other processes (annpm install, say) can leave those running.- There is no interactive terminal, so
vim,topand anything else that wants to take over the screen will not work. That is whatTandCare for. - It runs under
$SHELL -lc, which gets you thePATHfrom/etc/pathsand your.zprofile— but not what your.zshrcadds. Aliases, shell functions and anything amise activateor annvmputs on thePATHare not there.
Both put up the same one-key dialog: t for a new tab, l for a split to the
right, j for a split below — the same directions the rest of the app
navigates with. esc backs out.
Splits are Ghostty only, because that is where the recipe has been verified
against a running terminal. Ask iTerm2, Terminal.app or an FM_TERMINAL of
your own for one and you get a tab, and a warning saying so rather than silence.
cargo test # 262 passing, 3 ignored on purpose
cargo clippy --all-targets -- -D warnings
cargo build --release
tests/e2e/run.sh # the whole binary, keystroke by keystrokeThree layers, no cycles. fs/ is the core and does not know a terminal exists;
ui/ only reads state and draws; app/ is the only place an event becomes
a mutation. The core is tested without opening a terminal at all, and the
interface is tested by rendering onto ratatui's TestBackend and checking the
resulting buffer.
tests/e2e/run.sh is the one that is not a cargo test: it needs a release
binary and expect, and it drives the real interface in a pty of its own,
reading back the screen it painted. It covers the ten things the suite cannot
look at because the suite never draws anything.
Two documents go deeper, both written in Spanish:
docs/internals.md— why each feature behaves the way it does: the caps on image decoding, why the terminal tab goes through AppleScript, what a long operation leaves behind if you quit during it, and the known limitations.docs/context/README.md— architecture, the file map, the non-obvious design decisions, the environment traps and what is and is not covered by tests.
The code itself — identifiers, comments, tests and every string that reaches the screen — is written in English.
MIT. See LICENSE.