Full-screen TUI launcher for SSH profiles.
Project description
ssh-starter
ssh-starter is a full-screen Textual TUI for launching SSH profiles from an OpenSSH config.
It scans ~/.ssh/config by default, groups concrete Host aliases by visible server target (HostName/alias + port), shows those groups in a three-column grid using the default profile alias as the caption, and starts the real ssh command after a profile is selected.
The three grid columns automatically resize to the current terminal width. The UI is designed for vertical scrolling only; tables fit horizontally into the visible terminal width. The status line shows compact key hints. Mouse selection is supported when the terminal forwards mouse events.
Tool installation
For normal use, install ssh-starter as a Python tool.
With uv tool:
uv tool install ssh-starter
With pipx:
pipx install ssh-starter
Then run:
ssh-starter
The package exposes the console command ssh-starter.
Upgrade commands:
uv tool upgrade ssh-starter
pipx upgrade ssh-starter
Install directly from GitHub before a PyPI release is available:
uv tool install "git+ssh://git@github.com/mykolarudenko/ssh-starter.git"
pipx install "git+ssh://git@github.com/mykolarudenko/ssh-starter.git"
Local development setup
./setup.sh
The setup script recreates .venv and installs project dependencies with uv sync.
Local development command symlink
./install.sh
The install script creates or updates this symlink:
~/.local/bin/gossh -> <project>/run-app.sh
This is the personal/local development command alias. Packaged pipx and uv tool
installs expose ssh-starter, not gossh.
It does not run setup. If dependencies are missing, run ./setup.sh first.
Make sure ~/.local/bin is in PATH.
Run
ssh-starter
Or for local development without the personal symlink:
./run-app.sh
Use another SSH config file:
ssh-starter --config /path/to/ssh_config
Print the command instead of executing SSH after selection:
ssh-starter --dry-run
Before opening the full-screen UI, ssh-starter checks that the selected OpenSSH config file exists and has at least one non-comment line.
- If the config file is missing,
ssh-starterprints the missing path and exits. - If the config file is empty or contains only comments,
ssh-starterasks whether to create a sample profile.
Sample profile:
Host sample-server
HostName 192.0.2.10
User deploy
Port 22
After creating the sample, edit it for a real server and run ssh-starter again.
The default OpenSSH user config path is ~/.ssh/config.
On Windows, Python expands ~ to the current user profile, so this resolves to a path like:
C:\Users\<user>\.ssh\config
Keys
Enter— connect to the selected server with its default profile.Shift+Enter— open the user/profile picker for the selected server.Ctrl+Enter— also accepted when the terminal passes it through. Some terminals send this asCtrl+JorCtrl+M;ssh-starterhandles those too.F1— open help.F2— open options.F3— show details for the selected server's default profile.F10— exit.Esc Esc— exit from the main menu when no filter is active.- After an SSH session ends,
SpaceorEscreturns to the main menu. - Alphanumeric keys — quick search.
-,_,.,@— also accepted in quick search because SSH aliases often contain them.Backspace— remove one search character.Esc— clear the current search filter.
The status line shows total discovered servers, visible filtered servers, profile count, the current filter, and compact key hints including exit shortcuts.
Options
Press F2 from the main menu to edit options.
Current option:
- SSH
TERMstring for launched sessions.
Default:
term_string = "xterm-256color"
The chosen value is written to config.toml and is passed to the foreground SSH process as the TERM environment variable.
Grouping and default profile
Profiles are grouped when they point to the same visible target and port:
HostName/alias + Port
The default profile for a group is selected in this order:
- users listed in
preferred_users; - the first sorted profile in the group.
Enter connects with that default profile. Ctrl+Enter opens a menu where you can select another user/profile for the same server. Clicking a server in the main grid connects with the default profile; clicking a profile in the picker selects that profile.
Sorting and history
Server groups and picker profiles are sorted by last connection time, newest first. When timestamps are equal or missing, entries are sorted alphabetically by alias.
Connection history is stored in:
~/.local/state/ssh-starter/history.toml
If XDG_STATE_HOME is set, ssh-starter uses:
$XDG_STATE_HOME/ssh-starter/history.toml
On Windows, connection history is stored in:
%LOCALAPPDATA%\ssh-starter\history.toml
Any displayed connection time is formatted in local time.
Profile info
F3 shows:
- source
Hostblock location; HostNameor alias target;- user and port;
IdentityFilevalues from config;ProxyJump/ProxyCommandif present;- direct/proxied route type;
- network classification: local private network, Tailscale, direct public, or direct named/unclassified;
- other concrete aliases targeting the same
HostNameand port.
ssh-starter does not read private key contents. It only displays IdentityFile paths from the SSH config.
SSH config parsing
Supported:
- concrete
Hostaliases; - top-level and inline
Includeglobs; - first-value-wins options for
User,HostName,Port,ProxyJump, andProxyCommand; - multiple
IdentityFilevalues.
Skipped or warned:
- wildcard
Hostpatterns are not menu entries; Matchblocks are not evaluated;.envfiles referenced byIncludeare skipped.
Profiles are imported from the OpenSSH config. Other than the optional startup sample for an empty config, ssh-starter does not create or edit SSH profiles. To add, rename, or change a profile, edit the SSH config file directly, usually ~/.ssh/config.
Configuration
For installed pipx/uv tool usage, ssh-starter uses a user-level config.toml.
Default locations:
Linux/macOS: $XDG_CONFIG_HOME/ssh-starter/config.toml or ~/.config/ssh-starter/config.toml
Windows: %APPDATA%\ssh-starter\config.toml
If this user-level config is missing and --app-config was not passed, ssh-starter creates it with default values and prints the path.
If --app-config is passed explicitly, the file must already exist.
The config file contains:
ssh_config_path = "~/.ssh/config"
term_string = "xterm-256color"
preferred_users = ["root"]
You can override the OpenSSH config path per run with --config.
Local development runners (./run-app.sh and run-app.bat) explicitly use the repository config.toml.
Runtime behavior
After Enter, the TUI exits and runs OpenSSH directly:
- default config:
ssh <alias> - non-default config:
ssh -F <config> <alias>
The app does not synthesize SSH options from parsed data, so OpenSSH remains the source of truth for connection behavior.
When the foreground SSH process exits for any reason, ssh-starter opens a full-screen completion message. Press Space or Esc on that screen to return to the profile menu.
No .env usage
The app does not require .env files and does not read them. If an SSH Include pattern points to a .env file, it is skipped and shown as a parser warning.
Release process
PyPI publishing is handled by GitHub Actions when a v* tag is pushed.
The PyPI project must be configured for trusted publishing from:
Project name: ssh-starter
Repository owner: mykolarudenko
Repository name: ssh-starter
Workflow filename: publish.yml
Environment name: pypi
PyPI setup:
- Open PyPI account settings.
- Go to Publishing.
- Add a pending publisher for a new project.
- Select GitHub Actions.
- Fill the fields exactly as shown above.
- In GitHub, create environment
pypiunder repository Settings → Environments. Add required reviewers there if you want manual approval before publishing.
Create a release with a version bump:
uv run --no-sync python scripts/release.py --patch
Other supported bumps:
uv run --no-sync python scripts/release.py --minor
uv run --no-sync python scripts/release.py --major
uv run --no-sync python scripts/release.py --version 1.2.3
The release script:
- verifies a clean git worktree;
- increments
pyproject.tomlandapp/__init__.py; - runs
uv build; - creates commit
Release vX.Y.Z; - creates annotated tag
vX.Y.Z; - pushes the branch and tag to GitHub.
The pushed tag starts the PyPI publish workflow.
Regenerate the README screenshot mock:
uv run --no-sync python scripts/render_main_window_mock.py
The screenshot generator reads only the shape of ~/.ssh/config, writes a synthetic demo config to
docs/demo/ssh_config, starts the real Textual app with that fake config, and captures
docs/assets/main-window.png. Aliases, hostnames, users, IP addresses, and key paths in the generated assets are synthetic.
License
MIT License. Copyright (c) Mykola Rudenko.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ssh_starter-0.1.1.tar.gz.
File metadata
- Download URL: ssh_starter-0.1.1.tar.gz
- Upload date:
- Size: 204.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c554d9754224fa24cdc54a4467e13e3a3d4ace809040acf078962e6cb438133
|
|
| MD5 |
103f01b7f968d9ed7d2b6e671d21a334
|
|
| BLAKE2b-256 |
4914050ee5e6b83f702704355c7bec8972c9eea269c465704032aa55e0b4d953
|
Provenance
The following attestation bundles were made for ssh_starter-0.1.1.tar.gz:
Publisher:
publish.yml on mykolarudenko/ssh-starter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_starter-0.1.1.tar.gz -
Subject digest:
5c554d9754224fa24cdc54a4467e13e3a3d4ace809040acf078962e6cb438133 - Sigstore transparency entry: 1706621389
- Sigstore integration time:
-
Permalink:
mykolarudenko/ssh-starter@2af3fb693266a832f392123dcfe99efef6785063 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mykolarudenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2af3fb693266a832f392123dcfe99efef6785063 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ssh_starter-0.1.1-py3-none-any.whl.
File metadata
- Download URL: ssh_starter-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
681805d1fe8bcf3e41a754b56ba2cb87b4466bdf380f5211d6e21bfdd5d7bd05
|
|
| MD5 |
346de10d90debf4d841b84ec0600e4aa
|
|
| BLAKE2b-256 |
d699ecea14dd8def18ddcbcde34d70939d9fcfa7ee1277251abe0ca7c7e1de27
|
Provenance
The following attestation bundles were made for ssh_starter-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on mykolarudenko/ssh-starter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_starter-0.1.1-py3-none-any.whl -
Subject digest:
681805d1fe8bcf3e41a754b56ba2cb87b4466bdf380f5211d6e21bfdd5d7bd05 - Sigstore transparency entry: 1706622242
- Sigstore integration time:
-
Permalink:
mykolarudenko/ssh-starter@2af3fb693266a832f392123dcfe99efef6785063 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mykolarudenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2af3fb693266a832f392123dcfe99efef6785063 -
Trigger Event:
push
-
Statement type: