README
¶
sftpcommander
A terminal SFTP client with a keyboard-driven TUI. Browse, transfer, and manage remote files without leaving the shell.
Built on Bubble Tea, Lipgloss, pkg/sftp, and chroma.
Features
- Saved connections + SSH config — start screen lists
~/.config/sftpcommander/connections.tomlentries and~/.ssh/configHost aliases side by side. Successful connections are auto-saved (dedup byuser@host). - Secure auth chain — ssh-agent (via
SSH_AUTH_SOCK) -> private key (with passphrase prompt for encrypted keys) -> password. - ProxyJump — single-hop
ProxyJumpfrom~/.ssh/configis honoured; the jump host authenticates via ssh-agent and/or its declaredIdentityFile. - Host key verification —
~/.ssh/known_hostsenforced. First-time hosts trigger a TOFU prompt with fingerprint; changed keys hard-fail with a MITM warning. Key-algorithm rotation (e.g. RSA -> Ed25519) is detected and silently negotiated. - Parallel transfers — worker pool (default 4, cap 32) flattens directory trees into a flat task queue for both downloads and uploads; many small files transfer in parallel, large files use concurrent reads via
sftp.UseConcurrentReads. Workers share a pooled set of SFTP sessions so reuse is free across batches. - Parallel scan — directory walk during transfer pre-scan also runs in a worker pool, so deep trees stop blocking on the first slow
ReadDir. - Overwrite prompt — per-file
replace / skip / resume / abortdecision when the destination already exists, with sticky "all" variants to apply the same choice to the rest of the batch. - Optional SHA256 verification —
Verifymode hashes each file post-transfer (sha256sumover an ssh session) and surfaces mismatches as failures. - Cross-pane copy + move —
F5copies the selected entries to the other panel (download or upload, picked automatically by direction).F6moves entries when both panels share the same FS. - Remote-side copy —
F5in single-pane runscp -Rover an ssh session, so the bytes never round-trip through the client. - Recursive find —
Fwalks the current subtree and replaces the listing with matches; entry names show paths relative to the search root. Patterns share the filter syntax (substring, glob, orre:prefix). - External editor —
edownloads the remote file to a temp path, opens$EDITOR, and re-uploads on save (mtime-checked, so closing without changes is a no-op). - Text preview — first 256 KB rendered with chroma syntax highlighting (lexer chosen from filename), line numbers, and binary detection. Press
mto load the next 256 KB on demand; the status bar shows how much is buffered. - Symlink targets — symlinks in the listing show
→ targetin the status bar (resolved async, cached per session). Preview pane shows the target next to the path. - Directory summary — when no selection is active, status bar shows
N dirs, M files, X MBtotals for the current view. - Filtering and sorting — live substring filter (
/), three sort modes (name/size/mtime), asc/desc toggle, hidden-files toggle. Filter resets oncd. Sorted entries cached so cursor moves don't pay the sort cost. - Bookmarks — per-host path bookmarks stored in
~/.config/sftpcommander/bookmarks.toml. - Cursor memory — jumping into a subdirectory and back puts the cursor where it was.
- Multi-select — space to select,
ato select all, thend/Doperate on the whole selection. - chmod (recursive) —
cchanges the cursor entry,Capplies recursively.Mmakes a new directory;rrenames. - Two-pane mode (mc-style) —
Topens a local-FS panel next to the remote one.Tabswitches focus,F5/F6copy/move across,Ctrl-Uswaps panel contents,=aligns the inactive panel to the active path. - Error log —
Eopens an in-app log of failures recorded across operations; survives panel switches so the user can review what went wrong after dismissing the inline error.
Install
Homebrew (macOS / Linux)
brew install art-ps/tap/sftpcommander
Go install
Requires Go 1.25 or newer.
go install github.com/art-ps/sftpcommander@latest
From source
git clone https://github.com/art-ps/sftpcommander.git
cd sftpcommander
go build -o sftpcommander .
The resulting binary is a single self-contained executable.
Pre-built binaries
Download from the releases page — archives for darwin/linux on amd64/arm64.
Usage
sftpcommander
The app opens on the connection list. Pick a saved entry or n for a new
connection. After connecting, you land in the browser at the remote home
directory.
Keyboard shortcuts (browser)
| Key | Action |
|---|---|
up/down, j/k |
Move cursor |
pgup/pgdn |
Page |
g / G |
Top / bottom |
enter, l, right |
Open directory or preview file |
h, left, backspace |
Parent directory |
R |
Refresh current directory |
space |
Toggle select on cursor |
a |
Select / clear all |
esc |
Clear selection / error / status |
d |
Download selection (or cursor) |
u |
Upload from local path |
e |
Edit remote file in $EDITOR |
v |
Preview text file |
D |
Delete (with confirm; recursive for directories) |
r |
Rename |
M |
Make directory |
c / C |
Change mode (octal; C is recursive) |
F5 |
Remote-side copy (cp -R via ssh) |
/ |
Filter directory |
F |
Recursive find under cwd |
. |
Toggle hidden files |
s / S |
Cycle sort mode / toggle direction |
b |
Bookmark current path |
B |
Open bookmarks list |
T |
Open two-pane (mc-style) mode |
E |
Open error log |
? |
Help overlay |
q |
Quit |
Keyboard shortcuts (two-pane mode)
| Key | Action |
|---|---|
tab |
Switch active panel |
F5 |
Copy selection from active panel to the other panel (auto download/upload) |
F6 |
Move selection across panels (same-FS only — atomic rename) |
= |
Align inactive panel to the active panel's path |
Ctrl-U |
Swap panels |
F2, Ctrl-W |
Back to single-pane browser |
E |
Open error log |
q |
Quit |
All single-panel shortcuts (/, s, D, r, M, c, v, ?, etc.) also work inside the focused panel. Transfer keys d/u and bookmark keys b/B are disabled on the local panel.
Keyboard shortcuts (preview)
up/down/pgup/pgdn,g/Gto scroll.mto load the next 256 KB chunk.esc,q,h,left,backspaceto return.
Keyboard shortcuts (other screens)
- Connections list:
enterto connect,nfor a new form,Dto delete a saved entry,Rto reload. - Connect form:
tab/shift+tabto move between fields,enterto advance or submit,escback to the list. - Bookmarks list:
enterto navigate,Dto delete,escback. - Error log:
up/downto scroll,Cto clear,esc/qto close.
Configuration
All state lives under ~/.config/sftpcommander/. The format is TOML; files are
created on first write with mode 0600.
connections.toml
[[connection]]
name = "deploy@prod.example.com"
host = "prod.example.com"
port = "22"
user = "deploy"
key_path = "~/.ssh/id_ed25519"
Passwords and passphrases are never persisted.
bookmarks.toml
[[bookmark]]
host = "prod.example.com"
user = "deploy"
path = "/var/log/app"
label = "app logs"
Bookmarks are scoped by host + user, so the bookmarks list only shows
entries relevant to the current session.
SSH config
The app reads ~/.ssh/config via kevinburke/ssh_config, which understands
Include, wildcard Host patterns, and Match. Aliases declared there appear
in the connection list tagged [ssh]. For each alias the app honours:
HostNamePortUserIdentityFileProxyJump(single hop; jump host authenticates via ssh-agent or its ownIdentityFile)
Other directives (e.g. IdentitiesOnly) are not yet wired in.
Known hosts
~/.ssh/known_hosts is the source of truth for host key verification:
- Unknown host: prompt with the SHA256 fingerprint. On accept the key is
appended (one line per
hostname,ip). - Known host, same key: silent.
- Known host, same algorithm, different key: hard fail with a MITM warning.
Resolution is manual editing of
~/.ssh/known_hosts. - Known host, different algorithm: silently retry with
HostKeyAlgorithmsrestricted to algorithms already on file, so an RSA -> Ed25519 rotation does not trip the MITM path.
Display
SFTP_NO_NF=1forces the ASCII file-icon fallback for terminals without a Nerd Font.
Build from source
go mod tidy
go build -o sftpcommander .
go vet ./...
Project layout
.
├── main.go
├── internal/
│ ├── sftp/ # SSH dial, known_hosts, ProxyJump, file ops, parallel batch transfer
│ ├── ui/ # Bubble Tea models (connect, browser, download, upload, preview, edit, errlog, etc.)
│ └── config/ # TOML store for connections and bookmarks
└── go.mod
Roadmap
Not yet implemented, listed in rough priority:
- Multi-hop
ProxyJumpchains - Adaptive theme for light terminals
- Mouse support
- Cross-FS move (currently F6 errors out unless both panels share the same FS)
Releasing
Releases are built by GoReleaser on tag push. Setup (one-time):
- Create GitHub repos
art-ps/sftpcommanderandart-ps/homebrew-tap. - In
art-ps/homebrew-tap, create an emptyFormula/directory (or justgit init+ first commit). - Generate a GitHub Personal Access Token with
reposcope, save it as theHOMEBREW_TAP_GITHUB_TOKENsecret onart-ps/sftpcommander. - Push this repo:
git remote add origin git@github.com:art-ps/sftpcommander.git && git push -u origin main.
To cut a release:
git tag v0.1.0
git push origin v0.1.0
The release workflow builds darwin/linux × amd64/arm64 archives,
publishes a GitHub release, and pushes an updated formula to
art-ps/homebrew-tap. Users then install via:
brew install art-ps/tap/sftpcommander
License
MIT — free for any use, including commercial, with attribution.
Documentation
¶
There is no documentation for this package.