---
name: ferry
description: >-
  Move files to and from FTP, SFTP, Amazon S3, Google Cloud, and S3-compatible
  storage using the user's saved Ferry connections — without ever handling
  credentials. Use whenever the user asks to upload, download, list, read, or
  manage files on a remote server or bucket they keep in Ferry (e.g. "upload this
  to my web server", "what's in my backups bucket?", "download the latest logs
  from prod", "delete the old build on staging").
---

# Ferry CLI

Ferry is a desktop file-transfer app. It ships a `ferry` command-line tool that
moves files over the connections the user has **already saved in the app**. You
drive that tool.

## The one rule: never handle credentials

Connections are referenced **by name**. Ferry stores each connection's
credentials encrypted and loads them itself, in-process. Therefore:

- **Never ask the user for a password, key, host, or username.** If you need a
  connection, list the saved ones and use the name.
- **Never put secrets on the command line.** There is no flag for a password —
  by design.
- If the user wants a server you can't see, tell them to **add it in the Ferry
  app** (they may need to open Ferry → Settings → *Command-line tool* and click
  *Install "ferry" command* first). Do not try to create it with credentials.

## Finding the command

Try `ferry` on the PATH first. If it isn't found, fall back to the binary inside
the app bundle:

```bash
FERRY="$(command -v ferry || echo /Applications/Ferry.app/Contents/MacOS/ferry-cli)"
# (~/Applications/Ferry.app/... if it was installed for the current user only)
"$FERRY" doctor --json
```

`ferry doctor --json` confirms the tool works and shows the config location and
how many connections exist. If it errors or the command is missing, tell the
user to install the CLI from **Ferry → Settings → Command-line tool**.

## Always start by listing connections

```bash
ferry conn ls --json
```

Returns objects with `name`, `protocol`, `folder`, and a non-secret `summary`.
Use the exact `name` (case-insensitive; `folder/name` also works if names
collide). Never guess a name — list first.

## Commands

Remote paths use `scp`-style syntax: `<connection>:/path`. Add `--json` to any
command for machine-readable output you can parse.

| Command | What it does |
|---|---|
| `ferry conn ls` | List saved connections (no secrets) |
| `ferry conn test <name>` | Verify a connection works |
| `ferry ls <conn>:/path` | List a remote directory (`-l` for sizes/dates) |
| `ferry stat <conn>:/path` | Metadata for one entry; exit 4 if it doesn't exist |
| `ferry tree <conn>:/path --depth N` | Bounded directory tree |
| `ferry cat <conn>:/file` | Print a remote file to stdout (`--max-bytes` guard) |
| `ferry get <conn>:/remote [local]` | Download a file (`-r` for a folder) |
| `ferry put <local> <conn>:/remote` | Upload a file (`-r` for a folder) |
| `ferry mkdir <conn>:/path` | Create a folder (`-p` for parents) |
| `ferry mv <conn>:/a <conn>:/b` | Move/rename a file within one connection |
| `ferry rm <conn>:/path --yes` | Delete a file (`-r --yes` for a folder) |
| `ferry doctor` | Config location + connection count |

`<conn>:/` or `<conn>:` means the connection's root.

## Safety and confirmations

- **Destructive commands require `--yes`** (`rm`, `log clear`). Before running any
  `rm`, deleting, or overwriting existing remote data, **state exactly what will
  be affected and get the user's confirmation.** Do not add `--yes` on the user's
  behalf for a delete they didn't clearly ask for.
- `get`/`put` refuse to overwrite an existing file unless you pass `--overwrite`.
  Prefer telling the user a file exists over silently clobbering it.
- Prefer `ferry cat` / `ferry ls --json` to inspect before you mutate.

## Handling common errors

- **Exit 4** = not found. For `stat`, that's a normal "doesn't exist" answer.
- **Host key / certificate not trusted** — the error says to open the connection
  once in the Ferry app to review and trust the server. Relay that; you can't
  trust it from the CLI.
- **"no saved connection called …"** — run `ferry conn ls` and use a real name.

## Examples

```bash
# "Upload this build to my web server's releases folder"
ferry put ./release.zip webserver:/var/www/releases/

# "What's in my backups bucket for January?"
ferry ls backups:/2026/01 -l --json

# "Show me the nginx error log on prod"
ferry cat prod:/var/log/nginx/error.log --max-bytes 200000

# "Download the whole reports folder from staging"
ferry get staging:/reports ./reports -r
```

Work only with the user's own connections and the paths they ask about. Don't
send fetched file contents anywhere the user didn't request.
