XeFM (Xenolith File Manager) is a powerful dual-pane file manager that runs both as a native desktop app (Windows, macOS) and in the terminal (Windows, macOS, Linux). It provides efficient file management with cloud storage integration, advanced search capabilities, and extensive customization options.
Desktop app (Windows, macOS). Install from the
Microsoft Store on Windows, or
download the -macos.dmg from the
latest release on macOS —
Python is bundled in. This is the recommended way to run XeFM on the desktop;
see the Desktop Mode Guide.
Terminal app (Windows, macOS, Linux). From PyPI:
pipx install xefm # or: uv tool install xefm, or: pip install xefm
xefm
pipx and uv keep XeFM in
its own environment while putting the xefm command on your PATH — the right
shape for an application. Or run uvx xefm to try it once without installing
anything. Python 3.10 or later is all you need; every dependency, including the
platform-specific ones, comes with it.
The two installs coexist and share ~/.xefm/; installing both is a perfectly
normal setup. The README covers working from a
source checkout.
Use the tool you installed with, so pick the row you started from:
| Installed with | Upgrade | Uninstall |
|---|---|---|
pipx install xefm |
pipx upgrade xefm |
pipx uninstall xefm |
uv tool install xefm |
uv tool upgrade xefm |
uv tool uninstall xefm |
pip install xefm |
pip install --upgrade xefm |
pip uninstall xefm |
These are not interchangeable: each tool only knows about what it installed
itself. uv tool upgrade xefm on a pipx- or pip-installed copy fails with
`xefm` is not installed, and uvx xefm installs nothing at all, so there
is never anything for it to upgrade.
The desktop packages upgrade through their own channel: the Microsoft Store updates automatically, and on macOS you install the new DMG over the old app.
xefm: command not found after pip install xefm — the console script landed
in an environment that is not on your PATH; pipx install xefm or
uv tool install xefm handle that for you (or run it as python3 -m xefm)error: externally-managed-environment from pip —
PEP 668 is protecting a system Python
(Homebrew, Debian/Ubuntu); use pipx, uv, or a virtualenv`xefm` is not installed from uv tool upgrade xefm — uv only manages
what uv tool install put there, so this means pipx or pip owns your copy
(or uvx ran it without installing). Upgrade with the tool you installed
with — see the table aboveFirst run: arrow keys navigate, Tab switches panes, F1 opens help, Q
quits.
XeFM can run as a native desktop application with GPU acceleration, providing a modern windowed experience while maintaining the same keyboard-driven interface.
Install the desktop package (see Installation) and open XeFM from Launchpad / Spotlight on macOS, or the Start menu on Windows.
Running xefm --backend gui from a terminal opens the same window, but that is
the development path — it gives the wrong app icon and, on macOS, attributes
file permissions to your terminal rather than to XeFM. See
Why not xefm --backend gui?.
Desktop mode provides several advantages over terminal mode:
Desktop-mode font settings live in ~/.xefm/config.py (they are ignored in terminal mode):
# GUI fonts and size — the grid is derived from the monospace face
MONO_FONT_NAME = 'Menlo' # monospaced face for aligned columns (None = bundled default)
UI_FONT_NAME = None # proportional face for names/labels (None = bundled/OS default)
FONT_SIZE = 12 # point size applied to both faces (8–72)
The window’s size and position are remembered automatically across runs (via the native macOS window autosave) — there are no window-geometry config keys.
Common monospace fonts on macOS:
Menlo (default) - Apple’s default monospace fontMonaco - Classic Mac monospace fontSF Mono - San Francisco Mono (if installed)Courier New - Traditional monospace fontFira Code - Popular programming font (if installed)JetBrains Mono - Modern programming font (if installed)The backend is chosen only by the --backend flag; there is no configuration-file
preference. The default is terminal mode:
--backend tui — terminal, the default (--backend curses selects the
classic curses renderer instead, as a fallback for unusual terminals)--backend gui — native window on Windows (alias windows) or macOS (alias macos)On Linux there is no desktop backend; use terminal mode.
It is one keymap in both terminal and desktop mode. Two actions get a second,
platform-native chord in a desktop window — opening a file with the OS app, and
copying text on macOS — and everything else is shared; see “Three cases” in
~/.xefm/config.py.
Press F1 at any time for the built-in help, which is built from your own
KEY_BINDINGS and is therefore always the truth for your config.
Desktop mode provides excellent performance:
Desktop mode doesn’t start:
Window doesn’t appear:
python3 -c "import objc; print('OK')"python3 -m xefmFont issues:
Font Book.app to check installed fontsMONO_FONT_NAME from config (or set it to None)Performance issues:
When you first run XeFM, you’ll see:
switch_pane: Switch between left and right panesopen_item: Enter a directory or view a text filego_parent: Go to the parent directorygo_root: Go to the root of the current drive or locationhelp: Show the help dialog — and the key every other action is onPress F1 (help) first: that dialog is built from your own KEY_BINDINGS, so
it is the one list of keys that is always right for your config.
See detailed documentation: Status Bar Feature
See detailed documentation:
| Action | What it does |
|---|---|
toggle_select_down |
Select/deselect the file and step down |
select_range |
Select everything between the nearest selected item and the cursor |
copy_files |
Copy selected files to the other pane |
move_files |
Move selected files |
create_directory |
Create a directory (the same key, with nothing selected) |
delete_files |
Delete selected files |
rename |
Rename a file (or batch-rename several) |
edit_file |
Edit the selection (or the focused file) in the external editor |
create_file |
Create a new file |
See detailed documentation:
toggle_select_down selects individual filesselect_range selects a whole run: mark the first file, move the cursor to
the last one, and everything between them is selectedtoggle_select_files selects/deselects all filestoggle_select_items selects/deselects all items (files + directories)cursor_next_selected / cursor_prev_selected jump between what you selectedselect_range takes its anchor from the nearest selected item above the cursor,
or — when nothing above is selected — the nearest one below, so it reads the same
whether you moved down or up from the file you marked. It only ever adds: items
outside the run keep whatever they were, so several runs add up.
Getting the cursor to the far end of the run can be a search as well as an arrow
key: run isearch, type enough of the name, and isearch.accept (Enter) leaves
the search with the cursor on the file it found — then select_range fills the
run. The search bar has no range key of its own, deliberately: its cursor is a
match, but the item you marked earlier is not, so a range there would take in
files the pattern never matched. Leave the search first and the key means what it
says.
Its default key is Shift-Space, and a terminal cannot deliver that one: there
is no room for a modifier on a printable key, so a POSIX terminal reports a plain
space and toggles one item instead. In Terminal.app, iTerm2 or a Linux terminal,
bind it to a chord the terminal can carry, or use Select → Select to Cursor:
KEY_BINDINGS = {
'select_range': ['Ctrl-LEFT'], # or 'F6', 'Ctrl-Y' …
}
See detailed documentation: Key Bindings Feature
See detailed documentation: Batch Rename Feature
See detailed documentation: File Operations Feature
The arrows move the cursor and nav_left / nav_right walk between the panes
and in and out of directories; open_item enters a directory or views a file,
go_parent goes up, go_root jumps to the root of the current drive or
location, and cursor_top / cursor_bottom go to the first / last item.
| Action | What it opens |
|---|---|
favorites |
Your favorite directories |
jump_to_path |
Jump to a path (a file path lands the cursor on the file) |
history |
Directories this pane has visited |
sync_current_to_other |
Point the other pane at this one’s directory |
sync_other_to_current |
Point this pane at the other one’s directory |
See detailed documentation:
| Action | What it does |
|---|---|
isearch |
Incremental search (match as you type) |
find_files |
Threaded filename search dialog |
find_in_files |
Content search (grep) dialog |
filter |
Filter the pane (a pattern, or a filter from your config) |
clear_filter |
Clear the current filter |
sort opens the sort dialog (key + order), and quick_sort_name,
quick_sort_ext, quick_sort_size and quick_sort_date each sort in one press.
See Sort Dialog Feature for the dialog’s controls (a key is chosen by its initial; Left/Right choose ascending/descending).
Run isearch, then type. While the search bar is open:
| Action | What it does |
|---|---|
isearch.prev_match / isearch.next_match |
Previous / next match |
isearch.toggle_select_down |
Select the file, then move to the next match |
isearch.select_matches |
Select every match at once — again to clear them |
isearch.accept |
Stop at the current match |
isearch.cancel |
Cancel and go back to where the cursor was |
The search stops where its matches do: a character that would leave nothing
matching is refused, so the pattern stays on the last file it found instead of
running on into an empty list. Backspace always takes you back out. (Typing
romaji for Japanese keeps a couple of characters of leeway, since ni finds
Japanese only once it is nih — see Migemo Search.)
Space types a space — it separates the pattern’s words (re 24 finds
report_2024.txt), which is why marking a file here is Ctrl-Space rather than
the file list’s bare Space: Space selects, and Ctrl is what makes a command of a
key that would otherwise type. Marking backwards has no default key in either
surface; bind isearch.toggle_select_up if you want it.
isearch.select_matches marks the whole set the counter on the right is showing:
type .log, run it, and every log file is selected. Files selected outside the search are left alone, so a second search
adds to them. Every one of these keys can be rebound; see
Customization.
kensaku
also finds 検索 — no IME needed. See Migemo Search.*.txt or test_*. Several
patterns separated by spaces (or ;) show a file matching any of them —
*.jpg *.png *.svg; quote a pattern that contains a space: "my file*"filter prompt also lists the filters your config
defines, pinned under clear filter. They can match on anything about a file,
not just its name — “modified today”, “over 100 MB”. See
Customization.find_files): The query is an exact glob matched against the
whole filename — report.txt matches only that name. Add wildcards for partial
matches: report*, *.py, or *report* for the old “contains” behaviour.*.txt and only .txt files are grepped (subdirectories are still walked).
The dialog title shows the pattern while it applies. Unicode files with a
BOM (UTF-8, UTF-16, UTF-32) are searched as text, not skipped as binary.quick_sort_* actions sort in one pressSee detailed documentation: Search Animation Feature
view_file opens the focused file in the built-in viewer; open_item does too,
for a file with no enter rule of its own.
The arrows, paging and Home/End scroll; toggle_wrap turns line wrapping on and
off, toggle_view_mode switches between the rendered and raw view of a Markdown
or other rich file, change_encoding picks the text encoding, isearch searches
inside the file and edit_file hands it to your editor. quit (or Esc) closes
the viewer. The viewer draws its own keys along the bottom, and F1 inside it
lists them all — see
Text Viewer Feature.
edit_file edits the selection (or the focused file); create_file makes a new
one.
With several files selected, edit_file opens them all — files sharing an editor
are passed to it in one launch (vim a.txt b.txt).
Configure your preferred editor in ~/.xefm/config.py:
TEXT_EDITOR = 'vim' # or 'nano', 'code', etc.
See detailed documentation: Text Editor Feature
subshell opens a shell in the current directory.
In terminal mode, exit the shell to return to XeFM. The desktop app has no
terminal of its own to hand over, so it opens the shell in a terminal window
instead — see External Terminal. The shell sees the XEFM_* environment
variables (pane directories and selections) and a [XeFM] prompt prefix.
The prefix is passed via the PS1/PROMPT environment variables, so a shell
whose startup files set their own prompt overwrites it. zsh always does — on
macOS, /etc/zshrc resets the prompt for every interactive shell, even if you
have no ~/.zshrc — so key off XEFM_ACTIVE at the end of your
~/.zshrc instead:
if [[ -n $XEFM_ACTIVE ]]; then
PROMPT="[XeFM] $PROMPT"
fi
A framework that rebuilds the prompt before every command (powerlevel10k,
starship) overwrites even this; put the marker in its own config instead —
e.g. starship’s env_var module, or a custom powerlevel10k segment.
Which variable carries the prefix follows the shell being launched: cmd.exe
reads PROMPT in its own $-code syntax, so it gets [XeFM] $P$G (or your
existing PROMPT, prefixed). PowerShell builds its prompt from a prompt
function that no environment variable can reach, so it gets no prefix — define
one in your profile keyed off XEFM_ACTIVE:
if ($env:XEFM_ACTIVE) {
function prompt { "[XeFM] $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " }
}
By default XeFM launches $SHELL, falling back to the platform default
(cmd.exe on Windows, /bin/sh elsewhere). Override it in
~/.xefm/config.py:
SUBSHELL = 'zsh' # a single command...
SUBSHELL = ['powershell', '-NoLogo'] # ...or a command with arguments
XeFM provides native AWS S3 integration for seamless cloud storage management. For comprehensive S3 documentation including setup, usage, troubleshooting, and advanced features, see the AWS S3 Support Feature Guide.
aws configure, the AWS_ACCESS_KEY_ID /
AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION environment variables, or an
IAM role (nothing to set up on EC2)# Navigate to S3 bucket
s3://my-bucket/
# Navigate to specific path
s3://my-bucket/path/to/files/
# Copy local files to S3
# 1. Select files in left pane (local directory)
# 2. Navigate right pane to s3://bucket/path
# 3. Press 'c' to copy
# View S3 text file
# 1. Navigate to s3://bucket/file.txt
# 2. Press 'v' to view (editing needs a local copy)
subshell enters sub-shell mode (in the desktop app, in a terminal window —
see External Terminal) with environment variables:
XEFM_LEFT_DIR: Left pane directoryXEFM_RIGHT_DIR: Right pane directoryXEFM_THIS_DIR: Current pane directoryXEFM_OTHER_DIR: Other pane directoryXEFM_THIS_SELECTED: Files selected in the current pane — empty when nothing
is selectedXEFM_THIS_FOCUSED: The item under the cursor in the current pane, whatever
is selected (*_FOCUSED exists for the other three panes too)programs shows the external-programs menu. Programs have access to XeFM
environment variables.
See detailed documentation: External Programs Feature
adjust_pane_left / adjust_pane_right move the boundary between the panes and
reset_pane_boundary puts it back at 50/50; adjust_log_up / adjust_log_down
resize the log pane and reset_log_height restores it.
diff_files shows the diff between two selected text files, diff_directories
compares the two panes’ current directories recursively, and
compare_selection opens the file and directory comparison options.
See detailed documentation: Diff Viewer Feature (file and directory diff)
toggle_hidden shows or hides hidden files.
Other display settings — sorting, hidden files, themes — are also in the menu bar under View.
Color themes are switched from the menu bar (View → Theme); assign a key to
toggle_color_scheme in ~/.xefm/config.py to cycle them from the keyboard.
XeFM shows animated progress indicators during long-running operations like searching files.
See detailed documentation: Search Animation Feature
XeFM creates ~/.xefm/config.py on first run. Access it via:
~/.xefm/config.py directlyFor comprehensive configuration documentation, see the Configuration Feature Guide which covers all available options, examples, and best practices.
XeFM supports powerful key binding customization with modifier keys and multiple keys per action:
KEY_BINDINGS = {
# An action can have several keys — add your own alongside the defaults
'quit': ['Q'],
'help': ['F1'],
# e.g. add vim-style movement next to the arrow keys
'cursor_up': ['UP', 'k'],
'cursor_down': ['DOWN', 'j'],
# Modifier key combinations
'page_up': ['PAGE_UP', 'Shift-UP'],
'page_down': ['PAGE_DOWN', 'Shift-DOWN'],
# Extended form with a selection requirement
'delete_files': {
'keys': ['K', 'DELETE'],
'selection': 'required' # only when files are selected
},
'create_directory': {
'keys': ['M'],
'selection': 'none' # only when nothing is selected
},
}
Key features:
See detailed documentation: Key Bindings Feature
XeFM ships with several built-in themes (Dark+, Light+, Monokai, Dracula, Nord,
Solarized, Gruvbox Dark, Solarized Light) and remembers the last one you used.
Cycle themes at runtime with the T key, or pick one from View → Theme.
Define your own with the THEMES dict in config.
See detailed documentation: Color Schemes Feature
FAVORITE_DIRECTORIES = [
{'name': 'Projects', 'path': '~/dev'},
{'name': 'Documents', 'path': '~/Documents'},
{'name': 'S3 Bucket', 'path': 's3://my-bucket/'},
]
See detailed documentation: Navigation Dialogs Feature
PROGRAMS = [
{'name': 'Git Status', 'command': ['git', 'status']},
{'name': 'Open in VSCode', 'command': ['code', '.']},
{'name': 'View with less', 'command': ['less'],
'options': {'terminal': True}},
]
See detailed documentation: External Programs Feature
These apply to the terminal install and to source checkouts. The desktop packages take no command-line arguments — their launcher starts the native backend directly.
python3 -m xefm # Terminal mode — the default
python3 -m xefm --left ~/projects # Set the left pane's startup directory
python3 -m xefm --right ~/docs # Set the right pane's startup directory
--backend chooses the rendering backend:
python3 -m xefm --backend tui # Terminal — default (--backend curses: classic curses fallback)
python3 -m xefm --backend gui # Native window (aliases: --backend macos / windows)
--backend gui is the development path for desktop mode; for everyday use
install the desktop package instead
(why).
--backend {tui,curses,gui,macos,windows} # Rendering backend (default: tui)
--left DIR # Left pane startup directory
--right DIR # Right pane startup directory
--version # Show version and exit
--help # Show help and exit
# Desktop window with custom startup directories (from a checkout)
python3 -m xefm --backend gui --left ~/projects --right ~/docs
Startup directories set with --left/--right override any saved pane history for that session; an invalid path falls back to the saved (or home) directory.
python3 --version (3.10+ required)xefmDesktop mode only works on macOS. On other platforms, XeFM automatically falls back to terminal mode.
Check your terminal’s color support and TERM environment variable
Check terminal Unicode support and locale settings
See detailed documentation: Wide Character Support Feature
Check terminal key mappings and ESCDELAY setting
Verify AWS credentials and bucket permissions
Check file permissions and disk space
pygments for faster syntax highlighting--help command line optionSee detailed documentation: Help Dialog Feature
For detailed information about specific features, see these dedicated guides:
The reference is in XeFM. Press F1 for the help dialog: it is generated from
the keymap your config actually produced, action by action, so it can never drift
from what your keys do. The menu bar shows the same keys next to the items they
run.
This guide names actions rather than keys for that reason — copy_files,
find_files, jump_to_path — and one place holds the keys they ship on:
~/.xefm/config.py, which is a copy of
xefm/_config.py with every default written out and
commented. Reading that file is how you learn the defaults; editing it is how you
change them.
All key bindings can be customized in your configuration file (~/.xefm/config.py). The enhanced key binding system supports:
ENTER) and bare letter keys (q = Q) bind the same physical key; use Shift-Q for the shifted variantExamples:
KEY_BINDINGS = {
'page_up': ['PAGE_UP', 'Shift-UP'], # Two ways to page up
'cursor_up': ['UP', 'k'], # add a vim-style alternative
'delete_files': {
'keys': ['K', 'DELETE'],
'selection': 'required' # Only when files selected
},
}
See the Configuration section and Key Bindings Feature for complete documentation.
This comprehensive user guide covers all aspects of using XeFM effectively. For technical implementation details, see the developer documentation in the doc/dev/ directory.