purple-ssh 2.4.0

TUI for managing SSH servers. Search, connect, transfer files and sync from 11 cloud providers. Edits ~/.ssh/config with round-trip fidelity.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
<h1 align="center">purple.<br>SSH config manager with file transfer<br>and cloud sync.</h1>

<p align="center">
  <strong>Stop scrolling through your SSH config. Start searching it.</strong><br>
  Find any server instantly, connect with Enter, transfer files visually and sync from 11 cloud providers. One TUI that edits your <code>~/.ssh/config</code> directly.
</p>

<p align="center">
  <a href="https://crates.io/crates/purple-ssh"><img src="https://img.shields.io/crates/v/purple-ssh.svg" alt="purple-ssh crate version on crates.io"></a>
  <a href="https://crates.io/crates/purple-ssh"><img src="https://img.shields.io/crates/d/purple-ssh.svg" alt="purple-ssh total downloads"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
  <a href="https://getpurple.sh"><img src="https://img.shields.io/badge/Website-getpurple.sh-9333ea.svg" alt="purple website"></a>
</p>

<p align="center"><img src="demo.gif" alt="purple SSH config manager TUI demo: searching hosts, transferring files, connecting via SSH and syncing cloud providers" width="800"></p>

## What is purple?

purple turns your `~/.ssh/config` into a searchable, visual interface. Find any host instantly, connect with Enter, browse remote files side by side and sync servers from eleven cloud providers. One TUI. No context switching.

It reads your existing config, lets you search, filter, tag and connect with a single keystroke, and writes changes back without touching your comments or unknown directives. Transfer files visually, run commands across servers and handle SSH passwords automatically. Free, open-source, runs on macOS and Linux.

## Install

```bash
curl -fsSL getpurple.sh | sh
```

<details>
<summary>Other install methods</summary>

<br>

**Homebrew (macOS)**

```bash
brew install erickochen/purple/purple
```

**Cargo** (crate name: `purple-ssh`)

```bash
cargo install purple-ssh
```

**From source**

```bash
git clone https://github.com/erickochen/purple.git
cd purple && cargo build --release
```

</details>

## Self-update

```bash
purple update
```

Downloads the latest release from GitHub, verifies the checksum and replaces the binary in place. macOS and Linux (installed via `curl`). Homebrew users should run `brew upgrade erickochen/purple/purple` instead. Cargo users should run `cargo install purple-ssh`. The TUI also checks for updates on startup and shows a notification in the title bar when a new version is available.

## Features

### Search and connect

Find any host in under a second, no matter how large your config. Instant fuzzy search across aliases, hostnames, users, tags and providers. Navigate with `j`/`k`, connect with `Enter`. Frecency sorting surfaces your most-used and most-recent hosts.

### Tags

Organize hosts by environment, team or project without external tools. Label hosts with `#tags`. Filter with the tag picker (`#` key) or type `tag:web` in search. Tags are stored as SSH config comments and survive round-trips.

### Cloud provider sync

Never manually add a server IP to your SSH config again. Pull servers from **AWS EC2**, **DigitalOcean**, **Vultr**, **Linode (Akamai)**, **Hetzner**, **UpCloud**, **Proxmox VE**, **Scaleway**, **GCP (Compute Engine)**, **Azure** and **Tailscale** directly into `~/.ssh/config`. Sync adds new hosts, updates changed IPs and optionally removes deleted servers. Tags from your cloud provider are merged with local tags.

```bash
purple provider add digitalocean --token YOUR_TOKEN   # or use PURPLE_TOKEN env var
purple provider add digitalocean --token-stdin         # pipe from password manager
purple provider add aws --profile default --regions us-east-1,eu-west-1
purple provider add aws --token AKID:SECRET --regions us-east-1,eu-west-1
purple provider add proxmox --url https://pve:8006 --token user@pam!token=secret
purple provider add gcp --token /path/to/sa-key.json --project my-project --regions us-central1-a
purple provider add azure --token /path/to/sp.json --regions SUBSCRIPTION_ID
purple provider add tailscale                               # local CLI, no token needed
purple provider add tailscale --token tskey-api-YOUR_KEY    # or use API
purple provider add digitalocean --token YOUR_TOKEN --no-auto-sync  # disable startup sync
purple sync                                            # sync all providers
purple sync --dry-run                                  # preview changes
purple sync --remove                                   # remove deleted hosts
purple sync --reset-tags                               # replace local tags with provider tags
```

