Skip to content

art-ps/sftpcommander

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SFTP Commander

A keyboard-driven terminal SFTP client. Browse, transfer, and manage remote files without leaving the shell β€” with a Midnight-Commander-style two-pane layout, parallel transfers, and full SSH-config integration.

CI Release Go Report Card License: MIT Go Version

sftpcommander demo

Table of contents

Highlights

  • πŸ—‚οΈ Two-pane layout β€” Midnight Commander-style remote/local browser, default view on connect.
  • ⚑ Parallel transfers β€” worker pool (up to 32) with pooled SFTP sessions; concurrent reads for large files.
  • πŸ” Secure by default β€” ssh-agent β†’ key β†’ password chain; strict known_hosts with TOFU prompt and MITM hard-fail.
  • 🌐 SSH config aware β€” ~/.ssh/config aliases + single-hop ProxyJump.
  • 🎨 Syntax-highlighted preview β€” chroma-powered, lazy paging.
  • ✏️ External editor round-trip β€” e opens remote file in $EDITOR, re-uploads on save.

Install

Homebrew (macOS / Linux)

brew install art-ps/tap/sftpcommander

Go

go install github.com/art-ps/sftpcommander@latest

Requires Go 1.25 or newer.

Pre-built binaries

Grab a tarball from the releases page β€” darwin/linux Γ— amd64/arm64.

From source

git clone https://github.com/art-ps/sftpcommander.git
cd sftpcommander
go build -o sftpcommander .

Quick start

sftpcommander

The app opens on the connection list, merging entries from ~/.config/sftpcommander/connections.toml and ~/.ssh/config Host aliases.

  • n β€” new connection form.
  • enter β€” connect (auth via ssh-agent β†’ key β†’ password).
  • After a successful connect, you land in two-pane mode (local on the left, remote on the right). F2 switches to the classic single-pane view.

Successful connections are auto-saved (deduped by user@host).

Features

Connections & auth
  • Saved connections + SSH config β€” ~/.config/sftpcommander/connections.toml entries and ~/.ssh/config Host aliases shown side by side.
  • Secure auth chain β€” ssh-agent (SSH_AUTH_SOCK) β†’ private key (passphrase-prompted for encrypted keys) β†’ password.
  • ProxyJump β€” single-hop ProxyJump from ~/.ssh/config; jump host authenticates via ssh-agent and/or its declared IdentityFile.
  • Host key verification β€” ~/.ssh/known_hosts enforced. Unknown hosts get a TOFU prompt with the SHA256 fingerprint; changed keys hard-fail with a MITM warning. Key-algorithm rotation (e.g. RSA β†’ Ed25519) is detected and silently negotiated.
Transfers
  • Parallel batch transfers β€” worker pool (default 4, cap 32) flattens directory trees into a flat task queue. Many small files transfer in parallel; large files use concurrent reads (sftp.UseConcurrentReads). Workers share a pooled set of SFTP sessions so reuse is free across batches.
  • Parallel pre-scan β€” directory walk during transfer also runs in a worker pool, so deep trees stop blocking on the first slow ReadDir.
  • Overwrite prompt β€” per-file replace / skip / resume / abort decision when the destination exists, with sticky "all" variants to apply the same choice to the rest of the batch.
  • Optional SHA256 verification β€” hashes each file post-transfer (sha256sum over an ssh session) and surfaces mismatches as failures.
  • Cross-pane copy + move β€” F5 copies, F6 moves (same-FS only β€” atomic rename).
  • Remote-side copy β€” in single-pane, F5 runs cp -R over an ssh session, so bytes never round-trip through the client.
