One CLI to manage all your SSH identities, keys, and server connections — no more hand-editing
~/.ssh/config.
You are a developer on a single machine. You have:
- A personal GitHub account for side projects
- A work GitHub account that your company owns
- A test server your team gave you access to via a
.pemkey - Maybe a freelance client with their own GitLab
Every time you sit down to work you're either:
- Googling "how to use two GitHub accounts SSH" again
- Digging through
~/Downloadsfor a.pemfile you got in Slack three months ago - Breaking your
~/.ssh/configwith a stray edit - Forgetting which email address commits to which repo
sshelf fixes all of this. It manages SSH keys, host aliases, and agent sessions behind one clean CLI. You never touch ~/.ssh/config by hand again.
- Installation
- Real-world scenarios
- Commands
- Shell integration
- How sshelf stores data
- Key rotation
- Jump hosts (bastion servers)
- Doctor checks reference
- Security
- Building from source
- Contributing
# macOS (Apple Silicon)
curl -Lo sshelf https://github.com/ajf1016/sshelf/releases/latest/download/sshelf-darwin-arm64
chmod +x sshelf && sudo mv sshelf /usr/local/bin/
# macOS (Intel)
curl -Lo sshelf https://github.com/ajf1016/sshelf/releases/latest/download/sshelf-darwin-amd64
chmod +x sshelf && sudo mv sshelf /usr/local/bin/
# Linux (x86_64)
curl -Lo sshelf https://github.com/ajf1016/sshelf/releases/latest/download/sshelf-linux-amd64
chmod +x sshelf && sudo mv sshelf /usr/local/bin/go install github.com/ajf1016/sshelf/cmd/sshelf@latestbrew install ajf1016/tap/sshelfThe Homebrew tap is coming soon. Use the binary install above in the meantime.
Prerequisites: ssh-keygen and ssh-agent ship with macOS and every major Linux distro by default.
You have alex@gmail.com on personal GitHub and alex@acme.io on work GitHub. Both repos live on the same laptop.
One-time setup:
# Create your personal identity — generates a key pair
sshelf profile init
# Profile name: personal
# Type: git → GitHub
# Email: alex@gmail.com
# Username: alex-personal
# Create your work identity — generates a separate key pair
sshelf profile init
# Profile name: work
# Type: git → GitHub
# Email: alex@acme.io
# Username: alex-acmesshelf generates two separate key pairs and writes this automatically to ~/.ssh/config:
Host github-personal
HostName github.com
User git
IdentityFile ~/.sshelf/keys/personal_ed25519
IdentitiesOnly yes
Host github-work
HostName github.com
User git
IdentityFile ~/.sshelf/keys/work_ed25519
IdentitiesOnly yes
Copy the public keys to GitHub (you only do this once):
sshelf key copy personal_ed25519 # copies to clipboard → paste into GitHub personal settings
sshelf key copy work_ed25519 # copies to clipboard → paste into GitHub work settingsDay-to-day use:
# Clone a personal repo — use the alias, not github.com directly
git clone git@github-personal:alex/my-side-project.git
# Clone a work repo
git clone git@github-work:acme/backend-service.git
# Switch your active git identity in the current terminal
sshelf-switch work # requires one-time: sshelf shell setup
# Verify who you are right now
sshelf whoami
# Active profile: work
# Git identity: alex <alex@acme.io>
# SSH key: ~/.sshelf/keys/work_ed25519 (created 12 days ago)
# Agent: running · PID 34521 · 2 keys loaded
# Host aliases: github-workYour senior Slacked you:
"Here's the staging server key 👇
ssh -i ~/Downloads/staging.pem ubuntu@203.0.113.42"
You don't want to type that full command every time. You don't want that .pem rotting in ~/Downloads forever.
One-time setup:
# Step 1 — bring the key into sshelf management
sshelf key add ~/Downloads/staging.pem
# → Imported key: staging (ed25519)
# → Key stored at ~/.sshelf/keys/staging.pem
# → Permissions set to 0600
# Step 2 — create a short alias for the server
sshelf host add
# Host alias → staging
# Hostname or IP → 203.0.113.42
# SSH user → ubuntu
# Identity key → staging.pem ← tab-completes from your imported keysFrom now on:
ssh staging
# or
sshelf host connect stagingBoth do exactly the same thing as the original long command — but you never have to remember the IP, the username, or which .pem to use.
Verify the connection works:
sshelf host test staging
# → Testing connection to staging (203.0.113.42)...
# → Connected successfully as ubuntuTeam grows — more servers:
sshelf host add --name prod --hostname 203.0.113.100 --user ubuntu --key staging.pem
sshelf host add --name dev-box --hostname 203.0.113.55 --user ec2-user --key staging.pem
sshelf host list
# ALIAS HOSTNAME USER KEY
# staging 203.0.113.42 ubuntu staging.pem
# prod 203.0.113.100 ubuntu staging.pem
# dev-box 203.0.113.55 ec2-user staging.pemBefore you wiped your old machine, you backed up all your sshelf keys:
sshelf key backup
# Passphrase: ••••••••••••
# Confirm: ••••••••••••
# ✓ 6 key files backed up to ~/.sshelf/keys-backup-2026-05-17.vaultYou copied that .vault file to a USB drive (or cloud storage). Now on the new machine:
# Install sshelf, then:
sshelf key restore /Volumes/USB/keys-backup-2026-05-17.vault
# Passphrase: ••••••••••••
# ✓ 6 key files restored to ~/.sshelf/keys/
sshelf doctor
# → All checks passYour profiles, host aliases, and every key are back exactly as they were. Nothing to reconfigure.
You already have ~/.ssh/id_ed25519, ~/.ssh/work_rsa, and a hand-crafted ~/.ssh/config. You want sshelf to take over without losing anything.
sshelf importsshelf scans your existing setup and lets you pick what to bring in:
→ Scanning ~/.ssh/config... found 3 Host blocks
→ Scanning ~/.ssh/... found 4 key files
Select keys to import:
◉ id_ed25519
◉ work_rsa
○ id_rsa_old ← left this one out
Select hosts to import:
◉ github.com
◉ gitlab.acme.io
○ 192.168.0.5 ← skipped this one too
✓ Imported 2 keys, 2 hosts
✓ SSH config updated
Your hand-written entries outside the sshelf block are never modified. Safe to run at any time.
After importing:
sshelf doctor --fix # fixes any permission issues on imported keysA profile is your identity: name, email, git username, and an SSH key pair. Create one per account you own.
Start the interactive wizard to create a new profile and generate its key pair.
sshelf profile initWalks you through:
? Profile name: work
? Profile type: git (GitHub / GitLab / Bitbucket)
? Platform: GitHub
? Email: alex@acme.io
? Username: alex-acme
? Key name: work_ed25519 ← pre-filled, press Enter to accept
? Key type: ed25519 (recommended)
✓ Profile "work" created!
Public key (paste into GitHub → Settings → SSH keys):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... alex@acme.io
Show all profiles. The active one is marked with ●.
sshelf profile list
NAME TYPE PLATFORM EMAIL KEY AGE
● work git github alex@acme.io work_ed25519 12d
personal git github alex@gmail.com personal_ed25519 3mo
freelance git gitlab alex@client.com freelance_rsa 8moActivate a profile. Updates the SSH config block and exports git identity env vars to your shell.
After running sshelf shell setup once, you use the short form:
sshelf-switch work
# Now git commits go out as alex@acme.io with the work key
sshelf-switch personal
# Now git commits go out as alex@gmail.com with the personal keyIf shell integration is not installed, use the raw form:
eval $(sshelf profile switch work)Why
eval? A subprocess cannot export env vars to its parent shell.switchprintsexport GIT_AUTHOR_NAME=...lines;evalapplies them.sshelf shell setupinstalls a wrapper so you never typeeval $(...)again.
Print full details of a profile including its public key.
sshelf profile show work
Profile: work
Type: git
Platform: github
Email: alex@acme.io
Username: alex-acme
Key: work_ed25519
Created: 2026-05-05
Public key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... alex@acme.ioUseful when you need to give your public key to a server admin or add it to a new platform.
Update profile fields interactively. Pre-filled with current values — press Enter to keep any field unchanged.
sshelf profile edit work
# Change email from alex@acme.io → alex@newcompany.ioDelete a profile and optionally its key pair.
sshelf profile remove freelance # prompts for confirmation
sshelf profile remove freelance --yes # skip confirmation
sshelf profile remove freelance --keep-key # delete profile, keep key filesAlso works as:
sshelf profile delete freelanceDuplicate a profile as a starting point. Useful when onboarding to a new project that shares the same platform.
sshelf profile clone work work-clientx
# Now edit the clone to set the right email
sshelf profile edit work-clientxRename a profile and update all host aliases that reference it automatically.
sshelf profile rename work acme-workKeys are stored under ~/.sshelf/keys/ with 0600 permissions enforced automatically.
Generate a standalone key pair without creating a full profile. Useful for server keys or temporary access.
sshelf key generate --name deploy_ed25519
sshelf key generate --name legacy_server --type rsa --comment "old server needs RSA"| Flag | Default | Description |
|---|---|---|
--name |
(required) | Base filename for the key pair |
--type |
ed25519 |
ed25519 or rsa |
--comment |
<name>@sshelf |
Comment embedded in the public key |
List all managed keys with age and which profile they belong to.
sshelf key list
NAME TYPE AGE PROFILE
work_ed25519 ed25519 12d work
personal_ed25519 ed25519 3mo personal
freelance_rsa rsa 8mo freelance
staging.pem rsa 47d — ← key without a profile (server key)Keys with no profile are server keys imported via key add. They still work fine — they just aren't tied to a git identity.
Import an existing key into sshelf management. Copies it to ~/.sshelf/keys/ and sets correct permissions.
# Your senior gave you a server key
sshelf key add ~/Downloads/staging.pem
# Bring in a key you already had in ~/.ssh/
sshelf key add ~/.ssh/id_ed25519After importing, the original file is no longer needed — sshelf owns a copy.
Delete a managed key pair.
sshelf key remove old_rsa
sshelf key remove old_rsa --yes # skip confirmationNote: if a profile references this key, the profile's key_name field will be blank until you update it with sshelf profile edit.
Generate a new key pair for an existing name. The old key is archived as <name>.bak — not deleted — giving you time to update GitHub/GitLab before removing it.
sshelf key rotate work_ed25519
→ Generating new ed25519 key...
→ Old key backed up to ~/.sshelf/keys/work_ed25519.bak
New public key (add this to GitHub, then revoke the old one):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...newkey alex@acme.io
Remember to update your platforms, then run:
sshelf key remove work_ed25519.baksshelf doctor will warn you when any key is older than 1 year.
Copy a key's public key content to your clipboard. Falls back to printing if no clipboard tool is available.
sshelf key copy work_ed25519
# → Public key of "work_ed25519" copied to clipboard.Paste directly into GitHub → Settings → SSH keys, or into a server's authorized_keys.
Encrypt all managed keys into a single vault file. You'll be prompted for a passphrase (min 8 characters).
sshelf key backup
# Passphrase: ••••••••••••
# Confirm: ••••••••••••
# ✓ 4 key file(s) backed up to ~/.sshelf/keys-backup-2026-05-17.vault
# Custom output location — e.g. USB drive
sshelf key backup --out /Volumes/USB/my-ssh-keys.vaultThe vault is AES-256-GCM encrypted. Without the passphrase it cannot be read. Store it on a USB drive, cloud storage, or your password manager.
Decrypt a vault file and restore keys into ~/.sshelf/keys/. Existing files are skipped unless --force is passed.
sshelf key restore ~/.sshelf/keys-backup-2026-05-17.vault
# Passphrase: ••••••••••••
# ✓ 4 key file(s) restored to ~/.sshelf/keys/
# Overwrite existing keys (e.g. after key rotation gone wrong)
sshelf key restore ~/backup/my-ssh-keys.vault --forceAfter restoring, run sshelf doctor to verify permissions.
A host is a saved SSH destination: an alias name, hostname or IP, login user, and which key to use. Once added, ssh <alias> works anywhere on your machine.
Add a host alias interactively, or pass flags to skip the prompts.
# Interactive — shows autocomplete suggestions as you type:
sshelf host add
# Host alias → staging
# Hostname or IP → 203.0.113.42
# SSH user → ubuntu ← suggests: ubuntu, root, ec2-user, admin...
# Identity key name → staging.pem ← tab-completes from your imported keys
# Link to profile → ← tab-completes from your profiles
# ProxyJump / bastion →
# Non-interactive (good for scripts):
sshelf host add --name staging --hostname 203.0.113.42 --user ubuntu --key staging.pem
sshelf host add --name prod --hostname 203.0.113.100 --user ubuntu --profile work| Flag | Description |
|---|---|
--name |
Short alias used in ssh <alias> |
--hostname |
Real hostname or IP address |
--user |
SSH login username |
--port |
SSH port (default 22) |
--key |
Key name to use (overrides profile key) |
--profile |
Link to a profile — inherits its key automatically |
--jump |
ProxyJump (bastion) alias |
List all host aliases, grouped by profile.
sshelf host list
Profile: work
ALIAS HOSTNAME USER KEY
github-work github.com git work_ed25519
staging 203.0.113.42 ubuntu work_ed25519
prod 203.0.113.100 ubuntu work_ed25519
Profile: personal
github-personal github.com git personal_ed25519
(no profile)
dev-box 203.0.113.55 ec2-user staging.pemFilter by profile:
sshelf host list --profile workRemove a host alias and regenerate the SSH config block.
sshelf host remove old-staging
sshelf host remove old-staging --yesRun a quick connectivity check using ssh -T. Shows whether the connection and key are working.
sshelf host test github-work
# Hi alex-acme! You've successfully authenticated, but GitHub does not provide shell access.
sshelf host test staging
# Connected to 203.0.113.42 (OpenSSH_9.0)
sshelf host test broken-server
# ssh: connect to host 203.0.113.99 port 22: Connection refusedRun this after adding a new host or rotating a key to confirm everything still works.
Open a live SSH session to a host alias. Identical to ssh <alias> but explicitly goes through sshelf's config resolution.
sshelf host connect staging
# → opens a terminal session on the server
sshelf host connect prodPush the linked profile's public key to a remote server's authorized_keys using ssh-copy-id. Use this to provision a new server before you have key-based auth set up — you'll be prompted for the password once.
sshelf host copy-id new-server
# → /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed.
# → alex@203.0.113.88's password: ••••••••
# → ✓ Key installed. You can now ssh in without a password.Set or update a ProxyJump (bastion) for an alias — for reaching servers that aren't directly accessible from the internet.
sshelf host jump prod-app --via bastion
# → Set ProxyJump for "prod-app" → bastionSee Jump hosts for the full bastion server workflow.
Manages the ssh-agent lifecycle. sshelf stores the agent's socket path and PID so keys stay loaded across terminal sessions on the same login.
Start a new ssh-agent and load all managed keys.
eval $(sshelf agent start)
# ssh-agent started.
# PID: 34521Kill the running agent.
sshelf agent stopShow agent state, PID, and which keys are loaded.
sshelf agent status
Agent: running
PID: 34521
Socket: /tmp/ssh-XxYyZz/agent.34521
Loaded keys:
256 SHA256:abc... alex@acme.io (ED25519)
256 SHA256:xyz... alex@gmail.com (ED25519)Restart the agent and reload all keys. Run this after a profile switch or key rotation.
sshelf agent reloadRun a suite of health checks and print a colour-coded table of results.
sshelf doctor
CHECK STATUS MESSAGE
config-dir PASS ~/.sshelf exists (0700)
permissions WARN staging.pem has permissions 0644, expected 0600 [fixable]
agent WARN ssh-agent is not running [fixable]
active-profile PASS active profile: work
key-age WARN freelance_rsa is 14 months old — run: sshelf key rotate freelance_rsa
orphaned-keys PASS no orphaned keys
3 issue(s). Run `sshelf doctor --fix` to auto-fix fixable items.Auto-fix everything fixable in one shot:
sshelf doctor --fixRun a single check by name:
sshelf doctor --check permissions
sshelf doctor --check key-age
sshelf doctor --check agentFixable checks: config-dir, permissions, orphaned-keys
Informational only: key-age, active-profile, agent (agent restart requires eval)
Already have SSH keys and a ~/.ssh/config? This command scans your existing setup and lets you migrate everything into sshelf without losing anything. Entries outside the managed block are never touched.
sshelf import
→ Scanning ~/.ssh/config... found 3 Host blocks
→ Scanning ~/.ssh/... found 4 key files
Select keys to import:
◉ id_ed25519 ← will be managed by sshelf
◉ work_rsa
○ id_rsa_old ← leaving this one out
Select hosts to import:
◉ github.com
◉ gitlab.acme.io
○ 192.168.0.5
Link imported items to a profile?
❯ work
✓ Imported key: id_ed25519
✓ Imported key: work_rsa
✓ Imported host: github.com
✓ Imported host: gitlab.acme.io
4 item(s) imported. SSH config updated. Keys stored in ~/.sshelf/keys/.
Run `sshelf doctor` to verify everything looks good.Print a snapshot of your current identity at a glance. Useful at the start of a work session.
sshelf whoami
Active profile: work
Git identity: alex <alex@acme.io>
SSH key: ~/.sshelf/keys/work_ed25519 (created 12 days ago)
Agent: running · PID 34521 · 2 keys loaded
Host aliases: github-work, staging, prodGenerate shell completion scripts. After sourcing, <Tab> completes profile names, host aliases, and key names live from your store.
# Zsh
sshelf completion zsh >> ~/.zshrc && source ~/.zshrc
# Bash
sshelf completion bash >> ~/.bashrc && source ~/.bashrc
# Fish
sshelf completion fish > ~/.config/fish/completions/sshelf.fishsshelf profile switch <Tab>
# work personal freelance
sshelf host connect <Tab>
# staging prod dev-box github-work
sshelf key rotate <Tab>
# work_ed25519 personal_ed25519 freelance_rsaShell integration utilities.
Install the sshelf-switch function into your shell config file. Run once after installing sshelf — then switch profiles without ever typing eval $(...) again.
sshelf shell setup
# ✓ Shell integration installed in ~/.zshrc
# Apply now: source ~/.zshrc
# Then use: sshelf-switch workDetects your shell automatically ($SHELL). Supports zsh, bash, and fish. Safe to re-run — checks for existing installation and skips if already done.
Run this once after installing sshelf. It detects your shell (zsh, bash, or fish) and appends the sshelf-switch function to your config file automatically.
sshelf shell setup
# ✓ Shell integration installed in ~/.zshrc
#
# Apply now (or open a new terminal):
# source ~/.zshrc
#
# Then switch profiles with:
# sshelf-switch work
# sshelf-switch personalFrom then on, switching is a single short command:
sshelf-switch work # activates work profile + sets git identity
sshelf-switch personal # activates personal profileWhat it installs (you can also add this manually if you prefer):
Zsh / Bash (~/.zshrc or ~/.bashrc):
# sshelf shell integration
sshelf-switch() {
eval $(sshelf profile switch "$1")
}Fish (~/.config/fish/config.fish):
# sshelf shell integration
function sshelf-switch
eval (sshelf profile switch $argv)
end# ~/.zshrc or ~/.bashrc
if ! sshelf agent status &>/dev/null; then
eval $(sshelf agent start)
fisshelf keeps all state under ~/.sshelf/. The only thing it touches in ~/.ssh/ is a clearly delimited block inside ~/.ssh/config.
| Path | Contents | Permissions |
|---|---|---|
~/.sshelf/ |
Root config directory | 0700 |
~/.sshelf/profiles.toml |
All profile definitions | 0600 |
~/.sshelf/hosts.toml |
Host alias definitions | 0600 |
~/.sshelf/keys/ |
Managed key pairs | 0700 |
~/.sshelf/keys/<name> |
Private key | 0600 |
~/.sshelf/keys/<name>.pub |
Public key | 0644 |
~/.sshelf/keys-backup-<date>.vault |
AES-256-GCM encrypted key backup | 0600 |
~/.sshelf/agent.env |
Agent PID + socket path (runtime) | 0600 |
~/.ssh/config |
sshelf block only — everything else untouched | system |
# (your own entries above — sshelf never touches these)
# BEGIN sshelf-managed — do not edit this block manually
Host github-work
HostName github.com
User git
IdentityFile ~/.sshelf/keys/work_ed25519
IdentitiesOnly yes
Host github-personal
HostName github.com
User git
IdentityFile ~/.sshelf/keys/personal_ed25519
IdentitiesOnly yes
Host staging
HostName 203.0.113.42
User ubuntu
IdentityFile ~/.sshelf/keys/staging.pem
# END sshelf-managed
# (your own entries below — also untouched)
IdentitiesOnly yes is set on every profile-linked entry to prevent SSH from accidentally offering the wrong key when you have multiple accounts on the same service (e.g. two GitHub accounts).
SSH keys age. sshelf key rotate makes rotation painless:
sshelf key rotate work_ed25519What happens:
- A fresh ed25519 key pair is generated at the same path.
- The old key is moved to
work_ed25519.bak— not deleted yet. - The SSH config block updates immediately to the new key.
- The new public key is printed for you to add to GitHub/GitLab.
Once you've added the new key to all platforms:
sshelf key remove work_ed25519.baksshelf doctor warns you when any managed key is older than 1 year.
Many companies route internal server access through a bastion host. sshelf handles this natively.
Setup:
# Add the bastion first
sshelf host add --name bastion --hostname bastion.acme.io --user alex --profile work
# Add the internal app server
sshelf host add --name prod-app --hostname 10.0.1.50 --user ubuntu --profile work
# Link prod-app through bastion
sshelf host jump prod-app --via bastionsshelf generates this in ~/.ssh/config:
Host bastion
HostName bastion.acme.io
User alex
IdentityFile ~/.sshelf/keys/work_ed25519
Host prod-app
HostName 10.0.1.50
User ubuntu
IdentityFile ~/.sshelf/keys/work_ed25519
ProxyJump bastion
Connect through the bastion in one command:
ssh prod-app
# or
sshelf host connect prod-app| Check | ID | Severity | Auto-fixable | What it checks |
|---|---|---|---|---|
| Config directory | config-dir |
warn | yes | ~/.sshelf/ exists with 0700 permissions |
| Key permissions | permissions |
fail | yes | Every private key is 0600, public keys 0644 |
| Agent running | agent |
warn | no | ssh-agent process is alive and socket reachable |
| Active profile | active-profile |
warn | no | At least one profile is marked active |
| Key age | key-age |
warn | no | No managed key older than 1 year |
| Orphaned keys | orphaned-keys |
warn | yes | No key file exists without a profile referencing it |
- Private keys are stored at
0600.sshelf doctorflags any drift and can fix it automatically. - No network calls. sshelf never transmits your keys or config to any remote service.
- Atomic writes. Every config and key write uses temp-file →
os.Rename. A crash mid-write cannot corrupt your config. IdentitiesOnly yeson every generated entry prevents SSH from offering the wrong key to hosts where you have multiple accounts.- Key generation delegates to the system
ssh-keygenbinary. sshelf does not implement cryptography for key generation. - Vault encryption uses AES-256-GCM with a key derived from 100,000 rounds of iterated SHA-256 + a random 16-byte salt. Pure stdlib — no external crypto dependencies.
- Destructive operations require confirmation. Pass
--yesonly in scripts where you control the input. - sshelf never reads private key content. It manages paths, metadata, and permissions only.
Requires Go 1.23+ and the system ssh-keygen / ssh-agent binaries.
git clone https://github.com/ajf1016/sshelf.git
cd sshelf
# Run tests with race detector
go test -race ./...
# Development build
go build -o sshelf ./cmd/sshelf
sudo mv sshelf /usr/local/bin/
# Release build — strip debug info, inject version
go build \
-trimpath \
-ldflags="-s -w -X github.com/ajf1016/sshelf/internal/config.AppVersion=v0.1.0" \
-o sshelf \
./cmd/sshelf# Linux amd64
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -o dist/sshelf-linux-amd64 ./cmd/sshelf
# macOS arm64
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 \
go build -trimpath -o dist/sshelf-darwin-arm64 ./cmd/sshelfContributions are welcome. Before opening a PR:
-
Run the test suite with the race detector:
go test -race ./... -
Run the linter:
go vet ./... golangci-lint run
-
Keep commands thin. Business logic belongs in
internal/core/, notinternal/commands/. Commands parse flags, call core services, and format output — nothing more. -
Tests for core logic are required. Packages under
internal/core/need test coverage. Command files don't. -
No real credentials in tests. Use
t.TempDir()for all paths; never commit hostnames, IPs, emails, or key material.
sshelf/
├── cmd/sshelf/ # binary entry point — main.go only
├── internal/
│ ├── commands/ # cobra command wrappers — thin, no business logic
│ ├── config/ # constants, permission bits, default paths
│ ├── core/ # all business logic: profiles, keys, hosts, agent, vault, doctor
│ ├── platform/ # OS-specific helpers (clipboard)
│ ├── ui/ # lipgloss styles, table renderer, formatting helpers
│ ├── utils/ # atomic file I/O, typed errors
│ └── wizard/ # interactive huh forms for init and import flows
MIT — see LICENSE.
Built with cobra, huh, lipgloss, and BurntSushi/toml.