Synced hosts are tagged by provider and appear alongside your manual hosts. Auto-sync runs on startup for providers that have it enabled (configurable per provider). The provider list is sorted by last sync time and shows sync results.

### SSH tunnel management

Add, edit and manage LocalForward, RemoteForward and DynamicForward rules per host. Start and stop background SSH tunnels from the TUI or CLI.

```bash
purple tunnel list
purple tunnel add myserver L:8080:localhost:80
purple tunnel start myserver
```

### Command snippets

Save frequently used commands and run them on one host, a selection of hosts or all visible hosts at once. Output is captured in a scrollable panel inside the TUI. Multi-host runs execute in parallel. Snippets support `{{param}}` placeholders with optional defaults. Snippets are stored in `~/.purple/snippets` and managed from the TUI or CLI.

```bash
purple snippet add check-disk "df -h" --description "Check disk usage"
purple snippet add check-path "df -h {{path:/tmp}}"   # parameterized with default
purple snippet add uptime "uptime"
purple snippet run check-disk myserver             # run on single host
purple snippet run check-disk --tag prod           # run on all hosts with tag
purple snippet run check-disk --all                # run on all hosts
purple snippet run check-disk --all --parallel     # run concurrently
purple snippet list                                # list all snippets
purple snippet remove check-disk                   # remove a snippet
```

In the TUI, press `r` to run a snippet on the selected host. Select multiple hosts with `Ctrl+Space` and press `r` to run on all selected. Press `R` to run on all visible hosts. `Enter` runs the snippet with captured output in a scrollable panel. Press `!` for raw terminal mode (useful for interactive commands). Search snippets with `/` in the picker.

**Parameters.** Use `{{name}}` for a required parameter or `{{name:default}}` for one with a default value. When you run a parameterized snippet, a form appears where you fill in the values. A live preview of the resolved command is shown at the bottom. Values are shell-escaped automatically to prevent injection.

```
grep {{pattern}} {{file:/var/log/syslog}}   # two params, one with default
docker logs {{container}}                    # required param, no default
```

### Remote file explorer

Press `f` on any host to open a split-screen file explorer. Your local filesystem on the left, the remote server on the right. Navigate directories, select files and copy them between machines with `Enter`. No more constructing scp paths from memory.

The explorer uses your existing SSH config. ProxyJump chains, password sources and active tunnels all work transparently. Select multiple files with `Ctrl+Space` or select all with `Ctrl+A`. Copy entire directories and confirm the transfer direction before anything moves. Toggle hidden files with `.` and refresh both panes with `R`. Paths are remembered per host so you pick up where you left off.

### Round-trip fidelity

Comments, indentation, unknown directives, CRLF line endings, equals-syntax and inline comments are all preserved through every read-write cycle. Consecutive blank lines are collapsed. Hosts from `Include` files are displayed but never modified.

### Bulk import

Import hosts from a hosts file or `~/.ssh/known_hosts`. No manual re-entry.

```bash
purple import hosts.txt
purple import --known-hosts
```

### Password management

Configure a password source per host. Press `Enter` on the Password Source field in the host form to pick a source from the overlay or type it directly. Purple acts as its own SSH_ASKPASS program and retrieves passwords automatically on connect.

Supported sources: **OS Keychain**, **1Password** (`op://`), **Bitwarden** (`bw:`), **pass** (`pass:`), **HashiCorp Vault** (`vault:`) or any custom command. Press `Ctrl+D` in the password picker to set a source as the global default.

### SSH key management

Browse your SSH keys with metadata (type, bits, fingerprint, comment) and see which hosts reference each key.

### Additional features

- **Ping** TCP connectivity check per host or all at once (toggle to clear)
- **Clipboard** Copy the SSH command or full config block
- **Atomic writes** Temp file, chmod 600, rename. No half-written configs.
- **Automatic backups** Every write creates a timestamped backup (keeps the last 5)
- **Host key reset** Detects changed host keys after a server reinstall and offers to remove the old key and reconnect
- **Auto-reload** Detects external config changes and reloads automatically
- **Detail panel** Split-pane view showing connection info, activity sparkline, tags, provider metadata, tunnels and snippets alongside the host list. Toggle with `v`
- **Minimal UI** Monochrome with subtle color for status messages. Works in any terminal, any font. Respects [NO_COLOR](https://no-color.org/)
- **Shell completions** Bash, zsh and fish via `purple --completions`

## Usage

```bash
purple                              # Launch the TUI
purple myserver                     # Connect or search
purple -c myserver                  # Direct connect
purple --config ~/other/ssh_config  # Use alternate config file
purple --list                       # List all hosts
purple add deploy@10.0.1.5:22      # Quick-add a host
purple add user@host --alias name   # Quick-add with custom alias
purple add user@host --key ~/.ssh/id_ed25519  # Quick-add with key
purple import hosts.txt             # Bulk import from file
purple import --known-hosts         # Import from known_hosts
purple provider add digitalocean    # Configure cloud provider
purple provider list                # List configured providers
purple provider remove digitalocean # Remove provider
purple sync                         # Sync all providers
purple sync digitalocean            # Sync single provider
purple sync --dry-run               # Preview sync changes
purple sync --remove                # Remove hosts deleted from provider
purple sync --reset-tags            # Replace local tags with provider tags
purple tunnel list                  # List configured tunnels
purple tunnel list myserver         # List tunnels for a host
purple tunnel add myserver L:8080:localhost:80  # Add forward
purple tunnel remove myserver L:8080:localhost:80  # Remove forward
purple tunnel start myserver        # Start tunnel (Ctrl+C to stop)
purple snippet list                 # List saved snippets
purple snippet add NAME "COMMAND"   # Add a snippet
purple snippet remove NAME          # Remove a snippet
purple snippet run NAME myserver    # Run on a host
purple snippet run NAME --tag prod  # Run on hosts with tag
purple snippet run NAME --all       # Run on all hosts
purple snippet run NAME --all --parallel  # Run concurrently
purple password set myserver        # Store password in OS keychain
purple password remove myserver     # Remove password from keychain
purple update                       # Update to latest version
purple --completions zsh            # Shell completions
```

<details>
<summary><strong>Keybindings</strong>. Press <code>?</code> in the TUI for the cheat sheet</summary>

<br>

**Host List**

| Key         | Action                           |
| ----------- | -------------------------------- |
| `j` / `k`   | Navigate down and up             |
| `PgDn`/`PgUp`| Page down / up                  |
| `Enter`     | Connect to selected host         |
| `a`         | Add new host                     |
| `e`         | Edit selected host               |
| `d`         | Delete selected host             |
| `c`         | Clone host                       |
| `y`         | Copy SSH command                 |
| `x`         | Export config block to clipboard |
| `/`         | Search and filter                |
| `#`         | Filter by tag                    |
| `t`         | Tag host                         |
| `s`         | Cycle sort mode                  |
| `g`         | Group by provider                |
| `i`         | Inspect host details             |
| `v`         | Toggle detail panel              |
| `u`         | Undo last delete                 |
| `p`         | Ping selected host (toggle)      |
| `P`         | Ping all hosts (toggle)          |
| `S`         | Cloud provider sync              |
| `Ctrl+Space`| Select / deselect host           |
| `r`         | Run snippet on host(s)           |
| `R`         | Run snippet on all visible       |
| `f`         | Remote file explorer (scp)       |
| `T`         | Manage host tunnels              |
| `K`         | SSH key list                     |
| `?`         | Help                             |
| `q` / `Esc` | Quit                             |

**Tunnel List**

| Key         | Action                 |
| ----------- | ---------------------- |
| `j` / `k`   | Navigate down and up   |
| `Enter`     | Start / stop tunnel    |
| `a`         | Add tunnel             |
| `e`         | Edit tunnel            |
| `d`         | Delete tunnel          |
| `q` / `Esc` | Back                   |

**Provider List**

| Key         | Action                 |
| ----------- | ---------------------- |
| `j` / `k`   | Navigate down and up   |
| `Enter`     | Configure provider     |
| `s`         | Sync selected provider |
| `d`         | Remove provider        |
| `q` / `Esc` | Back (cancels syncs)   |

**Snippet Picker**

| Key         | Action                         |
| ----------- | ------------------------------ |
| `j` / `k`   | Navigate down and up           |
| `Enter`     | Run snippet (captured output)  |
| `!`         | Run snippet (raw terminal)     |
| `/`         | Search snippets                |
| `a`         | Add snippet                    |
| `e`         | Edit snippet                   |
| `d`         | Delete snippet                 |
| `q` / `Esc` | Back                           |

**Snippet Output**

| Key         | Action                 |
| ----------- | ---------------------- |
| `j` / `k`   | Scroll up and down     |
| `g` / `G`   | Jump to top / bottom   |
| `n` / `N`   | Next / previous host   |
| `c`         | Copy output            |
| `Ctrl+C`    | Cancel execution       |
| `q` / `Esc` | Close                  |

**File Explorer** (press `f` on a host)

| Key         | Action                 |
| ----------- | ---------------------- |
| `Tab`       | Switch pane            |
| `j` / `k`   | Navigate               |
| `Enter`     | Open directory / copy  |
| `Backspace` | Go up                  |
| `Ctrl+Space`| Select / deselect      |
| `Ctrl+A`   | Select all / none       |
| `.`         | Toggle hidden files    |
| `R`         | Refresh both panes     |
| `Esc`       | Close                  |

**Search**

| Key                 | Action                 |
| ------------------- | ---------------------- |
| Type                | Filter hosts           |
| `Enter`             | Connect to selected    |
| `Esc`               | Cancel search          |
| `Tab` / `Shift+Tab` | Next / previous result |
| `tag:name`          | Fuzzy tag filter       |
| `tag=name`          | Exact tag filter       |

**Form**

| Key                 | Action                                     |
| ------------------- | ------------------------------------------ |
| `Tab` / `Shift+Tab` | Next / previous field                      |
| `Enter`             | Save (or pick key/password on those fields)|
| `Ctrl+D`            | Set global default (in password picker)    |
| `Esc`               | Cancel                                     |

</details>

## How purple compares

Most SSH tools read your config but don't write it, sync one cloud but not ten, or require a GUI and a subscription. purple does all of it from the terminal.

**It edits your real SSH config.** Most SSH tools only read. purple reads, edits and writes `~/.ssh/config` directly with full round-trip fidelity.

**It syncs cloud servers.** purple is the only SSH config manager that pulls hosts from 11 cloud providers into your config. Configure once, sync anytime.

**It runs commands across hosts.** Save command snippets and execute them on one host, a selection or all hosts at once. Sequential or parallel. No Ansible, no Fabric, no extra tools.

**It manages SSH passwords.** Store passwords in your OS keychain or pull them from 1Password, Bitwarden, pass, HashiCorp Vault or a custom command. Purple handles SSH_ASKPASS automatically.

**It transfers files visually.** Open a split-screen file explorer on any host, browse remote directories alongside local ones and copy files with a keystroke. No scp paths to remember, no separate tool to open.

**It imports what you already have.** Bulk import from host files or `~/.ssh/known_hosts`. No manual re-entry.

**Single Rust binary.** No runtime, no daemon, no async framework, no Node.js, no Python. Install and run. Works on macOS and Linux.

## Cloud providers

purple syncs servers from eleven cloud providers into your SSH config. Each provider is configured with an API token (or AWS credentials profile). Tailscale also works without a token by using the local CLI. Synced hosts get an alias prefix (e.g. `do-web-1`) and are tracked via comments in your config. Provider metadata (region, plan, OS, status. Proxmox: node, type, status) is stored in config comments and displayed in the detail panel. Run `purple sync` to update all providers at once. Auto-sync runs on startup for providers that have it enabled.

### AWS EC2

Syncs running EC2 instances across multiple regions. Authenticate with an `~/.aws/credentials` profile or an explicit access key pair. AMI names are resolved for OS metadata. Tags from EC2 are synced (excluding internal `aws:*` tags).

```bash
purple provider add aws --profile default --regions us-east-1,eu-west-1
purple provider add aws --token AKID:SECRET --regions us-east-1,eu-west-1
```

### DigitalOcean

Syncs all Droplets. Uses the DigitalOcean API v2 with page-based pagination. Droplet tags are synced as purple tags. Requires a personal access token.

```bash
purple provider add digitalocean --token YOUR_TOKEN
```

### Vultr

Syncs all instances. Uses the Vultr API v2 with cursor-based pagination. Prefers IPv4. Falls back to IPv6 when no v4 address is available. Instance tags are synced.

```bash
purple provider add vultr --token YOUR_TOKEN
```

### Linode (Akamai)

Syncs all Linodes. Uses the Linode API v4 with page-based pagination. Filters out private and reserved IP addresses (10.x, 172.16-31.x, 192.168.x, CGNAT). Instance tags are synced.

```bash
purple provider add linode --token YOUR_TOKEN
```

### Hetzner

Syncs all servers from Hetzner Cloud. Uses page-based pagination. Hetzner labels (key-value pairs) are converted to tags in `key=value` format.

```bash
purple provider add hetzner --token YOUR_TOKEN
```

### UpCloud

Syncs all servers. Uses the UpCloud API with Bearer token authentication. Requires N+1 API calls (one list request, then one detail request per server to get IP addresses). Tags and labels are both synced.

```bash
purple provider add upcloud --token YOUR_TOKEN
```

### Proxmox VE

Syncs QEMU VMs and LXC containers from a Proxmox VE cluster. Requires an API token in `user@realm!tokenid=secret` format and the cluster URL. Uses N+1 API calls (cluster resources list, then per-VM detail requests). IP addresses are resolved via the QEMU guest agent or LXC network interfaces. Templates and stopped VMs are skipped. Supports self-signed TLS certificates with `--no-verify-tls`. Auto-sync is disabled by default due to the higher API call volume.

```bash
purple provider add proxmox --url https://pve.example.com:8006 --token user@pam!token=secret
purple provider add proxmox --url https://pve:8006 --token TOKEN --no-verify-tls
```

### Scaleway

Syncs instances across multiple availability zones (Paris, Amsterdam, Warsaw, Milan). Uses the Scaleway Instance API with `X-Auth-Token` header authentication and page-based pagination. Instance tags are synced. IP selection prefers public IPv4, falls back to IPv6 and then private IP.

```bash
purple provider add scaleway --token YOUR_TOKEN --regions fr-par-1,nl-ams-1
```

### GCP (Compute Engine)

Syncs instances across multiple zones using the aggregatedList API. Authenticate with a service account JSON key file or a raw access token (e.g. from `gcloud auth print-access-token`). Requires a GCP project ID. Instance network tags and labels are synced. IP selection prefers external (natIP), falls back to internal.

```bash
purple provider add gcp --token /path/to/sa-key.json --project my-project
purple provider add gcp --token /path/to/sa-key.json --project my-project --regions us-central1-a,europe-west1-b
purple provider add gcp --token "$(gcloud auth print-access-token)" --project my-project
```

### Azure

Syncs VMs across multiple subscriptions using the Azure Resource Manager API. Authenticate with a service principal JSON file (tenantId, clientId, clientSecret) or a raw Bearer token (e.g. from `az account get-access-token`). Requires at least one subscription ID. VM tags are synced. IP selection prefers public IP, falls back to private IP. Batch resolution (VMs, NICs, Public IPs) for efficient API usage.

```bash
purple provider add azure --token /path/to/sp.json --regions SUBSCRIPTION_ID
purple provider add azure --token /path/to/sp.json --regions SUB_ID_1,SUB_ID_2
purple provider add azure --token "$(az account get-access-token --query accessToken -o tsv)" --regions SUBSCRIPTION_ID
```

### Tailscale

Syncs devices from your Tailscale network. Works in two modes: without a token it uses the local `tailscale` CLI (no API key needed), with a token it uses the Tailscale HTTP API. Tailscale tags are synced (with `tag:` prefix stripped). IPv4 (100.x) is preferred over IPv6.

```bash
purple provider add tailscale                               # local CLI mode (no token needed)
purple provider add tailscale --token tskey-api-YOUR_KEY    # API mode
```

### Shared sync options

```bash
purple sync                     # sync all providers
purple sync --dry-run           # preview changes without writing
purple sync --remove            # remove hosts deleted from provider
purple sync --reset-tags        # replace local tags with provider tags
purple provider add NAME --no-auto-sync   # disable auto-sync on startup
```

## Password managers

purple can retrieve SSH passwords automatically when you connect. Configure a password source per host and purple takes care of the rest. No manual password entry, no plaintext passwords in your config.

### How it works

When you connect to a host that has a password source configured, purple sets itself as the SSH_ASKPASS program. SSH calls purple back to retrieve the password, and purple fetches it from whatever source you configured (keychain, 1Password, Bitwarden, etc.). This happens transparently. You press Enter to connect and the password is filled in automatically.

If the password turns out to be wrong, purple detects the retry (SSH calls ASKPASS again within 60 seconds) and falls back to the normal interactive password prompt so you don't get stuck in a loop.

Password sources are stored as comments in your SSH config (`# purple:askpass <source>`), so they survive edits and are visible if you open the config in a text editor.

### Setting a password source

**In the TUI.** Open the host form (`e` to edit, `a` to add), navigate to the Password Source field and press `Enter` to open the picker. Select a source and fill in the required details. Press `Ctrl+D` in the picker to set a source as the global default for all hosts that don't have one configured.

**From the CLI.** Use the password command for keychain entries, or edit the host form for other sources:

```bash
purple password set myserver        # prompts for password, stores in OS keychain
purple password remove myserver     # removes from keychain
```

### Supported sources

#### OS Keychain

Stores and retrieves passwords using macOS Keychain (`security`) or Linux Secret Service (`secret-tool`). This is the only source that stores the actual password. All other sources call an external tool at connect time.

When you connect for the first time with `keychain` as the source, purple prompts for the password and stores it. Subsequent connections retrieve it silently. Changing the source away from `keychain` removes the stored password automatically. Renaming a host alias migrates the keychain entry.

```
Source value: keychain
```

#### 1Password

Retrieves passwords using the `op` CLI. Requires 1Password CLI installed and authenticated. Uses the standard `op://` URI format to reference a specific vault, item and field.

```
Source value: op://Vault/Item/field
```

#### Bitwarden

Retrieves passwords using the `bw` CLI. Uses the item name or ID as the lookup key. When the vault is locked, purple prompts for your master password and caches the session for the remainder of the TUI session. Works with self-hosted Bitwarden and Vaultwarden (configure via `bw config server <url>`).

```
Source value: bw:item-name
```

#### pass (password-store)

Retrieves passwords from [pass](https://www.passwordstore.org/) (the standard Unix password manager). Returns the first line of the entry, following the pass convention.

```
Source value: pass:path/to/entry
```

#### HashiCorp Vault

Retrieves secrets using the `vault` CLI. Reads KV v2 secrets. Specify the path and field separated by `#`. Defaults to the `password` field if no field is specified. Requires `VAULT_ADDR` and authentication to be configured.

```
Source value: vault:secret/path#field
```

#### Custom command

Run any shell command that prints the password to stdout. The placeholders `%a` (alias) and `%h` (hostname) are substituted before execution. Useful for integrating with password managers or scripts that purple doesn't support natively.

```
Source value: my-script %a %h
```

## FAQ

**Can I transfer files with purple?**
Yes. Press `f` on any host to open the remote file explorer. It shows local files on the left and the remote server on the right. Navigate directories, select files and copy them between machines with `Enter`. Works through ProxyJump chains, password sources and active tunnels.

**What cloud providers does purple support?**
AWS EC2, DigitalOcean, Vultr, Linode (Akamai), Hetzner, UpCloud, Proxmox VE, Scaleway, GCP (Compute Engine), Azure and Tailscale. See the [Cloud providers](#cloud-providers) section for setup details per provider.

**Does purple modify my existing SSH config?**
Only when you add, edit, delete or sync. Auto-sync runs on startup for providers that have it enabled (toggle per provider, on by default except Proxmox). All writes are atomic with automatic backups.

**Will purple break my comments or formatting?**
No. purple preserves comments, indentation and unknown directives through every read-write cycle. Consecutive blank lines are collapsed to one.

**Does purple need a daemon or background process?**
No. It's a single 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. The TUI checks GitHub for new releases on startup (cached for 24 hours). No config data is transmitted in either case.

**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.

**How does password management work?**
Configure a password source per host (Enter on the Password Source field in the host form) and purple retrieves the password automatically when you connect via the SSH_ASKPASS mechanism. Six sources are supported: OS Keychain, 1Password, Bitwarden, pass, HashiCorp Vault and custom commands. See the [Password managers](#password-managers) section for details on each source and how to set them up.

**How does provider sync handle name conflicts?**
Synced hosts get an alias prefix (e.g. `do-web-1` for DigitalOcean). If a name collides with an existing host, purple appends a numeric suffix (`do-web-1-2`).

**Why is the crate called `purple-ssh`?**
The name `purple` was taken on crates.io. The binary is still called `purple`.

## Feedback

Found a bug or have a feature request? [Open an issue on GitHub](https://github.com/erickochen/purple/issues).

## Built with

Rust. 3000+ tests. Zero clippy warnings. No async runtime. Single binary.

<p align="center">
  <a href="LICENSE">MIT License</a>
</p>