A keyboard-driven, dual-panel file manager for the terminal, written in Rust (ratatui + crossterm). It recreates the look, layout, and workflow of Norton Commander 5.5 (1998) — blue/cyan panels, double-line frames, a function-key command bar, and an always-available command line — with a small set of modern extras layered on top: quick filter, fuzzy directory jump, panel tabs, git-aware panel info, mouse support, and switchable themes.
Platform: Windows-first (Windows Terminal / PowerShell console / conhost).
The codebase stays cross-platform via crossterm, but only Windows is tested
and supported.
What changed in each release is in CHANGELOG.md.
- Installing
- Building from source
- Command-line options
- Configuration
- Using FileCommand
- Diagnostics
- Project layout
- Testing
- Contributing
- Reporting issues
- License
The quickest way is a package manager:
winget install BigHatGroup.FileCommand
choco install filecommandwinget installs per-user by default (--scope machine for all users).
Chocolatey installs machine-wide by default, following its convention;
add --params "'/Scope:user'" for a per-user install. A new version can
take a few days to appear in either catalog after its GitHub release,
while it goes through their review.
Or use the installer directly:
-
Download
FileCommandSetup.exefrom the latest release, or build it — seeinstaller/README.mdfor build instructions and prerequisites (Rust, .NET SDK, WiX v4/v5 CLI). -
Run it. By default it installs per-user, with no elevation, to
%LocalAppData%\Programs\BigHatGroup\FileCommand, adds itself to your userPATH, and creates a Start Menu shortcut. -
Open a new terminal window (existing ones won't see the updated
PATH) and run:filecommand
For an elevated, machine-wide install instead:
FileCommandSetup.exe /quiet InstallScope=perMachineSilent uninstall:
FileCommandSetup.exe /uninstall /quietSee installer/README.md for the full scope
semantics (per-user vs. per-machine, upgrade behavior, production code
signing) and the winget package template.
Requires the Rust toolchain, stable 1.88 or newer.
The workspace declares rust-version = "1.88", so an older toolchain stops
with cargo's standard message naming the required version. Windows is the
primary target; Linux and macOS builds are best-effort. They need a C
compiler (for the vendored libgit2 and zlib) but no OpenSSL development
headers, because git2 is built without its network transports — git info
only reads local repository state.
git clone https://github.com/kkaminsk/FileCommand.git
cd FileCommand
cargo build --releaseRelease builds use thin LTO, a single codegen unit and stripped symbols; panics still unwind, which the terminal-restoring guard relies on.
The binary is produced at target\release\filecommand.exe. Run it directly,
or via Cargo:
cargo run --releaseThe workspace has two crates:
filecommand-core— state, the reducer (core::update), file operations, listing/sorting/filtering, git info, config/theme loading. No terminal dependencies; fully unit-testable.filecommand-tui— thefilecommandbinary: the event loop, rendering, input mapping, and terminal ownership.
| Flag | Effect |
|---|---|
--theme <name> or --theme=<name> |
Launch with a specific theme for this session, overriding the saved theme = in config.toml. Built-in names: nc-classic, nc-mono, terminal-green, purple-lights, yellow-storm, inverted. |
--nosplash |
Skip the startup splash screen, overriding splash = true in config.toml. |
--nomouse |
Disable mouse capture for this session, overriding [mouse] enabled in config.toml. |
--noanimations |
Disable the decorative UI animations (splash entrance, menu and dialog openings, panel reveal) for this session, overriding animations = true in config.toml. |
--config-dir <path> or --config-dir=<path> |
Use <path> as the profile directory holding config.toml, history.json, and usermenu.toml for this session, overriding portable mode and the platform default (see Configuration). |
--log <path> or --log=<path> |
Append diagnostic messages to <path> for this session (see Diagnostics). Overrides the FILECOMMAND_LOG environment variable. |
Flags can be combined in any order, e.g. filecommand --nosplash --theme=purple-lights.
FileCommand reads and writes all of its files in one profile directory, resolved once at startup in this order:
--config-dir <path>— an explicit override for testing and scripting (a relative path resolves against the launch directory).- Portable mode — when a file named
portableexists next to the executable, the executable's own directory is the profile directory. - The platform default —
%APPDATA%\FileCommandon Windows,$XDG_CONFIG_HOME/filecommand(falling back to~/.config/filecommand) elsewhere.--helpprints the resolved default.
The directory is created on first run. If it can't be created, the session
runs on in-memory defaults with saving turned off, and a startup warning
names the path and the error. Because history.json lives in one place,
command history and the Ctrl+J fuzzy-jump frecency list are shared no
matter which directory FileCommand is launched from.
Coming from a per-directory layout? Earlier versions read and wrote
these files in the launch directory. FileCommand never moves or deletes
the old files: when the profile directory has no config.toml yet and the
launch directory still holds any of the three, a startup notice says where
FileCommand now reads from so you can move them yourself.
Missing files are created with defaults on first run; a malformed file produces a startup warning and falls back to defaults rather than being silently overwritten.
Flat key = value lines (not a full TOML parser, aside from the [mouse]
table below), one setting per line:
splash = true
animations = true
theme = "nc-classic"
shell = "cmd.exe /C"
editor = "code -w"
panel_split = 50
key.paste_name = "ctrl+enter"
key.paste_path = "ctrl+]"
key.quick_filter = "ctrl+p"
key.fuzzy_jump = "ctrl+j"
key.split_left = "ctrl+left"
key.split_right = "ctrl+right"
key.split_reset = "ctrl+="
key.clipboard_files = "ctrl+c"
key.clipboard_paths = "ctrl+shift+insert"
key.clear_line = "ctrl+y"
[mouse]
enabled = trueTop-level settings must come before any [table]: a key.* line placed
after [mouse] belongs to that table and is ignored (with a warning). A
# outside quotes starts a comment, on its own line or after a value
(splash = false # straight to the panels); quote any value that itself
contains #. A UTF-8 byte-order mark (Notepad's "UTF-8 with BOM") is
accepted.
| Key | Meaning | Default |
|---|---|---|
splash |
Show the startup splash screen. | true |
animations |
Run the decorative ASCII animations: the splash entrance, the F9 bar sweep, pop-up menus unfolding, dialogs opening, and the panel reveal after the splash. false shows every surface in its final form immediately. Loading feedback (directory listings, progress counters) is never animated. |
true |
theme |
Active theme name (one of the six built-ins — see Themes). | nc-classic |
shell |
Shell command line used by the file-action menu's Run entry and the F2 user menu. | cmd.exe /C on Windows, /bin/sh -c elsewhere |
editor |
External editor command for F4; unset means "use the built-in editor". | unset |
panel_split |
Left-panel width as a percentage. | 50 |
[mouse] enabled |
Whether mouse capture is enabled at all. | true |
key.* |
Overridable key bindings (see table below). | see below |
Only the bindings listed above are configurable; the rest of the key map
(F-keys, Tab, arrows, Ctrl+T/W, etc.) is fixed. A missing config.toml
is silent, but anything FileCommand cannot use — an unknown key or table,
a line that is not key = value, a setting under the wrong table, a
boolean or key binding it cannot read, or a file that is not UTF-8 text —
produces a one-line startup warning naming the file and line (with a
(+N more) count when there are several); the rest of the file still
applies. The warning dialog is dismissed with any key, and FileCommand
starts regardless. With --log, every warning is written to the log.
F2 user-menu entries — a real TOML array of tables:
[[entry]]
label = "Open command prompt here"
command = "cmd.exe"
[[entry]]
label = "Directory listing"
command = "dir"Comments (# …, including after a value or after [[entry]]) and a UTF-8
byte-order mark are accepted. An unknown key in an entry, a key outside an
[[entry]] block, or an entry missing label or command produces a
startup warning naming the line, and the rest of the menu still loads.
Unlike config.toml, a malformed usermenu.toml is treated as an error: it
falls back to a default menu and shows a warning rather than being silently
overwritten. A file that is not UTF-8 text (saved as ANSI or UTF-16) is
reported the same way — re-save it as UTF-8, or delete it to recreate the
defaults — and is never overwritten.
Command-line history and fuzzy-jump directory frecency data. Not meant to
be hand-edited. Navigation never waits on the disk: changes are written in
the background a few seconds after a burst of navigation settles (and
within ten seconds at most), and anything still pending is written when
FileCommand quits. Each write goes to a flushed temporary file that then
atomically replaces history.json, so an interruption leaves either the
previous or the new complete file. Before writing, FileCommand merges its
history with the file on disk, so two instances sharing a profile keep
each other's commands and directories. The directory history is capped at
1,000 entries (the least frecent are dropped first); visit times are
wall-clock, so Ctrl+J ranking stays meaningful across restarts, and a file
from an earlier version is migrated on first load.
- Left navigation pane — its own directory, cursor, selection, sort mode, filter, and display mode; the path renders inverse in the top border when this panel is active.
- Right navigation pane — independent of the left: its own mode, sort, filter, and tabs, so the two panels can browse entirely different locations at once.
- F-key menu — the function-key command bar (
1Help 2Menu 3View 4Edit 5Copy 6RenMov 7Mkdir 8Delete 9PullDn 10Quit); clickable with the mouse, and relabels under Ctrl/Alt for their key-bar variants.
- Two independent panels, each with its own directory, cursor, selection, sort mode, filter, and display mode.
- Tab switches which panel is active (the active panel's path renders inverse in its top border).
- Large directories stream in with a live
Reading… Ncount and stay usable while they load. - Junctions and directory symlinks list and open like directories. The
panel keeps the link's own path, so
..returns to the folder that holds the link. An entry whose details can't be read is left out, and the panel's status line says how many were skipped. - The command line sits below the panels and always shows the active
panel's path as its prompt. It understands three built-in verbs (
cd,del,rmdir) and nothing else — see Command line. - Terminal is usable down to a minimum size; below that, panels are replaced with a "terminal too small" placeholder and the F-key bar degrades through progressively shorter forms as width shrinks.
- The startup splash (product name/version banner) is the very first frame
unless
--nosplash/splash = false; any key dismisses it early. Its box traces in and its lines reveal within the 800 ms hold; turn that and the menu/dialog motion off with--noanimations/animations = false.
The line just above the F-key bar is an NC-style command line. Its prompt is
the active panel's current directory (e.g. C:\Projects\app>), and it
follows the active panel: press Tab or change directory and the prompt
updates.
The command line is a quick way to navigate and to delete a single item by
name. It is not a shell: it never passes text to cmd.exe/PowerShell,
and anything other than the three built-in verbs below is rejected. To run
programs, see Running programs.
You don't need to focus the command line. While the panels have the keyboard (no dialog, menu, type-ahead jump, or Ctrl+P quick filter open), every plain printable key is inserted into the command line at the caret and the panel cursor stays put. The caret moves with ←/→ while the line has text, so fixing one character in the middle of a command never means retyping its tail.
Some keys mean different things depending on whether the line is empty:
| Key | Line empty | Line has text |
|---|---|---|
| Printable character | Starts the line | Inserted at the caret |
+ / - / * |
Select / deselect by wildcard, invert selection | Typed as text |
| Backspace | Go to the parent directory | Delete the character before the caret |
| ← / → | Nothing | Move the caret |
| Ctrl+Backspace | Go to the parent directory | Delete the word before the caret |
| ↑ / ↓ | Move the panel cursor | Recall older / newer history |
| Enter | Act on the cursor entry (open a directory, file-action menu for a file) | Run the line |
| Ctrl+Enter / Ctrl+] | Paste the cursor entry's name / full path | Same, after a separating space; caret lands at the end |
| Ctrl+V | Paste the clipboard's text at the caret | Same (line breaks become spaces) |
| Ctrl+Y | Nothing | Clear the line (ends any history recall) |
| Esc | Ask to quit | Ask to quit (cancelling keeps your text) |
All other keys keep their panel meaning while you type. Home/End and
PgUp/PgDn move the panel cursor even mid-composition, Ctrl+←/→ adjust the
panel split, and Tab switches panels without clearing the line. Delete
opens the delete confirmation for the cursor entry; it does not delete a
character. To clear the line, press Ctrl+Y (rebindable via
key.clear_line in config.toml), or backspace it until
it's empty.
The same editing keys work in every text field — the F5/F6/F7 destination and name fields, the file-action menu's Rename, an overwrite-conflict's Rename, the Alt+F7 find pattern, and the Ctrl+J fuzzy-jump pattern. There Home/End and Delete are editing keys (jump the caret to the start/end, delete the character after it), Ctrl+←/→ move the caret by words, Ctrl+Backspace/Ctrl+Delete delete words, and both Shift+Ins and Ctrl+V paste the clipboard's text at the caret.
| Command | What it does |
|---|---|
cd <path> |
Navigates the active panel to <path>. If the target doesn't exist or isn't a directory, the command is rejected and the panel stays where it is. |
del <file> |
Opens the normal F8 delete confirmation for that one file. Rejected if the target is a directory. |
rmdir <dir> |
Opens the normal F8 delete confirmation for that one directory, including the second confirmation if it isn't empty. Rejected if the target is a file. |
Rules that apply to all three:
-
Verbs ignore case.
CD,Cd, andcdare the same command. -
Everything after the verb is one argument, so a name with spaces doesn't need quotes. Surrounding double quotes are removed, so
cd Program Filesandcd "Program Files"both work. -
Paths resolve against the active panel's directory. These forms are accepted:
Form Example Resolves to Relative cd src\core,cd ..\otherBelow or beside the current directory Current / parent cd .,cd ..Current directory / its parent ( cd ..at a drive root is rejected)Drive-rooted cd \Temp\Tempon the current driveAbsolute cd C:\Windows\System32That exact path Bare drive letter cd D:The root of D:(cmd would use D:'s last directory)UNC cd \\server\share\dirThe network path (this is how you enter a UNC location by hand) -
One target, no wildcards.
del *.tmplooks for a file literally named*.tmp. To delete several items, select them with Ins or+(select by wildcard), then press F8. -
del/rmdirnever delete on their own. They only open the same confirmation dialog as F8, and nothing is removed until you accept it. Deletes are permanent (there's no recycle bin)..and..are always rejected as targets, so the dialog can never offer to delete the panel's own directory or its parent. -
A verb needs an argument. A bare
cd(no "print current directory" form) is rejected as unrecognized.
Pressing Enter always clears the line. A rejected command shows its reason in the active panel's bottom-border status line, where it stays until the panel next lists a directory successfully:
| Message | Cause |
|---|---|
'dir' is not a recognized command |
Anything other than cd / del / rmdir with an argument |
C:\Projects\nosuch not found |
The target doesn't exist |
C:\Projects\readme.txt is not a directory |
cd or rmdir on a file |
C:\Projects\docs is a directory |
del on a directory |
rmdir: invalid target .. |
. or .. given to del / rmdir |
Each cd that succeeds is saved to the command history in history.json,
which keeps the most recent 200 entries. Running a command that's already
saved moves it to the newest position. Rejected lines and del/rmdir
aren't saved.
Up/Down only recall history while the line has text (on an empty line they move the panel cursor). To browse history from an empty line, type a space and press ↑:
- Each ↑ steps to an older entry, and each ↓ steps to a newer one.
- A recalled entry replaces the whole line. History isn't filtered by what you've typed.
- Pressing ↓ past the newest entry stops recalling and leaves the text as it is.
- Press Enter to run the recalled entry, or Backspace to edit it.
To fill in a command from the panel instead of typing names:
- Ctrl+Enter adds the cursor entry's file name to the end of the line.
- Ctrl+] adds the cursor entry's full path.
If the line doesn't already end in a space, one is added first. For example, to delete the file under the cursor in the other panel's directory:
- Type
del. - Press Tab, then Ctrl+] to paste the file's full path.
- Press Enter.
Ctrl+] works in every terminal. Ctrl+Enter works on Windows, and elsewhere
only when the terminal supports the kitty keyboard protocol. You can rebind
both with key.paste_name and key.paste_path in
config.toml.
The command line can't run programs, so dir, notepad notes.txt, and
git status are all rejected. There are three ways to launch something:
-
Open. Press Enter on a file and choose Open — the first, highlighted entry for a non-executable (so Enter, Enter opens a document), second under Run for an executable; on a directory (right-click menu) Open shows the directory in the system file browser. Open launches the target through the platform's file association (
ShellExecutewith theopenverb on Windows,xdg-open/openelsewhere) without suspending FileCommand and without waiting — a GUI program simply appears beside the terminal, and a launch failure (no association for the extension, a missing target) is reported on the panel's status line. -
The file-action menu's Run. Press Enter (or right-click) on an executable (any
PATHEXTextension, or a.lnk) and choose Run — the first entry for executables. Enter never starts an executable directly. -
The F2 user menu. Add an entry to
usermenu.toml:[[entry]] label = "git status" command = "git status"
The command string goes to the configured
shellexactly as written, with no placeholder substitution.
Run and the user menu suspend FileCommand's screen and run the command in
the active panel's directory using the shell from config.toml
(cmd.exe /C by default; PowerShell adds about 200 ms or more of startup
per command) — the suspension is what makes a console program's output
visible, so Run remains the right choice for console tools. (Open on a file
type associated with a console program — say .py → python.exe —
attaches it to FileCommand's console instead, and its output interleaves
with the panels.) When the command finishes, press a key to return.
Afterwards, Ctrl+O
hides the panels so you can read the output in your terminal's scrollback,
and any key brings the panels back. FileCommand doesn't keep its own copy of
command output; only your terminal's scrollback does.
Navigation & panels
| Key | Action |
|---|---|
| ↑ / ↓ / PgUp / PgDn / Home / End | Move cursor (page size follows panel height) |
| Tab | Switch active panel |
| Enter | Directory: enter it. ..: go to parent. File: opens the file-action menu (Open/View/Edit/Send to clipboard/Copy/Rename/Move/Delete; executables list Run first, then Open). Command line non-empty: dispatches the built-in verbs — cd navigates, del/rmdir open delete-confirmation, anything else is rejected. Tree mode: navigate/expand. |
| Backspace (empty command line) | Parent directory |
| Ctrl+PgUp | Parent directory |
| Ins | Toggle selection at cursor, cursor advances |
| + / - / * | Select by wildcard / deselect by wildcard / invert selection |
| Alt+letter | Start type-ahead jump to the first matching entry |
| Ctrl+P | Toggle the inline quick filter (narrows the panel to a substring match as you type) |
| Ctrl+J | Fuzzy directory-jump dialog (frecency-ranked, persists across sessions) |
| Alt+F7 | Find file |
| Ctrl+R | Re-read (refresh) the active panel |
| Ctrl+L | Toggle Info display mode on the active panel |
| Ctrl+O | Show terminal scrollback (leaves the alternate screen; any key returns) |
| Ctrl+T / Ctrl+W | New tab / close tab on the active panel |
| Ctrl+U | Swap the two panels' entire state (directories, tabs, listings, cursors, selections, modes); the active side and split stay put |
| Alt+1..9 | Switch to tab n on the active panel |
| Ctrl+←/→ | Shrink / grow the left panel (adjust the split) |
| Ctrl+= | Reset the split to 50/50 |
| Alt+F1 / Alt+F2 | Drive select for the left / right panel |
| Ctrl+F3..F6 | Sort active panel by Name / Extension / Time / Size |
| Ctrl+F7 | Unsorted (raw enumeration order) |
File operations
| Key | Action |
|---|---|
| F3 | View file under cursor |
| F4 | Edit file under cursor (external editor if configured, else the built-in editor; large files open in the viewer instead) |
| F5 | Copy |
| F6 | Rename/Move |
| F7 | Make directory |
| F8 | Delete (confirmation; a second confirmation for non-empty directories) |
| Ctrl+C or Ctrl+Ins | Copy the cursor entry (or selection) to the Windows clipboard as file objects, pasteable into Explorer |
| Ctrl+Shift+Ins | Copy the cursor entry's (or selection's) absolute path(s) to the clipboard as text |
| Ctrl+Enter | Paste the cursor entry's file name onto the command line |
| Ctrl+] | Paste the cursor entry's full path onto the command line |
General
| Key | Action |
|---|---|
| F1 | Help |
| F2 | User menu (from usermenu.toml) |
| F9 | Open the pull-down menu bar |
| F10 or Esc | Request quit (Y/N confirmation) |
| Esc (in a dialog/menu/overlay) | Cancel or close it |
| Up / Down (command line non-empty) | Recall previous/next command-line history |
| Alt+F8 | Command-history dialog: newest first; Enter places the highlighted entry on the command line (it does not run it), Esc closes |
| Left / Right (command line non-empty) | Move the command-line caret |
| Ctrl+Backspace (command line non-empty) | Delete the word before the command-line caret |
| Ctrl+V | Paste the clipboard's text into the command line (or the focused dialog field) |
Text fields (destination/name dialogs, Rename, overwrite-conflict Rename, Alt+F7 find pattern, Ctrl+J fuzzy-jump pattern)
| Key | Action |
|---|---|
| ← / → / Home / End | Move the caret one char / to the start / to the end |
| Backspace / Delete | Delete the character before / after the caret |
| Ctrl+← / Ctrl+→ | Move the caret one word back / forward |
| Ctrl+Backspace / Ctrl+Delete | Delete the word before / after the caret |
| Shift+Ins or Ctrl+V | Paste the clipboard's text at the caret (line breaks become spaces) |
All of the bindings marked overridable in Configuration
(paste_name, paste_path, quick_filter, fuzzy_jump, split_left,
split_right, split_reset, clipboard_files, clipboard_paths,
clear_line) can be
remapped in config.toml; the rest of the table above is fixed.
Enabled by default; disable with [mouse] enabled = false or --nomouse.
Mouse is only honored in contexts a key press would also reach (panels, the
key bar, menu titles/items, and dialog buttons) — it does nothing in the
viewer/editor beyond wheel scrolling, and nothing at all while an
unsupported overlay (drive select, fuzzy jump, find file, user menu,
command history, theme picker, help, startup warning) is open.
| Gesture | Action |
|---|---|
| Left click on an entry | Focus that panel and move the cursor to the entry |
| Ctrl+left click on an entry | Toggle that entry's selection in place |
| Double left click on an entry | Same as Enter |
| Left click on empty panel area / title | Focus that panel |
| Right click on an entry | Open the file-action menu for it (same entries as Enter for a file; a directory lists Open/Send to clipboard/Copy/Rename/Move/Delete, with Open showing it in the system file browser) |
| Left-drag from an entry onto a target | Propose a Copy |
| Right-drag, or Shift+drag, from an entry | Propose a Move |
| Esc during a drag | Cancel the drag |
| Scroll wheel over a panel | Scroll 3 lines |
| Click a key-bar slot | Activate that F-key |
| Click a menu-bar title / pull-down item | Open / activate it; clicking outside an open pull-down closes it |
| Click a dialog button | Activate it |
Five menus, navigated with ←/→ (between menus), ↑/↓ (within one), Enter, Esc, or a hotkey letter. Every listed item works:
- Left / Right (mirror each other, acting on their own panel): display mode (Brief/Full/Tree/Quick view/Info), sort mode, Filter (the panel's quick filter), re-read, drive select, new/close tab.
- Files: View, Edit, Copy, Rename/Move, Make directory, Delete, copy to clipboard (files/paths/names), Select/Deselect/Invert group, Quit.
- Commands: Find file, History (the Alt+F8 command-history dialog), Swap
panels (Ctrl+U), Panels on/off, Fuzzy jump, Menu file edit (opens
usermenu.tomlin the F4 editor and reloads the F2 menu on return). - Options: Themes (opens the live theme picker).
| Mode | Description |
|---|---|
| Full (default) | Name | Size | Date | Time columns |
| Brief | Three columns of names only |
| Info (Ctrl+L) | Drive/system/directory info instead of a listing |
| Tree | Lazily-expanded directory tree of the current drive; moving the cursor updates the opposite panel's listing |
| Quick view | Live preview (text head) of the file under the opposite panel's cursor |
In Full mode, numeric sizes are right-aligned in the Size column (the Norton
Commander convention) and every value renders complete — ▶SUB-DIR◀,
MM-DD-YY dates, and HH:MM times are never truncated.
Hidden and system entries are still listed — there is no show/hide toggle —
but render dimmed in both Full and Brief modes: on Windows, entries carrying
the hidden or system attribute; on other platforms, names beginning with
.. Selection and the cursor bar take precedence over the dimming.
Copy (F5), Move/Rename (F6), Mkdir (F7), and Delete (F8) run as cancellable background jobs with a progress dialog (file/byte counts, a cancel button). Overwrite conflicts prompt Overwrite/Skip/Rename/Overwrite All/Skip All, or Cancel (Esc or C) to stop the whole job; errors (permission denied, path too long, disk full, sharing violation) pause the job for Retry/Skip/Skip All/Abort, and skipped files are listed in a summary at the end. Quitting while a job runs cancels it and waits briefly for its cleanup before exiting.
Copies are written to a temporary <name>.~fc-partial file next to the
target and renamed into place only once complete, so a failed or cancelled
copy never leaves a truncated file, and a failed Overwrite keeps the
original. Copies keep attributes, timestamps, and alternate data streams.
Copying a file onto itself is skipped rather than truncating it.
Same-volume moves are instant renames. Cross-volume moves flush and verify
each copied file before deleting its source, and never delete anything
inside a junction or symlink target: only the link itself is removed.
There is no recycle bin — deletes are permanent, and the confirmation
dialog says so.
- Viewer — read-only, opens instantly at any file size (reads bounded windows rather than indexing). F2 toggles wrap, F4 toggles text/hex mode, F7 searches, F10/Esc closes.
- Editor — F4 opens your configured external editor if
editor =is set inconfig.toml; otherwise the built-in editor (files under 10 MB; larger files open in the viewer instead). The built-in editor edits UTF-8 text only: a file that is not valid UTF-8, is UTF-16, or contains NUL bytes opens in the viewer with a notice instead (a UTF-8 byte-order mark is fine and is kept on save). F2 saves, F3 marks a line-selection anchor, F4 opens search-and-replace, F7 searches, Ctrl+X/C/V cut/copy/paste, Ctrl+Z undoes, F10 quits (prompting to save if modified). Saves are atomic — the new content is written to a temporary file and flushed before it replaces the original, so a crash or a full disk never leaves a truncated file — and keep the file's attributes, creation time and (on Windows) alternate data streams. Read-only files are not saved; the editor says so and leaves them untouched.
Six built-in themes: nc-classic (default, the authentic NC palette),
nc-mono (black/white), terminal-green, purple-lights, yellow-storm,
and inverted. Switch live via Options → Themes (arrow keys preview
each theme before you commit) or the F2 user menu; the choice persists to
config.toml. Pin a theme for a single session with --theme <name>
without touching the saved default. Every theme defines the full role set,
including a dedicated panel.hidden role — a visibly dimmer variant of the
theme's file color, used for hidden/system entries in the panels.
FileCommand keeps an opt-in diagnostic log for troubleshooting. It is off by default — nothing is created or written unless you ask for it:
filecommand --log C:\temp\fc.log(also accepted as--log=<path>), or- set the
FILECOMMAND_LOGenvironment variable to the log path.
When both are given, the flag wins. The file is opened in append mode, so earlier sessions' lines are kept; each line starts with a UTC timestamp and a short category. The log records the failures FileCommand otherwise handles quietly:
persist— a failed write ofhistory.jsonorconfig.toml;channel/job— a result or dialog answer that could not be delivered between a worker thread and the event loop;copy— metadata that could not be restored on a copied file (alternate data streams, modification time, read-only attribute);startup— every startup warning, including ones a later warning replaced in the startup-warning dialog.
If the log file cannot be opened, the session starts without logging and a startup warning names the path and the reason. When reporting an issue, attach the log file alongside the error text (see Reporting issues).
FileCommand/
├── crates/
│ ├── filecommand-core/ # state, reducer, fs ops, git info — no UI deps
│ └── filecommand-tui/ # ratatui + crossterm binary (the `filecommand` exe)
├── installer/ # WiX (v4/v5) MSI + Burn bootstrapper packaging
├── openspec/ # OpenSpec proposals and per-capability specs
│ └── specs/ # source of truth for current behavior
├── docs/superpowers/specs/ # original full design document
└── Screen/ # screenshots used in this README
cargo test --workspaceCovers filecommand-core unit and property-based tests — the file-operation
engine against the in-memory FakeFs double (conflicts, error recovery,
cancellation, fault injection), sorting/filtering, selection semantics,
config/theme parsing, and git info against fixture repositories created in
temporary directories — plus the RealFs integration suite
(crates/filecommand-core/tests/realfs.rs), which runs the job engine
against the real filesystem in per-test temporary directories (round trips,
cancellation, read-only targets, Windows-specific cases). filecommand-tui
has ratatui TestBackend snapshot tests (panels, dialogs, viewer/editor,
splash, help, menus) via insta, plus integration tests for the binary's
flags and terminal restoration.
Every pull request runs the same gates in CI (.github/workflows/ci.yml:
Windows, pinned stable toolchain, against the committed Cargo.lock). To
reproduce the required job locally:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
$env:RUSTDOCFLAGS = '-D warnings'; cargo doc --workspace --no-deps --lockedA second required job checks the workspace on the declared minimum Rust
version (cargo check --workspace --all-targets --locked on 1.88). A
best-effort Linux job builds and tests on ubuntu-latest; its result is
reported but never blocks a merge.
This project develops behind an OpenSpec-driven, branch-per-change
workflow: research and proposals happen on Spec, get merged to main,
then each approved proposal is implemented on its own build/<name> branch
off main. Nothing lands on main without an explicit human go-ahead. See
CLAUDE.md for the full rules.
Found a bug or want to request a feature? File it on GitHub: https://github.com/kkaminsk/FileCommand/issues
A good report includes:
- What you did — the keys pressed or command typed, step by step.
- What happened versus what you expected.
- Environment — Windows version, terminal (Windows Terminal, conhost, PowerShell), and the FileCommand version shown on the startup splash.
- Error text — anything shown on the panel's status line or in a job dialog, copied verbatim. The UI is plain text, so pasting the screen works as well as a screenshot.
Dual-licensed under MIT or Apache-2.0, at your option.

