Manage multiple GitHub identities for git operations.
Project description
ghswitch
A small CLI for juggling multiple GitHub identities (personal, work, client, …)
across git clone, git push, and per-repo user.name / user.email.
- Stores profile metadata in
$XDG_CONFIG_HOME/ghswitch/profiles.json(0600) - Stores Personal Access Tokens in the OS keychain via
keyring(macOS Keychain, Windows Credential Manager, GNOME Secret Service). Falls back to a 0600 JSON file ifkeyringisn't installed. - Picks the right profile from folder patterns or the SSH host alias in
the repo's
originURL. - Cross-platform: Linux, macOS, Windows (WSL or native Python).
Install
pip install ghswitch-cli # core
pip install 'ghswitch-cli[keyring]' # recommended — uses OS keychain for tokens
That installs a ghswitch console script. Verify with ghswitch --help.
If you'd rather not install: python3 ghswitch.py … works the same way.
Setup walkthrough
1. Generate distinct SSH keys (recommended)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_personal -C "you@personal"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C "you@work"
Add each public key to the respective GitHub account.
2. Define SSH host aliases
Append to ~/.ssh/config:
Host github.com-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
Host github.com-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
3. Register profiles
ghswitch add personal \
--username alice \
--email alice@personal.dev \
--ssh-key ~/.ssh/id_ed25519_personal \
--host-alias github.com-personal \
--folder '~/code/personal/*' \
--set-default
ghswitch add work \
--username alice-corp \
--email alice@workco.com \
--ssh-key ~/.ssh/id_ed25519_work \
--host-alias github.com-work \
--folder '~/code/work/*'
For HTTPS + PAT instead of SSH, add --prompt-token (hidden input) or
--token-stdin (read from stdin), and skip --ssh-key/--host-alias.
Commands
| Command | Purpose |
|---|---|
ghswitch list |
List all profiles. The default is marked with *. |
ghswitch add <name> [...] |
Add or update a profile. With no flags, prompts interactively. |
ghswitch remove <name> |
Delete a profile and its stored token. |
ghswitch use <name> |
Apply a profile to the current repo (user.name, user.email, core.sshCommand). Add --rewrite-remote to also flip the origin URL to the profile's host alias. |
ghswitch clone <url> [profile] [dir] |
Clone with the chosen profile; auto-detects from URL host alias / folder patterns / default. |
ghswitch status |
Show the current repo's identity and which profile (if any) it matches. |
ghswitch whoami |
Print the default profile name. |
ghswitch completion bash|zsh |
Emit a completion script. |
Example workflows
Cloning into a work folder, hands-off
work has --folder '~/code/work/*' configured.
cd ~/code/work
ghswitch clone git@github.com:workco/api.git
# → auto-detects "work", rewrites URL to git@github.com-work:workco/api.git,
# sets core.sshCommand to use the work key, and pins user.name/email.
Pushing from a personal repo
cd ~/code/personal/dotfiles
ghswitch use personal --rewrite-remote
git push
use --rewrite-remote flips origin to git@github.com-personal:owner/repo.git
so subsequent git pull/push ride the right SSH key without env vars.
Verifying
ghswitch status
# repo: /Users/alice/code/work/api
# remote: git@github.com-work:workco/api.git
# user.name: alice-corp
# user.email: alice@workco.com
# ssh: ssh -i /Users/alice/.ssh/id_ed25519_work -o IdentitiesOnly=yes
# profile: work <- matches stored profile
Shell completion
# bash
ghswitch completion bash >> ~/.bash_completion
# zsh
ghswitch completion zsh > ~/.local/share/zsh-completions/_ghswitch
Auto-switching rules
When a profile match is needed (e.g. during clone without an explicit
profile), ghswitch checks, in order:
- Folder patterns — longest-matching pattern wins. Patterns support
~expansion andfnmatch-style globs. - SSH host alias — if
origin(or the URL passed toclone) uses a host matching a profile'shost_alias. - Default profile — set via
add --set-defaultor implicitly the first profile added.
If none of these resolve and you didn't pass a name, clone exits with an
error.
GitHub CLI integration
gh is not required, but if it's installed ghswitch plays well with it:
SSH-based clones go through your ~/.ssh/config host aliases regardless of
which tool you use, and HTTPS clones can use the PAT stored under the matching
profile in your OS keychain.
Security notes
profiles.jsonand the fallbacksecrets.jsonare written0600.- PATs are never written into git remote URLs on disk; for HTTPS clones the
token is injected into the URL only for the
git cloneinvocation, and the remote is reset to the canonical URL afterward. core.sshCommandis set withIdentitiesOnly=yesso SSH won't fall back to another agent-loaded key.- Removing a profile also deletes its token from the keychain.
Development
pip install -e '.[dev]'
pytest
CI is in .github/workflows/:
- test.yml — runs
pyteston Linux, macOS, and Windows across Python 3.9 / 3.11 / 3.13 on every push and PR. - release.yml — on
vX.Y.Ztag pushes, builds sdist + wheel, verifies the tag matchespyproject.toml'sversion, and creates a GitHub Release with auto-generated notes. PyPI trusted publishing is wired up but commented out — uncomment thepypi-publishjob after configuring trusted publishing for the project.
To cut a release:
- Bump
versioninpyproject.tomland commit + push - Go to Actions → release → Run workflow and enter the version number
The workflow creates the tag, builds sdist + wheel, creates a GitHub Release, and publishes to PyPI automatically.
Uninstall
pip uninstall ghswitch
rm -rf ~/.config/ghswitch # Linux/macOS
# or %APPDATA%\ghswitch on Windows
Project details
Release history Release notifications | RSS feed
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 ghswitch_cli-1.0.0.tar.gz.
File metadata
- Download URL: ghswitch_cli-1.0.0.tar.gz
- Upload date:
- Size: 15.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1589cfa9f9483b74d7f8105057b61fc3a4b25b22198c0f34330aa78ed78ed9b8
|
|
| MD5 |
4049138e83cd193064e21e171cc78e2f
|
|
| BLAKE2b-256 |
d0ce1cbfe851773a1b72dec06aa13bff63507b271fd64f74d3be2cc1631a47b8
|
Provenance
The following attestation bundles were made for ghswitch_cli-1.0.0.tar.gz:
Publisher:
release.yml on saugat86/ghswitch-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ghswitch_cli-1.0.0.tar.gz -
Subject digest:
1589cfa9f9483b74d7f8105057b61fc3a4b25b22198c0f34330aa78ed78ed9b8 - Sigstore transparency entry: 1566469099
- Sigstore integration time:
-
Permalink:
saugat86/ghswitch-cli@b5b784f13cd086ef395fcebcc3c650be971a0805 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/saugat86
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b5b784f13cd086ef395fcebcc3c650be971a0805 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file ghswitch_cli-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ghswitch_cli-1.0.0-py3-none-any.whl
- Upload date:
- Size: 10.6 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 |
a6eefbbc2577e743835639f01a8712092652a1bccae74b4416521c832b552911
|
|
| MD5 |
099921357bb42fe28892eccfa5c70296
|
|
| BLAKE2b-256 |
5279da5cdbd2f1033cc244720108381d96ecc6e95718b2a669a1ba47f663f740
|
Provenance
The following attestation bundles were made for ghswitch_cli-1.0.0-py3-none-any.whl:
Publisher:
release.yml on saugat86/ghswitch-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ghswitch_cli-1.0.0-py3-none-any.whl -
Subject digest:
a6eefbbc2577e743835639f01a8712092652a1bccae74b4416521c832b552911 - Sigstore transparency entry: 1566469138
- Sigstore integration time:
-
Permalink:
saugat86/ghswitch-cli@b5b784f13cd086ef395fcebcc3c650be971a0805 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/saugat86
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b5b784f13cd086ef395fcebcc3c650be971a0805 -
Trigger Event:
workflow_dispatch
-
Statement type: