Small CLI helper around Git that focuses on:
- SSH key/config management (add, remove, rotate, select).
- A tiny wrapper to clone repositories using a chosen SSH HostAlias.
- A local vault for storing a master password encrypted via GPG.
The main command is gt, which dispatches to small helpers like ssh.sh, vault.sh and doctor.sh.
Install via Homebrew using the project's tap and package name:
brew tap ElaraDevSolutions/tools
brew install gt
After installation the gt executable will be on your PATH. Locally (from source) you can run the scripts under src/ directly (for example bash src/gt.sh ssh help).
If you don't (or can't) use Homebrew (e.g. on minimal Linux images), a self-contained installer script is provided. It copies the scripts under src/ to a prefix and creates a lightweight wrapper gt in <prefix>/bin.
Quick install (defaults to /usr/local if writable, otherwise ~/.local):
curl -fsSL https://raw.githubusercontent.com/ElaraDevSolutions/gittool/v1.0.4/install.sh | bash
You can override the prefix:
curl -fsSL https://raw.githubusercontent.com/ElaraDevSolutions/gittool/v1.0.4/install.sh | bash -s -- --prefix=$HOME/.local
Options supported by install.sh:
| Option | Description |
|---|---|
--prefix=DIR |
Installation root (DIR/bin/gt + DIR/lib/gittool). |
--force |
Overwrite existing files. |
--uninstall |
Remove previously installed wrapper and library directory. |
--dry-run |
Show actions without performing them. |
-h, --help |
Show help extracted from the script header. |
After install, ensure <prefix>/bin is on your PATH (add export PATH="<prefix>/bin:$PATH" to your shell profile if needed). Upgrading is just re-running the install command (possibly with --force). Uninstall with:
bash install.sh --uninstall --prefix=/usr/local # or the prefix you used
Security note: Prefer pinning to a version tag (vX.Y.Z) instead of a mutable branch name. Optionally download first and inspect:
curl -fsSLO https://raw.githubusercontent.com/ElaraDevSolutions/gittool/vX.Y.Z/install.sh
less install.sh # inspect
bash install.sh
Core commands:
| Command | Description |
|---|---|
gt ssh add |
Add/register SSH keys and Host blocks in ~/.ssh/config. |
gt ssh remove <HostAlias> |
Remove key files and the Host block for the alias. |
gt ssh rotate [flags] <HostAlias> |
Rotate keys while keeping the same HostAlias. |
gt ssh list |
List Host aliases from ~/.ssh/config. |
gt ssh copy [HostAlias] |
Copy public key to clipboard (default: current repo origin). |
gt ssh show <HostAlias> |
Show details about a configured alias and key. |
gt ssh select |
Switch the current repo's origin to a chosen HostAlias. |
gt ssh unlock <HostAlias> |
Unlock the SSH key for a HostAlias using the vault master. |
gt clone <SSH-link> |
Clone using a selected HostAlias. |
gt vault init |
Initialize a local vault and master password (GPG-encrypted). |
gt vault -m / gt vault show-master |
Decrypt and print the master password. |
gt doctor |
Run diagnostics on your Git/SSH/gittool setup. |
The dispatcher forwards gt ssh ... and gt vault ... to their helpers. For normal Git commands gt simply forwards to git.
The rest of this README is split by topic:
- SSH helper:
gt ssh ...(most of the tool today). - Vault helper:
gt vault ...(local master password storage). - Clone wrapper and doctor.
All SSH helper commands are exposed via gt ssh <cmd> (or by running src/ssh.sh directly).
- Add a key (interactive)
This is the normal path for creating and registering a new SSH key:
gt ssh add
The script will prompt for:
- HostName (defaults to github.com)
- Key name (a short alias used as HostAlias in ~/.ssh/config, e.g.
personal) - Email (used as the key comment during generation)
After successful generation the key is stored at ~/.ssh/id_ed25519_<alias> and a Host block is appended to ~/.ssh/config.
1.0) Using the vault master as SSH key passphrase
If you have already initialized the local vault (gt vault init), the interactive gt ssh add flow will also ask:
"Use vault master as SSH key passphrase? [y/N]:"
Behavior when you answer y:
gt ssh addcallsgt vault -munder the hood to decrypt the stored master.- The SSH key is generated with that value as its passphrase (
ssh-keygen ... -N <master>), so there is no extra prompt asking for the key password. - The new HostAlias is recorded in
~/.config/gittool/configunder the[vault]section as part of thessh_hostslist; e.g.:
[vault]
provider=local
path=/Users/you/.gittool/vault/vault-XXXX.gpg
ssh_hosts=work-ssh,personal-ssh,new-aliasBehavior when you answer N or just press Enter:
- The key is generated without a passphrase (
-N "") as before. - The vault configuration is left untouched.
The vault is never created implicitly by gt ssh add: you must call gt vault init explicitly once. After that, any alias created with the vault option becomes part of the ssh_hosts mapping so you can see which SSH identities are tied to that master.
1.1) Add a key (non-interactive / CI)
You can create or register keys without prompts by supplying flags:
gt ssh add --alias personal --email user@example.com --hostname github.com
If the key does not exist it will be (or would be, in dry-run) generated at ~/.ssh/id_ed25519_personal.
Register an existing key by path:
gt ssh add --path ~/.ssh/id_ed25519_work --alias work --hostname github.com
Register by pattern (searches ~/.ssh, auto-selects if a single match):
gt ssh add --pattern work
Flags supported by add:
| Flag | Purpose |
|---|---|
--alias <name> |
Set the HostAlias (for new key or when registering existing). |
--email <email> |
Key comment (required for non-interactive generation). |
--hostname <h> |
HostName for the block (default github.com). |
--path <file> |
Path to an existing private key. |
--pattern <frag> |
Fragment to locate unconfigured keys. |
--no-agent |
Do not run ssh-add. |
--no-sign |
Do not update allowed_signers / signing configs. |
--dry-run |
Show planned actions only. |
Common mistakes:
- Using
--aliaswithout--emailwhen generating a new key (email is required). --patternmatching multiple files withoutfzfinstalled → aborts and asks to use--path.
Dry-run example:
gt ssh add --alias tempkey --email temp@example.com --dry-run
- Register an existing key by path
If you already have a private key file you can point the helper at it (path to the private key):
gt ssh add /path/to/id_ed25519_alias
This registers the provided key in ~/.ssh/config (it may still prompt for a HostName). The helper attempts to add the key to ssh-agent (warnings from ssh-add are non-fatal).
- Register an existing key by pattern (new)
You can now pass just a pattern (alias fragment). The tool searches ~/.ssh for private key files whose names contain that fragment and are not yet configured as a Host.
Examples (assume files like id_ed25519_personal, personal-ssh, my-personal exist):
gt ssh add personal
Behavior:
- If exactly one unconfigured match is found, it's registered automatically.
- If multiple matches are found, you'll get an interactive selector:
- Uses
fzfif installed (arrow keys / fuzzy search). - Falls back to a numbered Bash
selectmenu otherwise.
- Uses
- After choosing (or auto-detecting), a Host block is added and the key is loaded into
ssh-agent.
Notes:
- Only private key files (non-
.pub) are considered. - The HostAlias used is derived from the filename (strips
id_ed25519_prefix and any.pubsuffix). - Already-configured aliases are skipped so you don't create duplicates.
- List configured Host aliases
gt ssh list
This prints a short list of Host aliases found in ~/.ssh/config.
4.1) Show details for a configured HostAlias (new)
Use this to inspect the SSH and Git context for a specific HostAlias:
gt ssh show personal
# or
gt ssh -s personalIt prints:
- Basic SSH config for that alias (HostName, User, IdentityFile, whether the key is loaded in ssh-agent).
- Key metadata (type, fingerprint, creation time, age in days, email from the public key comment).
- Git/signing context (current repo and origin, whether origin uses this HostAlias, local/global user.email, user.signingkey match, and allowed_signers status).
- Remove a key and its Host block
gt ssh remove personal
This removes the Host block for personal from ~/.ssh/config and deletes ~/.ssh/id_ed25519_personal (and the .pub file) if present. The helper will also ask ssh-agent to unload the key (ssh-add -d) but will continue if ssh-agent is not available.
- Help
gt ssh help
Shows the available SSH helper commands.
- Select a key for the current repository (new)
Use this when you already have multiple HostAliases configured and want to switch which SSH identity Git uses for pushes/clones in the current repo:
gt ssh select
What it does:
- Ensures you are inside a Git work tree and that
originexists. - Lists all configured
Hostaliases from~/.ssh/config. - Lets you choose one (via
fzfif installed, or a numbered menu). - Rewrites the
originremote fromgit@original-host:owner/repo.gittogit@<chosen-alias>:owner/repo.git.
Example:
Initial remote:
git@github.com:ElaraDevSolutions/gittool.git
Run:
gt ssh select → choose work-ssh
New remote:
git@work-ssh:ElaraDevSolutions/gittool.git
Return codes / edge cases:
- Exits with error if not inside a Git repo, if
originis missing, or if the currentoriginURL is not SSH format (git@host:path). - If only one alias exists, it's auto-selected.
- No changes are made if selection is aborted (ESC/Ctrl-C in
fzfor empty choice).
7.1) Unlock a key via the vault master (new)
When a HostAlias was created with the vault-enabled gt ssh add flow, it is registered under [vault].ssh_hosts in ~/.config/gittool/config. You can later unlock that key in the current session without typing its passphrase:
gt ssh unlock personalBehavior:
- Finds the
IdentityFilefor the given HostAlias in~/.ssh/config. - Checks that the alias is listed in
[vault].ssh_hosts. - Calls
gt vault -mto obtain the master and uses it to runssh-addfor that key. - If the key is already loaded in
ssh-agent, it exits without doing anything.
This is useful before running commands like git clone or git fetch that would otherwise prompt for the key passphrase. Once unlocked, the key remains available to the agent for the rest of the session according to your ssh-agent lifetime.
- Copy public key to clipboard
Use gt ssh copy (or gt ssh -c) to copy the public key content to your clipboard, making it easy to paste into GitHub/GitLab settings.
# Copy key for the current repository's origin
gt ssh copy
# Copy key for a specific alias
gt ssh copy personal- Rotate an existing key (new)
Use this to periodically replace a key while keeping the same HostAlias (remotes like git@alias:org/repo.git keep working):
gt ssh rotate personal
What happens:
- Backs up current private & public key to
id_ed25519_<alias>.old-<timestamp>. - Prompts for email (defaults to previous key comment or
git config user.email). - Optional passphrase prompt.
- Generates fresh Ed25519 key at original path.
- Adds to ssh-agent (unless
--no-agent). - Updates commit signing & allowed_signers (unless
--no-sign). - Removes old key content from
allowed_signers(unless--no-sign).
Flags:
--dry-runShow planned actions only (no backups, no key generation).--no-agentSkipssh-add.--no-signSkip signing setup & allowed_signers adjustments.--email <address>Provide new key email non-interactively (CI/automation).
Examples:
# Preview without touching files
gt ssh rotate --dry-run personal
# Rotate skipping side-effects
gt ssh rotate --no-agent --no-sign personal
# Provide a new email non-interactively (then passphrase default answer)
echo -e "new.email@example.com\n\n" | gt ssh rotate personal
Exit codes: 0 success / dry-run; 1 on errors (missing alias, invalid flag, generation failure).
Why rotate? Reduced exposure window, algorithm migration, enforce periodic hygiene, refresh signing metadata.
When adding or selecting a key, the helper attempts to configure SSH commit signing:
- Creates (if necessary)
~/.config/git/allowed_signersand adds a lineemail public_key_contentif the key is not yet present. - Sets
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers. - Ensures
git config --global user.signingkeypoints to the private key file (without.pub). - Verification command:
gt ssh sign-status.
If the email cannot be detected (via git config user.email or the public key comment), you will be prompted in interactive mode.
To enable commit signing globally:
git config --global commit.gpgsign true
git commit -S -m "feat: example"Quick check:
gt ssh sign-statusThe vault module manages a single master password stored locally, encrypted with GPG.
- Uses a config directory under
~/.gittool/vault(or$GITTOOL_CONFIG_DIR/vaultif set). - On first
gt vault init:- Ensures a GPG key exists:
- If you already have keys, it picks the first public key as the default.
- If no keys exist, it auto-generates a non-interactive RSA key just for gittool using a params file
gpg-key-params.txtstored in the vault directory.
- Decides the master password:
- If running in a TTY, you are prompted:
Enter master password (leave empty to auto-generate):and then asked to confirm; empty input generates a random strong password. - If you pass
-p/--password <value>, that value is used directly (no prompt). - In non-interactive/CI contexts with no TTY and no
-p, a random password is generated.
- If running in a TTY, you are prompted:
- Encrypts the master with the GPG key into a file
vault-<random-id>.gpginside the vault directory.
- Ensures a GPG key exists:
- Initialize (interactive or with flags):
gt vault init # prompt for master; empty = auto-generate
gt vault init -p "MySecret" # use provided master without prompt
gt vault init KEYID123 # advanced: override which GPG key to use- Show the stored master password:
gt vault -m
gt vault show-masterThis simply runs gpg --decrypt on the vault-*.gpg file and prints the master. If the underlying GPG private key has no passphrase (the auto-generated one uses %no-protection), this is fully non-interactive. If you later switch to a protected key, any passphrase prompt will come from gpg itself.
gt vault initcan be safely called in pipelines via:
gt vault init -p "$SOME_SECRET" # explicit master- The tests in
test/test_vault.shrun entirely non-interactively by piping empty lines intovault.shand asserting that a master is generated and that vault initialization is idempotent.
gt includes a tiny wrapper around git clone that replaces the host portion of an SSH repo link with a selected HostAlias from your SSH config. Example:
gt clone git@github.com:owner/repo.git
If multiple HostAlias entries exist the script will prompt you to choose one (it integrates with fzf if available). The final git clone will use the selected HostAlias so the specified key is used for authentication.
Example flow (multiple keys configured):
- You run:
gt clone git@github.com:acme/service.git - Script lists
personalandwork(or opensfzfif installed) - You choose
work - It runs
git clone git@work:acme/service.git
- The
ssh.shhelper is primarily interactive for theaddflow. In CI or automation you can pre-generate keys and append Host blocks to~/.ssh/configdirectly (the test suite uses this approach). The helper supports passing an existing key path togt ssh addbut it may still prompt for HostName. - The helper tolerates
ssh-agentnot being present;ssh-addwarnings are non-fatal. For CI ensure your runner has the right key permissions (600) and that~/.sshexists. - Set environment variable
GITTOOL_NON_INTERACTIVE=1to suppress email & passphrase prompts duringgt ssh rotate(it auto reuses previous email and skips passphrase query). This is useful for unattended rotations. Foradd, supplying the needed flags (--alias,--email, etc.) already avoids prompts.
For normal Git commands, gt simply forwards to git. However, it provides several shortcuts for common operations:
| Shortcut | Expands to |
|---|---|
gt -c |
git checkout |
gt -b |
git branch |
gt -s |
git status |
gt -d |
git diff |
gt -a |
git add |
gt -p |
git pull |
gt -pp / gt -P |
git push |
gt -f |
git fetch |
gt -m |
git merge |
gt -cm <msg> |
git commit -m <msg> |
gt -cam <msg> |
git commit -am <msg> |
Example usage:
gt -c -b new-feature # git checkout -b new-feature
gt -s # git status
gt -cm "Initial commit" # git commit -m "Initial commit"Use gt doctor for a quick health check of your environment:
gt doctor
gt doctor --ssh-only
gt doctor --git-only
gt doctor --alias personalWhat it checks:
- Environment: presence of
git,ssh,ssh-agent,fzf. - SSH config:
~/.ssh,~/.ssh/config, and configuredHostaliases. - SSH signing & Git config:
allowed_signers,gpg.ssh.allowedSignersFile,user.signingkey,user.email. - Current repository (when inside a Git work tree): repo root,
originURL, SSH vs HTTPS,user.email,commit.gpgsign.
With --alias <HostAlias>, it appends the detailed view from gt ssh show <HostAlias> to the report so you can inspect one alias in depth.
- If
gt ssh listreports no config found, ensure~/.ssh/configexists and is readable by your user. - If
ssh-addfails, check that an ssh-agent is running or use the system keychain (macOS) or other helpers. - The
gtdispatcher looks for the helper scripts relative to thegtbinary (this works both when run from source and when installed via Homebrew).
Patches, bug reports and improvements are welcome. Tests are small bash scripts under test/ that run in an isolated temporary HOME (test_ssh.sh, test_vault.sh, test_install.sh).
Current distribution: Homebrew tap + raw repo.
Potential additional channels (pick those that bring real user value; simplest first):
- GitHub Releases (recommended early step)
- For each version tag (
vX.Y.Z), create a Release and attach a tarball or just rely on the auto-generated source tarball. Provide SHA256 sums. Users can:curl -L -o gittool.tgz ...and extract, or consume theinstall.shpinned to the tag.
- For each version tag (
- Linuxbrew (already supported implicitly)
- The existing Homebrew formula works on Linux if users install Homebrew (Linuxbrew). Just document it.
- Simple curl|bash installer (done)
- Already covered above. Optionally add a signature (GPG) and detached
.ascfile in Releases.
- Already covered above. Optionally add a signature (GPG) and detached
- Debian / Ubuntu (
.debpackage)- Create a minimal deb: place scripts into
/usr/lib/gittoolor/usr/share/gittooland a symlink/usr/bin/gt. Tools:dpkg-debmanually or usefpm(fpm -s dir -t deb ...). - To publish broadly, either host your own APT repo (serve
Release,Packages, etc.) or use a PPA via Launchpad (requires packaging metadata). For a simple project, publishing a.debasset in Releases might be enough for advanced users.
- Create a minimal deb: place scripts into
- RPM (Fedora / RHEL / openSUSE)
- Similar: use
fpm -s dir -t rpm ...or create an RPM spec and build withrpmbuild. Host via OBS (openSUSE Build Service) for multiple distros.
- Similar: use
- Snap (optional)
- Create a
snapcraft.yamlwith acommand: gt; confinement:classicmay be needed for seamless access to SSH keys. Users thensnap install gittool --classic. Overhead may not justify for small bash scripts.
- Create a
- Nix / flakes
- Provide a
default.nixorflake.nixthat installs scripts and wrapper. Users:nix profile install github:ElaraDevSolutions/gittool(if flake). Very low maintenance once added.
- Provide a
- asdf plugin
- Create
asdf-community/asdf-gittool(or your namespace) with abin/installthat fetches a tagged tarball and placesgton PATH. Popular for multi-language/dev-tool environments.
- Create
- Homebrew Core (later)
- Once stable and with sufficient popularity, you could propose inclusion in homebrew-core for broader visibility (must meet their acceptance guidelines).
What NOT to prioritize early: Flatpak (not ideal for simple CLI), AppImage (benefits mostly binaries, not bash scripts).
When you push a tag vX.Y.Z:
- GitHub Actions workflow
release.ymlruns. - It builds archives (
tar.gz,zip),.deb,.rpm, andSHA256SUMS. - Checksums are verified and the relevant changelog section is extracted.
- Assets are attached to the GitHub Release created (or updated) for the tag.
Manual scripts (under scripts/):
| Script | Purpose |
|---|---|
release_checksums.sh |
Create tar.gz, zip, and SHA256SUMS. |
build_fpm_packages.sh |
Generate .deb and .rpm using fpm. |
sign_release.sh |
GPG sign artifacts (optional). |
Assuming sibling repo layout:
gittool/
homebrew-tools/
The script replaces the version in the formula url and updates sha256 to match the new source archive.
If you want to configure signing manually:
mkdir -p ~/.config/git
echo "your.email@example.com $(cat ~/.ssh/your-key.pub)" >> ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
git config --global user.signingkey /Users/youruser/.ssh/your-key
git config --global commit.gpgsign true- Optional GPG auto-sign integration in CI (provide
GPG_PRIVATE_KEY+GPG_PASSPHRASEsecrets). - Publish asdf plugin for easy multi-tool management.
- Provide a Nix flake for reproducible installations.
Open an issue if you want help bootstrapping any further distribution channel.
See LICENCE.md for license details.