Skip to content

🪨 Cairn

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.

CI License Status

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 $EDITOR editing (RFC-0012), and read-only archive browsingEnter on a .tar/.zip/.tgz/.tbz2/.tzst mounts it like a directory (RFC-0013, behind the archive feature; .txz is 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. See docs/PRD.md, docs/LLD.md, and the live docs/IMPLEMENTATION_PLAN.md.


Why Cairn?

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.

Highlights (planned)

  • 🗂️ 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.

Documentation

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

Building

Requires the Rust toolchain (see rust-toolchain.toml).

cargo build --workspace
cargo run -p cairn

Configuration

Cairn 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 f1f24); 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.

Connections

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.

Shell-command actions

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.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md and our Code of Conduct. All changes land via pull request — main is protected.

License

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.

About

A modern terminal file manager for every filesystem — local, SSH, S3, GCS, Azure, Docker, and Kubernetes. An MC successor in Rust, with a secrets vault and agentic AI.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages