I’ve been moving files to servers and SSH-ing into boxes for more than a decade. And for most of that decade, my workflow looked exactly like this:
- Open PuTTY (or Windows Terminal + OpenSSH) for the shell.
- Open FileZilla for the files.
- Log in twice to the same server.
- Drag, drop,
chmod, restart, repeat.
It works, but it also means managing the same server in two separate places. FileZilla handles the files. PuTTY handles the shell. They do not share a connection, a session list, or state. And when I want an AI agent to help me debug something on the box, I end up copy-pasting terminal output or handing over my keys.
A month ago I started building Faro, a single desktop client that brings both together, plus the cloud-storage browser I was also keeping open for S3/R2 buckets. Then I added something I did not know I needed until I had it: a bridge that lets a local AI agent run commands through the session I already opened, with me approving every command.
What Faro actually is
Faro is a desktop app built with Tauri 2 (Rust core, React UI). It unifies the things I used to need three windows for:
- SFTP / SSH - dual-pane file browser + terminal, sharing one connection.
- FTP / FTPS - for the legacy boxes we all pretend don’t exist.
- S3-compatible storage - S3, Cloudflare R2, Backblaze B2, Azure Blob.
Save a server or bucket once, and you can browse it, sync directories, edit remote files in your local editor with auto-upload on save, or run a shell against the same SSH session. No second login.
The things that made me stay in Faro instead of going back:
- One SSH session, two surfaces. The file browser and terminal share a single connection per profile. No more FileZilla window + PuTTY window doing the same handshake.
- Drag-and-drop + sync. Local ↔ remote transfers, recursive copies, and one-way sync (additive or mirror) with a planner that works across all backends.
- Edit in place. Right-click a remote file, it opens in your default editor on a temp file, and saves upload back automatically.
-
Importers. Pull connections from
~/.ssh/config, FileZilla’ssitemanager.xml, and PuTTY sessions. Because nobody wants to retype 40 saved hosts. - Known-host verification that doesn’t suck. Mismatched host keys get a danger-toned modal; MITM attempts are obvious.
-
faro-cli. A standalone Rust CLI that scripts every backend the GUI speaks, using the same saved profiles.faro-cli sync ./site prod:/var/www --mirror --dry-runis now my deploy preview step.
The part that changed how I work: Agent Bridge
Lately I’ve been using Claude Code and other MCP-speaking agents for a lot of coding tasks. The obvious next step is asking the agent to do something on a server: check disk usage, restart a service, inspect logs, fix a config. But the usual options are bad:
- Hand the agent your SSH keys. Now a third-party process has your private key.
- Install something on the remote server. Not always possible, and definitely not secure-by-default.
- Copy-paste terminal output back and forth. Works, but it’s slow and the agent can’t iterate.
Faro’s Agent Bridge takes a different approach: the agent borrows the session you already authenticated.
Here’s how it works:
- Connect to a server in Faro.
- Open the Bridge panel, start the localhost server, and flip Allow agent access for that session.
- Add the MCP server to Claude Code with the one-liner the panel generates:
claude mcp add --transport http faro http://127.0.0.1:<port>/mcp \
--header "Authorization: Bearer <token>"
Now the agent has two tools: faro_list_sessions and faro_exec. Ask it “check disk usage on the server” and it sends the command to Faro. Faro shows you the exact command, waits for your approval, runs it through your live SSH session, and returns the output. If you deny it, nothing reaches the server.
The guardrails are all on by default:
-
Localhost only - the server binds to
127.0.0.1on a random port. - Bearer token - required on every request, regenerated each launch.
- Per-session opt-in - a connection is invisible to the agent until you enable it.
-
Per-command approval - every
execblocks on a prompt in Faro. - Audit log - every command, approval, and denial is recorded in the panel.
No remote install. No keys handed over. No ambient access.
Why I built it this way
I wanted a tool that felt native to the way I work now. That meant:
- A real desktop app, not a web app wrapped in Electron.
- Density over dashboards. Information-rich, keyboard-first, no marketing fluff.
- One mental model for every backend. The same sync dialog and transfer queue whether I’m on SFTP or S3.
- Security surfaces that make risk obvious, not hidden behind a “trust us” checkbox.
The architecture reflects that. Everything in the Rust core flows through a single RemoteFs trait. Add a protocol, and the GUI, CLI, sync planner, and transfer engine pick it up automatically.
React + TypeScript + Tauri webview
│
Tauri commands + events
│
Rust core (faro_lib)
RemoteFs trait → LocalFs · SftpFs · FtpFs · ObjectFs
SessionManager pools one SSH session per profile
TransferManager + sync planner
│
faro-cli (clap + same crate)
What’s next
Faro is open source under MIT. Prebuilt installers for macOS, Windows, and Linux ship with every release, plus the standalone faro-cli binary. You can also build it from source with npm install && npm run tauri dev.
The current roadmap is focused on polish: transfer speed limits, queue editing, a command palette, filename filters, WebDAV support, and eventually code signing so first-launch isn’t a fight with Gatekeeper and SmartScreen.
If you’re still juggling FileZilla, PuTTY, and a cloud console, give it a try. And if you’re already using Claude Code or another MCP agent, the Agent Bridge is the closest thing I’ve found to letting an AI touch a server without actually letting it touch your credentials.
Repo: github.com/jhd3197/faro
Latest release: github.com/jhd3197/faro/releases/latest
I built this because I was genuinely tired of my own workflow. If Faro saves you from the same two-window dance, or helps you safely hand a live session to an agent, it was worth writing.
Top comments (0)