A modern terminal file manager for every filesystem.
A Midnight Commander successor, written in Rust — where every dual-pane is a virtual filesystem: local disk, SSH/SFTP, S3, GCS, Azure Blob, Docker, and Kubernetes. With a secure secrets vault and an agentic AI assistant.
Status: 🚧 Alpha. Working today: an interactive dual-pane local file manager (browse, navigate, sort, mark) with configurable keybindings, a cross-backend transfer engine (copy/move/delete with a confirm dialog), an encrypted secrets vault (XChaCha20-Poly1305 + Argon2id) behind a capability broker, TOML config, an AI plan → confirm overlay, a read-only file pager + in-place
$EDITORediting (RFC-0012), and read-only archive browsing —Enteron a.tar/.zip/.tgz/.tbz2/.tzstmounts it like a directory (RFC-0013, behind thearchivefeature;.txzis recognized but not yet decoded). Backend mapping cores for SSH/SFTP, object stores (S3/GCS/Azure-shaped), Docker, and Kubernetes are implemented against a transport seam and fully unit-tested with in-memory mocks, and the WASM plugin host (wasmtime, resource-limited, default-deny) runs sandboxed modules. Still integration-bound (need live services + heavy SDKs/TLS): the live SSH/cloud/cluster transports, the HTTP LLM providers, and the WASM component-model bridge. Seedocs/PRD.md,docs/LLD.md, and the livedocs/IMPLEMENTATION_PLAN.md.
Terminal users juggle a fragmented toolbox: mc for local files, aws s3/gsutil/az for
object stores, kubectl for clusters, docker for containers, scp/sftp for remotes — each
with its own syntax and auth model. Moving a file from a Kubernetes pod to an S3 bucket means
stitching several tools together by hand.
Cairn unifies these workflows behind one consistent, modern, keyboard-driven TUI, with credentials handled safely and an AI layer that turns intent into reviewed, executed actions.
- 🗂️ Dual-pane, MC-faithful UX — familiar muscle memory, switchable vim/custom keybinds.
- 🌐 Every pane is a backend — local, SSH/SFTP, S3, GCS, Azure Blob, Docker, Kubernetes.
- 🔁 Cross-backend operations — copy/move/diff between any two backends (pod → bucket, etc.).
- 🔐 Secure secrets vault — encrypted credentials, OS-keychain-protected, no plaintext ever.
- 🤖 Agentic AI — natural-language intent → a reviewable plan → confirmed execution.
- ⚡ Async & responsive — the UI never blocks on slow networks or huge listings.
- 🧩 Extensible — sandboxed WASM plugins for custom backends, viewers, and actions.
- 💻 Truly cross-platform — Linux, macOS, Windows, and headless/remote terminals.
| Doc | Purpose |
|---|---|
| PRD | Product requirements — what and why (high-level) |
| LLD | Low-Level Design — architecture & technical design |
| Implementation Plan | Milestones, sequencing, and the living progress tracker |
| ADRs | Architecture Decision Records |
| RFCs | Design proposals for non-trivial work |
| Contributing | How to build, branch, and submit changes |
Requires the Rust toolchain (see
rust-toolchain.toml).
cargo build --workspace
cargo run -p cairnCairn reads an optional TOML config from the platform config directory (e.g.
~/.config/cairn/config.toml on Linux); a missing or unreadable file falls back to defaults.
Keys can be remapped under [ui.keybindings] — a map of key-chord → action, layered over the
built-in scheme. Chords combine optional ctrl+/alt+/shift+ modifiers with a key (a single
character, a named key like enter/space/esc/tab/arrows, or f1–f24); actions are
snake_case (cursor_down, copy, move, delete, ai_propose, cycle_sort, toggle_hidden,
filter, make_dir, rename, calculate_size, view, edit, open_queue, toggle_pause,
background, quit, …). Unrecognized entries are ignored with a warning, and Ctrl-C always quits.
Each entry shows its Unix permissions (drwxr-xr-x) and last-modified date next to the name,
right-aligned MC-style; on a narrow pane the columns drop out responsively — permissions first,
keeping the date as long as it fits — so the name keeps priority, and are blank for backends that
don't expose that metadata. Dates are shown in UTC (YYYY-MM-DD). By default s cycles the active pane's sort order (name → size → modified →
type) and . toggles whether hidden entries (dotfiles) are listed; the current sort mode and hidden
state show in each pane's bottom-right corner, and the volume's free disk space (e.g.
137.0 GiB free) shows in the bottom-left for backends that report it (local now; SSH is a
follow-up). Ctrl-R reloads the active pane. F7 creates a new
directory and r renames the entry under the cursor (F2 is an alias; both
open a text prompt; Enter confirms, Esc cancels). Ctrl-S recursively calculates the size of
the folder under the cursor and shows it in a stats popup (total size, file and subfolder counts);
the walk runs in the background with live totals, and Esc cancels it and closes the popup. v opens a read-only pager on the entry
under the cursor (F3 alias), auto-detecting text vs. binary content and switching to a hex view for the
latter. e opens the entry under the cursor in an external editor (F4 alias) — $VISUAL, then $EDITOR,
then vi (Unix only; on Windows, set one of the two first); Enter on a text file opens the same
editor, while Enter on a binary file still opens the read-only hex pager. Editing works on
every backend, local or remote: a remote file is downloaded to a private temp copy, edited
there, and written back after a conflict check (has the remote file changed since you started
editing?) — with size limits and a confirm prompt if the remote drifted or the local edit came
back empty (see RFC-0012).
/ filters the listing as you type (Enter keeps the filter, Esc clears it). Copying or moving
files auto-opens an MC-style transfer dialog — a progress bar, byte count, rate, and ETA per active
transfer, plus the pending queue. ↑/↓ select a row (active transfer or pending item); p
pauses/resumes the selected active transfer and d cancels it (or drops it if it's a pending
item); Esc aborts all active transfers (the panic-stop); b sends the dialog to the
background (transfers keep running, the status line keeps its compact summary) and Ctrl-T brings
it back to the foreground. For the pending queue, K/J reorder and x clears it. The dialog
dismisses itself once the last transfer finishes and nothing remains queued. Up to two transfers
run at once by default — set [transfers] concurrency = N in config to change it.
[ui.keybindings]
"ctrl+a" = "ai_propose" # ask the AI assistant for a plan
"G" = "cursor_bottom"
"f5" = "copy"
"s" = "cycle_sort" # name → size → modified → type
"." = "toggle_hidden"Entries are colored by type so folders, files, and archives read at a glance: blue directories,
amber archives, green executables, cyan symlinks, purple streams (logs), red special nodes — and a
hidden (.-prefixed) directory or file uses the dimmed variant of its color, so .git/ is clearly
a folder but recedes next to src/.
Pick a theme with [ui] theme = "...":
| Preset | Look |
|---|---|
dark (default) |
modern truecolor Tokyo-Night; leaves your terminal's own background |
mc |
classic Midnight Commander — the iconic saturated VGA-blue panels, white folders, black-on-cyan selection |
nord |
the arctic Nord palette |
gruvbox |
warm retro Gruvbox (dark) |
light |
a clean light scheme (forces its own light background) |
[ui]
theme = "mc"Or switch live in the TUI with Shift-T, which cycles through the presets
(dark → mc → nord → gruvbox → light → …) — the status line shows the new theme. (The live choice is
session-only; set [ui] theme to make it the default.)
All presets use truecolor RGB, so they're best-effort on terminals without 24-bit color. dark
leaves the terminal background untouched, while mc/nord/gruvbox/light paint their own.
Every color role is themeable under [ui.colors] on top of the chosen preset — override individual
roles (background, foreground, focused_border, unfocused_border, dir, hidden_dir,
file, hidden_file, archive, executable, symlink, stream, special, error, status,
remote, selection_bg, selection_fg) using color names or #rrggbb. background/foreground
also accept none to clear a preset's forced value back to the terminal default:
[ui]
theme = "mc"
[ui.colors]
background = "none" # keep MC's colors but drop its blue panel background
dir = "#7aa2f7"
hidden_dir = "#4c5a8c" # dimmed dir tone for .folders
archive = "#e0af68" # .zip / .tar.gz / …
executable = "#9ece6a"
remote = "yellow" # accent for a pane on a remote backend (SSH/S3/…)Each pane's top border shows which backend it is browsing: a local pane shows just its path,
while a remote pane shows a full scheme://user@host:path locator (e.g.
ssh://root@dietpi6:/home) in the remote accent color, so the two are easy to tell apart.
Ctrl-O opens the connection switcher — pick a backend to open in the active pane. Docker and
Kubernetes connections appear automatically (auto-discovery; opt out per-source with
[discovery] docker = false / kubernetes = false), alongside your saved profiles and the local
filesystem roots. Inside the switcher:
| Key | Action |
|---|---|
Enter |
Open the highlighted connection in the active pane |
Ctrl-N |
Add a new connection (scheme picker → fields → credential) |
e |
Edit the highlighted saved profile |
d |
Delete the highlighted saved profile (asks to confirm; cleans up its vault credential) |
t |
Test the highlighted connection's reachability — without opening it into a pane |
P |
Pin/unpin the highlighted entry to the top of the list |
H |
Hide/un-hide the highlighted entry from the default view |
S |
Show hidden entries for this session (so a hidden one can be found again and un-hidden) |
Pin/hide apply to any entry (built-in, saved, or auto-discovered) and persist to
[discovery].pinned / [discovery].hidden in config.toml, keyed by a stable identifier — not
the display name, so renaming a profile never orphans its pin/hide state. Testing reuses the same
connection logic as a real open (so a real SSH/S3/GCS/Azure test performs genuine credential
resolution) but never mounts the result or switches any pane; a connection that needs the secrets
vault unlocked reports that directly rather than popping the vault-unlock prompt.
Bind a key to run a local program against the entry under the cursor. Each [[shell_actions]] entry
has a name, a key (same chord syntax as keybindings), a command, and args with the
placeholders {path} (the file's real path), {dir} (its directory), and {name} (its file name):
[[shell_actions]]
name = "Checksum"
key = "ctrl+h"
command = "/usr/bin/sha256sum"
args = ["--", "{path}"]
# confirm = false # skip the confirm prompt for a trusted action (default: true)Security: actions run only on local panes, with no shell (so filenames can't inject
commands — prefer -- before a {path}/{name} argument), in a scrubbed environment (no secrets are
passed to the program), with a confirm prompt and a timeout. For this reason the [[shell_actions]]
section is ignored if config.toml is writable by other users or not owned by you. Interactive
programs (editors) are not yet supported. See docs/adr/0005-shell-command-actions.md.
Contributions are welcome! Please read CONTRIBUTING.md and our
Code of Conduct. All changes land via pull request — main is protected.
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.