purple.

SSH config manager and host launcher for the terminal

$ curl -fsSL getpurple.sh | sh
or via Homebrew: brew install erickochen/purple/purple
or via cargo: cargo install purple-ssh
purple SSH config manager TUI demo: searching hosts, connecting via SSH and syncing cloud providers in the terminal

Features

Search. Fuzzy find across aliases, hostnames, users and tags
Tags. Organize hosts with #tags and filter instantly
Snippets. Save commands, run on one or many hosts. Sequential or parallel
Tunnels. Manage SSH port forwards (local, remote, dynamic) per host
Round-trip fidelity. Preserves comments, formatting and unknown directives
Bulk import. From hosts files or ~/.ssh/known_hosts
Passwords. OS Keychain, 1Password, Bitwarden, pass, Vault integration
SSH keys. Browse keys with metadata and linked hosts
Cloud sync. Pull servers from 9 cloud providers into your config
Self-update. Run purple update
Atomic writes. Temp file, chmod 600, rename. Automatic backups
Ping. TCP connectivity check per host or all at once
Detail panel. Split-pane view with connection info, activity sparkline, provider metadata, tunnels and snippets

Cloud provider sync

Pull servers from nine cloud providers directly into your ~/.ssh/config. Sync adds new hosts, updates changed IPs and merges tags. Provider metadata (region, plan, OS, status. Proxmox: node, type, status) is stored in config comments and displayed in the detail panel. Tags you add manually are preserved across syncs.

AWS EC2
DigitalOcean
Vultr
Linode
Hetzner
UpCloud
Proxmox VE
Scaleway
GCP

Preview changes with --dry-run. Remove deleted hosts with --remove. Replace local tags with --reset-tags.

Your config, respected

purple reads and writes ~/.ssh/config directly with full round-trip fidelity. Comments, indentation, unknown directives, CRLF line endings and Include files are all preserved. Every write is atomic (temp file, chmod 600, rename) with automatic backups.

Built with Rust

Single binary. No runtime, no daemon, no async framework. 2700+ tests. Zero clippy warnings. MIT licensed.

FAQ

Does purple modify my existing SSH config?
Only when you add, edit, delete or sync. All writes are atomic with automatic backups. Auto-sync runs on startup for providers that have it enabled (configurable per provider).
Will purple break my comments or formatting?
No. purple preserves comments, indentation and unknown directives through every read-write cycle.
Does purple need a daemon or background process?
No. It is a single Rust binary. Run it, use it, close it.
Does purple send my SSH config anywhere?
No. Your config never leaves your machine. Provider sync calls cloud APIs to fetch server lists. No config data is transmitted.
How do command snippets work?
Save commands in purple and run them on remote hosts via SSH. Select one host, multiple hosts or all visible hosts. The CLI supports parallel execution with --parallel. Snippets are stored locally in ~/.purple/snippets.
How does password management work?
Set a password source per host via the TUI or a global default. When you connect, purple acts as SSH_ASKPASS and retrieves the password automatically. Supported sources: OS Keychain, 1Password, Bitwarden, pass, HashiCorp Vault and custom commands.
Can I use purple with Include files?
Yes. Hosts from Include files are displayed in the TUI but never modified. purple resolves Include directives recursively (up to depth 5) with tilde and glob expansion.
What cloud providers does purple support?
AWS EC2, DigitalOcean, Vultr, Linode (Akamai), Hetzner, UpCloud, Proxmox VE, Scaleway and GCP (Compute Engine). Each provider is configured with an API token or credentials profile. Synced hosts are tracked in your SSH config and updated on each sync.
How do I sync Google Cloud (GCP) instances?
Run purple provider add gcp --token /path/to/sa-key.json --project my-project. Pass a service account JSON key file path as the token. Purple reads the key, creates a JWT and exchanges it for an access token automatically. Alternatively, pass a raw access token from gcloud auth print-access-token. Only compute.readonly scope is required.