▄██████████▄
▄█ ◉ ◉ █▄ gitme
███ ▀▀ ███─────● multi-account github cli
█████████████████ ●
▀█████████████▀ ●
▀███▀ ▀███
Multi-account GitHub CLI — manage SSH keys, git identities, and GitHub auth per repo so you never commit or push as the wrong person.
Meet Gigi the gitmeleon — your identity-switching mascot. Just like a chameleon adapts to its environment, gitme adapts your git identity to each repo automatically.
If you have both a personal and work GitHub account, every git operation is a landmine. You might commit with the wrong email, push with the wrong SSH key, or clone into a directory configured for the wrong account.
gitme fixes this permanently. It configures git and SSH at the repo level — once. After that, every tool just works: VS Code, JetBrains, GitHub Desktop, terminal, Claude Code, CI scripts. No wrappers, no runtime dependency, no environment variables.
gitme is a setup tool, not a runtime wrapper. It configures two things that every git tool already respects:
- Local
.git/config— setsuser.nameanduser.emailper repo, so commits always use the right identity - SSH host aliases — rewrites remotes to use per-account SSH aliases (e.g.,
git@github.com-work:org/repo.git), so push/pull always use the right key
Set up once, then forget about it.
npm install -g @timbenniks/gitmeRequires Node.js 18+. Only depends on git and ssh-keygen (universally available).
Run gitme with no arguments. Gigi greets you and scans your machine for existing git config, SSH keys, and gh CLI auth.
$ gitme
▄██████████▄
▄█ ◉ ◉ █▄ Hey! I'm Gigi the gitmeleon.
███ ▀▀ ███─────● I help you juggle GitHub identities.
█████████████████ ● Set up once, never think about it again.
▀█████████████▀ ●
▀███▀ ▀███
┌ █◉█─● gitme ──────────────────────────────────────
● Scanning your existing git setup...
◆ Found existing configuration:
Git user: Gigi Meleon <gigi@example.com>
SSH key: ~/.ssh/id_ed25519
gh CLI auth: gigimeleon
◇ Import this as your first profile?
│ > Yes, import as 'personal'
│ Yes, but let me customize it
│ No, start fresh
After importing your personal profile, gitme asks if you want to add another account. Say yes and enter your work details.
◇ Set up another GitHub account?
│ Yes
◇ Profile name:
│ work
◇ GitHub username:
│ gigi-at-acme
◇ Full name for commits:
│ Gigi Meleon
◇ Email for commits:
│ gigi@acme.com
◇ SSH key for this profile:
│ > Generate a new key
● Generating SSH key...
◆ Created: ~/.ssh/gitme_work
◆ ❐ Public key (copied to clipboard):
ssh-ed25519 AAAAC3Nz... gigi@acme.com
→ Add this key to GitHub: https://github.com/settings/ssh/new
◇ Open browser? Yes
◇ Have you added the key to GitHub? Yes
● Testing ssh -T git@github.com-work...
◆ Authenticated as gigi-at-acme
Tell gitme which GitHub orgs belong to which profile. This enables automatic profile detection when cloning.
◇ Map GitHub orgs to profiles?
│ Yes
◇ Map an org to 'personal':
│ gigimeleon
◇ Map an org to 'work':
│ acme-corp
gitme scans common directories to find and register your existing repos.
● Scanning for git repos...
◆ Found 7 git repos
◆ Discovered repos:
~/work/api-service acme-corp/api-service → work
~/work/frontend acme-corp/frontend → work
~/projects/my-site gigimeleon/my-site → personal
~/projects/dotfiles gigimeleon/dotfiles → personal
◇ Register these repos? Yes
◆ Registered 4 repos
└ Gigi says: you're all set! Happy committing.
From now on, just use gitme clone instead of git clone. It auto-detects the profile.
$ gitme clone git@github.com:acme-corp/new-service.git
◆ Detected org: acme-corp → profile 'work'
● Cloning acme-corp/new-service...
◆ Cloned as 'work' (gigi@acme.com)
◆ Registered in repo registry
Inside any managed repo, check who you are:
$ cd ~/work/api-service
$ gitme whoami
◆ Repository: api-service
● Profile: work
✉ Email: gigi@acme.com
⚷ SSH key: ~/.ssh/gitme_work (✓ valid)
⤷ Remote: git@github.com-work:acme-corp/api-service
Now every tool — VS Code, terminal, Claude Code, GitHub Desktop — uses the correct identity automatically. No wrappers. No thinking. It just works.
A profile is a named GitHub identity: username, name, email, SSH key, and optional API token.
$ gitme profiles
PROFILE USERNAME EMAIL REPOS DEFAULT
personal gigimeleon gigi@example.com 5 ✓
work gigi-at-acme gigi@acme.com 8
Full CRUD:
- Add:
gitme setup→ "Add a new profile" - Edit:
gitme setup→ "Edit an existing profile" (change any field, regenerate SSH key, update token) - Remove:
gitme setup→ "Remove a profile" (cleans up SSH config block and org mappings) - List:
gitme profiles
gitme clone auto-detects the right profile from the org, rewrites the remote, sets local git config, and registers the repo — all in one step.
$ gitme clone git@github.com:acme-corp/api-service.git
◆ Detected org: acme-corp → profile 'work'
● Cloning acme-corp/api-service...
◆ Cloned as 'work' (gigi@acme.com)
◆ Registered in repo registry
If the org isn't mapped, gitme asks which profile to use and offers to remember the mapping.
A central registry at ~/.gitme/repos.json — one source of truth for all managed repos. No dotfiles inside repos, no gitignore entries, no pollution.
$ gitme repos
PROFILE REPO PATH CLONED
work acme-corp/api-service ~/work/api-service 2 days ago
work acme-corp/frontend ~/work/frontend 1 day ago
personal gigimeleon/my-site ~/personal/my-site 2 weeks ago
3 repos across 2 profiles
Full CRUD:
- Add:
gitme clone <url>orgitme init(bind existing repo) - Remove:
gitme repos remove(interactive picker, orgitme repos remove /path/to/repo) - List:
gitme repos(with--profile <name>filter,--jsonfor scripting) - Health check:
gitme repos --check(verify repos exist on disk) - Clean:
gitme repos --clean(remove stale entries)
Map GitHub organizations to profiles so gitme clone auto-detects which account to use.
$ gitme config org list
ORG PROFILE
acme-corp work
gigimeleon personal
Full CRUD:
- Add:
gitme config org add <org> <profile> - Remove:
gitme config org remove <org> - List:
gitme config org list
$ gitme whoami
◆ Repository: api-service
● Profile: work
✉ Email: gigi@acme.com
⚷ SSH key: ~/.ssh/gitme_work (✓ valid)
⤷ Remote: git@github.com-work:acme-corp/api-service
◈ GitHub API: ✓ token configured
Manage PRs and issues with the correct GitHub account — no gh CLI required.
gitme pr list # list open PRs
gitme pr create # create a PR (interactive)
gitme pr view 42 # view PR details
gitme pr status # PRs you authored + review requests
gitme issue list # list open issues
gitme issue create # create an issue
gitme issue view 17 # view issue detailsAuthenticated via personal access tokens stored per profile. SSH-based operations (clone, push, pull) always work without a token.
Running bare gitme adapts to where you are:
| Context | Behavior |
|---|---|
| First run (no config) | Gigi greets you + interactive setup wizard |
| Inside a registered repo | Dashboard with identity + branch info |
| Inside an unregistered repo | Offers to adopt it with a profile |
| Outside any repo | Hub menu with all actions |
Enter any git repo that isn't managed by gitme yet, and run gitme or gitme init:
$ cd ~/random/some-project
$ gitme
▲ This repo isn't managed by gitme yet.
⊙ Detected remote: git@github.com:some-org/some-project.git
◇ Set it up now? Yes
◇ Which profile?
│ > work (gigi@acme.com)
◆ Rewriting origin to: git@github.com-work:some-org/some-project.git
◆ Set git user to: Gigi Meleon <gigi@acme.com>
◆ Registered in repo registry.
| Command | Description |
|---|---|
gitme |
Context-aware default (setup / dashboard / adopt / menu) |
gitme setup |
Interactive setup wizard (add, edit, remove profiles) |
gitme clone <url> [dir] |
Clone with the right identity |
gitme init [profile] |
Bind an existing repo to a profile |
gitme whoami |
Show current repo identity |
gitme status |
Git status with identity info |
| Command | Description |
|---|---|
gitme profiles |
List all profiles with repo counts |
gitme repos |
List all registered repos |
gitme repos --profile <name> |
Filter repos by profile |
gitme repos --check |
Verify all repos exist on disk |
gitme repos --clean |
Remove stale entries |
gitme repos --json |
Machine-readable JSON output |
| Command | Description |
|---|---|
gitme repos remove [path] |
Unregister a repo (interactive picker if no path) |
| Command | Description |
|---|---|
gitme config default <profile> |
Set the default profile |
gitme config org add <org> <profile> |
Map a GitHub org to a profile |
gitme config org remove <org> |
Remove an org mapping |
gitme config org list |
List all org mappings |
gitme config list |
Show all settings |
| Command | Description |
|---|---|
gitme pr list |
List open pull requests |
gitme pr create |
Create a pull request (interactive) |
gitme pr view [number] |
View pull request details |
gitme pr status |
Show PRs you authored + review requests |
gitme issue list |
List open issues |
gitme issue create |
Create an issue (interactive) |
gitme issue view [number] |
View issue details |
Each profile gets a unique SSH alias in ~/.ssh/config, managed by gitme with marker comments:
# gitme-begin:work
Host github.com-work
HostName github.com
User git
IdentityFile ~/.ssh/gitme_work
IdentitiesOnly yes
# gitme-end:work
When a repo's remote is git@github.com-work:org/repo.git, SSH automatically uses the right key. No agent switching, no environment variables. Every tool that calls git push inherits this — VS Code, JetBrains, Claude Code, GitHub Desktop, CI scripts.
When gitme needs to determine the active profile for the current directory:
- Registry lookup — resolve the repo root, look up the path in
~/.gitme/repos.json - Org mapping — read the remote URL, extract the org, match against configured mappings
- Default profile — fall back to the default profile
- Prompt — if running interactively, ask the user to pick
All config lives in ~/.gitme/ (permissions: 0700 directory, 0600 files):
~/.gitme/
├── config.json # profiles, default profile, org-to-profile mappings
└── repos.json # central repo registry (path → profile + metadata)
Nothing is written inside your repos. Portable, backupable, scriptable.
config.json structure:
{
"version": 1,
"defaultProfile": "personal",
"profiles": {
"personal": {
"githubUsername": "gigimeleon",
"gitName": "Gigi Meleon",
"gitEmail": "gigi@example.com",
"sshKeyPath": "~/.ssh/gitme_personal",
"sshHost": "github.com-personal",
"githubToken": null
}
},
"orgMappings": {
"acme-corp": "work",
"gigimeleon": "personal"
}
}- TypeScript with strict mode
- Vite+ unified toolchain (tsdown build, Vitest tests, oxlint linting, oxfmt formatting)
- @clack/prompts for beautiful interactive CLI output
- picocolors for terminal colors
- commander for CLI argument parsing
- Zero runtime dependencies beyond
gitandssh-keygen
git clone https://github.com/timbenniks/gitme.git
cd gitme
npm install
npm run dev # run from source (tsx)
npm run build # build to dist/ (vp pack)
npm test # run tests (vp test)
npm run check # format + lint + typecheck (vp check)MIT