use std::net::SocketAddr;
use std::path::PathBuf;
use clap::{Args, CommandFactory, Parser, Subcommand};
#[derive(Debug, Parser)]
#[command(
name = "ferry",
version,
about = "Terminal-native LAN file transfer",
long_about = "FileFerry moves files and directories between local-network ferry peers over QUIC, with mDNS discovery, fingerprint pinning, daemon receive mode, a TUI send flow, and newline-delimited JSON for scripts.",
after_help = "Examples:\n ferry recv --listen 0.0.0.0:53318 --dest ~/Downloads/ferry\n ferry send stephen-mbp ./bundle\n ferry send --fingerprint <full-fingerprint> 192.168.1.42:53318 ./bundle\n ferry --json send 192.168.1.42:53318 ./bundle\n ferry peers trust <alias-or-full-fingerprint>\n ferry daemon --listen 0.0.0.0:53318 --dest ~/Downloads/ferry"
)]
pub struct Cli {
#[arg(
long,
global = true,
help = "Override the default command port where supported"
)]
pub port: Option<u16>,
#[arg(
long,
global = true,
help = "Bind command listeners to a specific interface where supported"
)]
pub bind: Option<String>,
#[arg(long, global = true, help = "Emit newline-delimited JSON on stdout")]
pub json: bool,
#[arg(long, global = true, help = "Disable mDNS discovery and announcements")]
pub no_discovery: bool,
#[arg(short, long, global = true, help = "Reduce human output")]
pub quiet: bool,
#[arg(short, long, action = clap::ArgAction::Count, global = true, help = "Increase diagnostic output")]
pub verbose: u8,
#[command(subcommand)]
pub command: Option<Command>,
}
impl Cli {
pub fn clap_command() -> clap::Command {
Self::command()
}
}
#[derive(Debug, Subcommand)]
pub enum Command {
#[command(
about = "Send files or directories to a ferry peer",
after_help = "Examples:\n ferry send stephen-mbp ./bundle\n ferry send 192.168.1.42:53318 ./bundle\n ferry send --fingerprint <full-fingerprint> 192.168.1.42:53318 ./bundle\n ferry send --psk-file ~/.config/ferry/transfer.psk 192.168.1.42:53318 ./bundle"
)]
Send(SendArgs),
#[command(
about = "Receive one transfer in the foreground",
after_help = "Examples:\n ferry recv --listen 0.0.0.0:53318 --dest ~/Downloads/ferry\n ferry recv --no-discovery --listen 127.0.0.1:53318 --dest ./incoming\n ferry recv --psk-file ~/.config/ferry/transfer.psk --listen 0.0.0.0:53318"
)]
Recv(RecvArgs),
#[command(about = "List and manage trusted or discovered peers")]
Peers {
#[command(subcommand)]
command: Option<PeersCommand>,
},
#[command(
about = "Run a long-lived receiver using persisted daemon settings",
after_help = "Examples:\n ferry daemon\n ferry daemon --listen 0.0.0.0:53318 --dest ~/Downloads/ferry\n ferry daemon --psk-file ~/.config/ferry/transfer.psk"
)]
Daemon(DaemonArgs),
#[command(about = "Print the redacted configuration")]
Config,
#[command(about = "Print the local alias and device fingerprint")]
Identity,
#[command(about = "Print the ferry version")]
Version,
#[command(about = "Open the interactive terminal send interface")]
Tui,
}
#[derive(Debug, Args)]
pub struct SendArgs {
#[arg(
long,
value_name = "FINGERPRINT",
help = "Require the receiver certificate to match this full fingerprint"
)]
pub fingerprint: Option<String>,
#[arg(
long,
value_name = "FILE",
help = "Read an explicit transfer PSK from a file"
)]
pub psk_file: Option<PathBuf>,
#[arg(help = "Peer alias, hostname, fingerprint prefix, or direct ip:port")]
pub peer: String,
#[arg(value_name = "PATH", help = "File or directory to send")]
pub paths: Vec<PathBuf>,
}
#[derive(Debug, Args)]
pub struct RecvArgs {
#[arg(long, help = "Listen address for incoming QUIC transfers")]
pub listen: Option<SocketAddr>,
#[arg(long, value_name = "DIR", help = "Destination directory")]
pub dest: Option<PathBuf>,
#[arg(
long,
help = "Accept the incoming transfer without interactive confirmation"
)]
pub accept_all: bool,
#[arg(
long,
value_name = "FILE",
help = "Require an explicit transfer PSK read from a file"
)]
pub psk_file: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct DaemonArgs {
#[arg(long, help = "Listen address for incoming QUIC transfers")]
pub listen: Option<SocketAddr>,
#[arg(long, value_name = "DIR", help = "Destination directory")]
pub dest: Option<PathBuf>,
#[arg(
long,
value_name = "FILE",
help = "Require an explicit transfer PSK read from a file"
)]
pub psk_file: Option<PathBuf>,
}
#[derive(Debug, Subcommand)]
pub enum PeersCommand {
#[command(
about = "Trust a full fingerprint or discovered peer hint",
after_help = "Examples:\n ferry peers trust 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n ferry peers trust stephen-mbp\n ferry peers trust 012345"
)]
Trust {
#[arg(value_name = "FINGERPRINT_OR_HINT")]
fingerprint: String,
},
#[command(about = "Remove a trusted peer fingerprint")]
Forget {
#[arg(value_name = "FINGERPRINT")]
fingerprint: String,
},
}