Browsing
  • Two-pane mode (mc-style) β€” default after connect. Tab switches focus, = aligns the inactive panel, Ctrl-U swaps panels.
  • Recursive find β€” F walks the cwd subtree and shows matches relative to the search root. Patterns: substring, glob, or re:-prefixed regex.
  • External editor β€” e downloads the remote file to a temp path, opens $EDITOR, re-uploads on save (mtime-checked).
  • Syntax-highlighted preview β€” first 256 KB rendered via chroma (lexer chosen from filename), line numbers, binary detection. m loads the next 256 KB chunk on demand.
  • Symlink targets β€” symlinks show β†’ target in the status bar (resolved async, cached per session).
  • Directory summary β€” when nothing is selected, status bar shows N dirs, M files, X MB totals.
  • Filter, sort, hidden toggle β€” live substring filter /, three sort modes (name/size/mtime), asc/desc, hidden toggle. Filter resets on cd; sorted entries cached.
  • Bookmarks β€” per-host path bookmarks (b add, B list).
  • Cursor memory β€” jumping into a subdirectory and back puts the cursor where it was.
  • Multi-select β€” space selects, a selects all; d/D operate on the whole selection.
  • chmod (recursive) β€” c cursor entry, C recursive. M mkdir; r rename.
  • Error log β€” E opens an in-app log of failures recorded across operations; survives panel switches.

Keyboard shortcuts

Browser (single-pane & focused two-pane panel)
Key Action
↑/↓, j/k Move cursor
pgup/pgdn Page
g / G Top / bottom
enter, l, β†’ Open directory or preview file
h, ←, 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
F2 Toggle two-pane / single-pane
E Open error log
? Help overlay
q Quit
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 Switch 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.

Preview, connect form, bookmarks, error log

Preview: ↑/↓/pgup/pgdn, g/G scroll. m loads next 256 KB. esc/q/h/←/backspace return.

Connections list: enter connect, n new form, D delete saved entry, R reload.

Connect form: tab/shift+tab move between fields, enter advance/submit, esc back.

Bookmarks list: enter navigate, D delete, esc back.

Error log: ↑/↓ scroll, C clear, esc/q close.

Configuration

All state lives under ~/.config/sftpcommander/. Files are TOML, mode 0600, created on first write.

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

~/.ssh/config is parsed via kevinburke/ssh_config, which understands Include, wildcard Host patterns, and Match. Aliases appear in the connection list tagged [ssh]. Honoured directives:

  • HostName
  • Port
  • User
  • IdentityFile
  • ProxyJump (single hop)

Other directives (e.g. IdentitiesOnly) are not yet wired in.

Known hosts

~/.ssh/known_hosts is the source of truth:

  • Unknown host β€” prompt with 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 MITM warning. Resolution is manual edit of ~/.ssh/known_hosts.
  • Known host, different algorithm β€” silently retry with HostKeyAlgorithms restricted to algorithms already on file, so an RSA β†’ Ed25519 rotation does not trip the MITM path.

Environment

Variable Effect
EDITOR Editor invoked by e.
SFTP_NO_NF=1 Force ASCII file-icon fallback for terminals without a Nerd Font.
SSH_AUTH_SOCK Used for ssh-agent auth (set by your agent).

Development

go mod tidy
go build -o sftpcommander .
go vet ./...
go test ./...

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
β”œβ”€β”€ .goreleaser.yaml  # cross-build + brew tap publishing
└── go.mod

Releasing

Releases are built by GoReleaser on tag push.

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.

One-time setup (already done for this repo):

  1. Create the GitHub repos art-ps/sftpcommander and art-ps/homebrew-tap.
  2. In art-ps/homebrew-tap, create an empty Formula/ directory.
  3. Generate a Personal Access Token with repo scope, save it as the HOMEBREW_TAP_GITHUB_TOKEN secret on art-ps/sftpcommander.

Roadmap

  • Multi-hop ProxyJump chains
  • Adaptive theme for light terminals
  • Mouse support
  • Cross-FS move (currently F6 errors out unless both panels share the same FS)

Contributing

Issues and PRs welcome. Before opening a PR:

  1. go vet ./... clean.
  2. go test ./... passing.
  3. Keep changes focused β€” a one-feature-per-PR policy makes review easier.
  4. Match the existing style; this codebase favours small, composable Bubble Tea models.

Bug reports: open an issue with the OS, terminal, Go version, and steps to reproduce.

License

MIT β€” free for any use, including commercial, with attribution.

Acknowledgments

Built on the shoulders of:

About

Terminal SFTP client with a keyboard-driven TUI

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages