purple-ssh 1.24.0

Manage SSH configs and launch connections from the terminal. TUI host manager with search, tags, tunnels, command snippets, password management (keychain, 1Password, Bitwarden, pass, Vault), cloud sync (AWS EC2, DigitalOcean, Vultr, Linode, Hetzner, UpCloud, Proxmox VE), self-update and round-trip fidelity for ~/.ssh/config.
Documentation
use std::io::Write;
use std::process::{Command, Stdio};

/// Try to find a working clipboard command by checking PATH.
fn clipboard_cmd() -> Result<&'static str, String> {
    let candidates = [
        ("pbcopy", &[][..]),                              // macOS
        ("wl-copy", &[][..]),                             // Wayland
        ("xclip", &["-selection", "clipboard"][..]),      // X11
        ("xsel", &["--clipboard", "--input"][..]),        // X11 alt
    ];

    for (cmd, _) in &candidates {
        let found = Command::new("sh")
            .args(["-c", &format!("command -v {} >/dev/null 2>&1", cmd)])
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .status()
            .is_ok_and(|s| s.success());
        if found {
            return Ok(cmd);
        }
    }

    Err("No clipboard tool found. Install pbcopy (macOS), wl-copy (Wayland), or xclip/xsel (X11).".to_string())
}

/// Get the extra args needed for a clipboard command.
fn clipboard_args(cmd: &str) -> &'static [&'static str] {
    match cmd {
        "xclip" => &["-selection", "clipboard"],
        "xsel" => &["--clipboard", "--input"],
        _ => &[],
    }
}

/// Copy text to the system clipboard.
pub fn copy_to_clipboard(text: &str) -> Result<(), String> {
    let cmd = clipboard_cmd()?;
    let args = clipboard_args(cmd);

    let mut child = Command::new(cmd)
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .map_err(|_| format!("Failed to run {}.", cmd))?;

    let write_result = (|| {
        let stdin = child
            .stdin
            .as_mut()
            .ok_or_else(|| format!("Failed to write to {}.", cmd))?;
        stdin
            .write_all(text.as_bytes())
            .map_err(|_| format!("Failed to write to {}.", cmd))
    })();

    // Drop stdin so the child process gets EOF before we wait
    child.stdin.take();

    // Reap the child process and check exit status
    let status = child
        .wait()
        .map_err(|_| format!("Failed to wait for {}.", cmd))?;

    write_result?;

    if !status.success() {
        return Err(format!("{} exited with error.", cmd));
    }

    Ok(())
}