ferry-cli 0.1.17

The ferry command for terminal-native LAN file transfer with QUIC, discovery, resume, TUI, and JSON output.
Documentation
# FileFerry

FileFerry is a terminal-native local file transfer tool for moving files
between machines on the same network without a cloud account, relay, or GUI.
The command is `ferry`.

It is built around a single binary for Linux, macOS, and Windows that can:

- Discover nearby peers automatically on the local network.
- Send files and directories over a native QUIC fast path between `ferry`
  instances.
- Receive transfers in foreground, TUI, or daemon mode.
- Provide clean human output by default and newline-delimited JSON when used
  from scripts.

FileFerry is pre-1.0, but the native binary already supports native mDNS peer
discovery, direct-address and peer-targeted QUIC transfer, manifest-backed
skip/resume, scriptable CLI output, long-running daemon receive mode, an
interactive TUI send flow, foreground Ctrl+C transfer cancellation with
partial-file cleanup, trust-on-first-use prompting and persistence for first
contact, release packaging automation, and the public website. The remaining
build plan lives in [`BUILD.md`](BUILD.md).

The public project site is live at [`https://fileferry.app/`](https://fileferry.app/).
A separate web send portal is planned for `send.fileferry.app` after the
native CLI, TUI, daemon, release, and documentation foundations are solid.

## Why

Local file transfer tools proved that discovery, trust, and receiving files
can feel simple. `ferry` brings that simplicity to a fast, scriptable,
terminal-native workflow.

FileFerry fills that gap:

- Move large files between machines at LAN line rate.
- Use aliases, fingerprint prefixes, or direct addresses instead of copy-paste
  URLs.
- Run interactively when you want a file picker and transfer queue.
- Run quietly and predictably in scripts, SSH sessions, cron jobs, and
  headless machines.

## Quick Start

```sh
ferry send stephen-mbp ./vacation.zip
ferry send pixel-9 ~/Downloads/report.pdf
ferry recv --accept-all --dest ~/Downloads/ferry
ferry peers
ferry peers trust 9a4f2c
ferry identity
ferry daemon
ferry tui
```

JSON mode is designed for tools:

```sh
ferry --json send pixel-9 ./backup.tar.zst
```

Events are emitted as one JSON object per line, including version, transfer
start, progress, completion, cancellation, and failure events for implemented
commands.

![Terminal demo showing a direct FileFerry transfer with fingerprint pinning and JSON events](docs/demo-terminal.svg)

## Architecture

The project is one Cargo workspace with focused crates:

```text
crates/
  ferry-core/   protocol, transport, discovery, crypto, filesystem engine
  ferry-cli/    clap CLI, progress output, JSON events, daemon commands
  ferry-tui/    ratatui interactive interface
  ferry-site/   public fileferry.app project website (Leptos + Axum SSR)
  ferry-send-web/ future send.fileferry.app web portal
xtask/          build, release, benchmark, and packaging automation
```

`ferry-site` is self-contained: its CSS and favicon are embedded with
`include_str!`, it listens on plain HTTP at `FERRY_SITE_ADDR` (default
`127.0.0.1:3000`), and the Ubuntu + Caddy deployment is documented in
[`docs/site-deployment.md`](docs/site-deployment.md). The native `ferry`
binary does not depend on the site at runtime.

`ferry-core` owns all network, protocol, filesystem, discovery, trust, and
resume behavior. Frontends should stay thin.

### Discovery

FileFerry advertises and browses native peers with mDNS:

- Service: `_ferry._udp.local`
- TXT records: protocol version, transport support, fingerprint, alias, and
  ports

Discovered peers are unified into a single registry keyed by fingerprint.
Aliases, hostnames, and fingerprint prefixes are lookup hints only.

### Transport

Native `ferry` transfers use QUIC via `quinn`. The current protocol uses one
QUIC connection and one bidirectional stream per transfer session for
negotiation, manifest, receive plan, payload, and acknowledgement frames.

Transport selection is automatic:

- Discovered `ferry` peer: QUIC.
- Direct address: QUIC.

### Resume

Every transfer starts with a manifest containing session id, file entries,
sizes, hashes, permissions where supported, and modification times.

The receiver compares the manifest against the destination:

- Existing matching file: skip.
- Matching partial file: resume from verified offset.
- Anything else: transfer from byte zero.

BLAKE3 is used because it is fast, parallel-friendly, and suitable for cheap
prefix verification.

### Trust

FileFerry's target native trust model is trust-on-first-use:

- A persistent device key is generated on first run.
- Self-signed TLS certificates identify peers by public key fingerprint.
- First contact shows a short fingerprint for user confirmation.
- Known peers are pinned in the platform config directory.
- `ferry peers trust` can pin a full fingerprint or a discovered peer resolved
  by alias, hostname, or unique fingerprint prefix.
- `ferry identity` prints the local alias and full device fingerprint for
  explicit inspection or out-of-band confirmation.
- Discovered-peer sends and TUI sends require the receiver certificate
  fingerprint to match the discovered peer fingerprint before payload bytes are
  sent.
- Direct-address sends can require a specific receiver certificate with
  `ferry send --fingerprint <full-fingerprint> <ip:port> <path>`.
- Scripted and headless workflows can require an explicit PSK with
  `--psk-file` or the redacted `trust.psk` config field.

Important current limitation: the transfer path is encrypted, persistent
identity and trust-store commands exist, and discovered-peer sends now verify
the advertised fingerprint before payload. Direct-address sends can be pinned
with an explicitly confirmed fingerprint, and unknown direct-address receivers
are now confirmed before any manifest or payload bytes are sent when the sender
is interactive. Non-interactive first contact should use `ferry peers trust`,
`--fingerprint`, or an explicit policy change in config. Explicit PSK mode
exists for scripted/headless workflows, but it is opt-in and must be configured
on both peers. See
[`docs/security.md`](docs/security.md) for the exact current boundary.

## Target Commands

```text
ferry send <peer> <path>...        Send files or directories
ferry recv [--accept-all]          Receive in foreground
ferry recv --dest <dir>            Receive into a specific directory
ferry peers                        List discovered peers
ferry peers trust <fingerprint>    Trust a peer fingerprint
ferry peers forget <fingerprint>   Remove a known peer
ferry daemon                       Run as a background receiver
ferry config                       Print or edit configuration
ferry identity                     Print the local alias and fingerprint
ferry version                      Print version information
ferry tui                          Force interactive TUI mode
```

Global flags:

```text
--port <PORT>        Override default listen port
--bind <IFACE>       Bind to a specific interface
--json               Emit newline-delimited JSON events
--no-discovery       Disable discovery
--quiet, -q          Reduce output
--verbose, -v        Increase output
```

Send-specific flags:

```text
--fingerprint <FINGERPRINT>  Require a direct-address receiver certificate
                             to match this full fingerprint
--psk-file <FILE>            Read an explicit transfer PSK from a file
```

Planned exit codes:

```text
0  success
1  generic error
2  peer not found
3  peer rejected transfer
4  transfer started but failed mid-flight
5  config or permission error
```

## Configuration

`ferry` uses platform-native config directories through `directories`.

Example:

```toml
alias = "stephen-desktop"
listen_port = 53317
quic_port = 53318
download_dir = "~/Downloads/ferry"
auto_accept_known = true
auto_accept_unknown = false
discovery = ["native"]
max_concurrent_files = 8

[trust]
require_fingerprint = true
psk = ""
```

## Web Surfaces

The public project website is [`fileferry.app`](https://fileferry.app/). It is
a Rust web app built with Leptos and Axum, deployed separately from the native
binary.

The later hosted send portal at `send.fileferry.app` is a separate product
surface. It is not part of the v1 LAN boundary and must keep its own trust,
relay, retention, abuse, and operating-cost design before any hosted transfer
capability ships.

## Performance Targets

On commodity desktop hardware with NVMe storage and 1GbE:

- Single large file: at least 110 MB/s on the wire.
- Many small files, 10k files at 4KB each: at least 50 MB/s effective.
- Resume detection for a 50 percent complete 10GB file: under 5 seconds.

Tuning knobs should include QUIC congestion behavior, stream buffer size,
hashing parallelism, chunk size, and concurrent files in flight.

## Possible Future Work

- Compatibility bridge for third-party LAN transfer tools.
- Mobile companion app.

## Development

The implementation plan lives in [`BUILD.md`](BUILD.md). The expected local
loop is:

```sh
cargo fmt --all
cargo clippy --workspace --all-targets --all-features
cargo test --workspace --all-features
cargo build --workspace
```

The command package is published on crates.io as `ferry-cli`, which installs
a binary named `ferry`:

```sh
cargo install ferry-cli --version 0.1.17
```

Tagged releases are built through cargo-dist for Linux, macOS, and Windows
with SHA-256 checksums and Sigstore/cosign signature bundles.
Crates are published in dependency order: `ferry-core`, `ferry-tui`, then
`ferry-cli`.

Two-machine Mac/Ubuntu transfer benchmarks can be run with:

```sh
just lan-bench-ubuntu
```

The active performance notes distinguish overlay-route results from physical
1GbE measurements.

For docs-only changes, the narrow verification command is:

```sh
git diff --check
```

Durable implementation references now live under [`docs/`](docs/), including
the current architecture, native protocol, discovery contract, security state,
daemon/headless notes, performance plan, release plan, and CLI/JSON examples.

## License

MIT. See [`LICENSE`](LICENSE).