npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ssh-persona

v1.0.9

Published

Route SSH keys and gh accounts per repository on macOS — the right identity per project, chosen by git itself, with nothing global to switch. Installs the `sshid` command.

Readme

sshid

Multiple SSH identities on one machine, picked per repository by git itself.

If you have more than one SSH key — a personal account and a work one, several organisations, a fork you push under a different name — every repository needs the right key, and gh needs the right login. The usual approach is to switch something global before starting work. That breaks as soon as two repositories are open at once: whichever switch ran last wins, and a push goes out under the wrong account.

sshid removes the switch. Which key a repository uses is a function of the repository itself — where it lives and which organisation its remote belongs to. Nothing to set beforehand, nothing to remember, and nothing two terminals can race over.

$ sshid whoami
  identity       work                  (matched a rule)
  key            ~/.ssh/sshid/work/id_ed25519
  decided by     ~/.config/git/identities/work.gitconfig
  gh account     octo-work             (org 'example-org')

Install

brew install reddeer-tech/tap/sshid
npm install -g ssh-persona              # the npm package; the command it installs is `sshid`

The npm package is published as ssh-persona because npm blocks short ssh-prefixed names as possible typosquats. The command it installs is sshid, which is what you type.

Then:

sshid setup     # installs the gh shim, if you have gh
sshid adopt     # imports any routing already on this machine
sshid doctor

Installing the package on its own changes nothing — setup is the opt-in, and it is what puts the gh shim on your PATH.

SSH routing works without setup: sshid create and sshid bind write git config directly, and git picks the key up on the next command. setup is what you need for the gh half, because routing gh means putting a shim ahead of it on PATH, and no package install should do that to you silently. Run it unless you have no use for gh.

How it works

Two layers of includeIf in ~/.gitconfig, which git evaluates on every command:

| Layer | Matches on | Example | |---|---|---| | baseline | everything else | unmatched repositories use one nominated key | | 1 | the folder | ~/code/example/ uses the example key | | 2 | the organisation in any remote | [email protected]:example-org/** uses it too |

Precedence is positional and last-match-wins, so layer 2 beats layer 1. That matters more than it sounds: a third-party repository cloned inside one of your folders keeps its own key rather than inheriting the folder's, and a repository with no remote yet still gets the right key from its path. sshid maintains that ordering for you — it is the part most easily broken by hand.

gh cannot be routed this way, because it authenticates with OAuth tokens and keeps a single global "active account". So sshid installs a small shim ahead of gh on your PATH that works out the account per call and passes a token to that one invocation.

It does not bundle gh. The shim hands off to whatever gh you already have, reading the tokens already in your keyring — no re-login, and your active account never moves. If you don't have gh at all, setup says so and skips it; SSH routing works regardless.

The fallback key

A repository that matches nothing uses the baseline — one identity you nominate:

sshid baseline personal     # what unmatched repositories use
sshid baseline              # show the current one
sshid baseline --none       # clear it; git falls back to its own ssh defaults

Keys live in ~/.ssh/sshid/<name>/ by default. If you already keep them elsewhere, sshid uses that instead — point SSHID_KEYDIR at it, or let sshid adopt find them.

Everyday use

There isn't any. cd into a repository and use git.

cd ~/code/project-a && git push     # project-a's key
cd ~/code/project-b && git push     # project-b's key, at the same time, another terminal

Worth knowing:

sshid whoami        # what applies here, and why
sshid list          # every identity, what it is bound to, how many repos use it
sshid paths         # every repository, and which identity it uses
sshid doctor        # exits non-zero when something is actually wrong

Adding an identity

Create the key, put it on the account, bind it to a folder. That's the whole flow:

sshid create example                       # make the key; nothing is bound to it yet
sshid pubkey example | pbcopy              # register the public key on the account
sshid bind example --dir ~/code/example    # bind it to a folder — done

# optional, and usually unnecessary:
sshid bind example --org github.com:example-org \
                   --proof [email protected]:example-org/some-repo.git

After the third line, every repository under ~/code/example uses that key — including ones that have no remote yet. Nothing else is required.

--org takes host:org, host/org, or just org (which assumes github.com).

When you'd want the optional org binding

A folder binding says "repositories in this directory". An org binding says "repositories belonging to this organisation, wherever they happen to be on disk".

Two reasons to add one:

  • a repository of theirs lives outside the folder — cloned to a scratch directory, say — and you still want the right key
  • a third-party repository is cloned inside your folder. An org binding beats a folder binding, so that repository keeps its own key instead of silently borrowing yours

If everything for that account lives in one folder, skip it.

Why the org binding needs --proof

Binding a folder contacts nothing. It is a statement about files on your own disk, and it cannot be wrong in a way that surprises you — so nothing is checked.

Binding an organisation is a claim about a remote server: this key can reach that org's repositories. That claim is easy to get wrong and it fails misleadingly. An SSH key can authenticate perfectly — the server greets you by name — and still be denied every repository on the account, because authenticating and being authorised are different things. When that happens git says:

ERROR: Repository not found.

which reads like a typo in the URL and is almost always the wrong identity. So sshid will not bind an organisation until it has watched the key reach a real repository there. --proof takes any repository URL in that org; sshid makes one read-only request and refuses the binding if it fails. --force skips the check if you have a reason to.

Replacing or importing a key

sshid create work --from ~/keys/id_ed25519   # a key from another machine; type is detected
sshid rekey work                             # a fresh key, keeping every binding

--from takes a file, or - to read the key from stdin — which is how you paste one:

pbpaste | sshid create work --from -
pbpaste | sshid rekey  work --from -    # replace an existing identity's key

It works out the key type itself and rebuilds the public half if only the private key is supplied. A passphrase-protected key is refused, because routing runs without an agent and git would prompt on every command. Passing the key as an argument works too, but it warns: an argument is visible in ps to every process and lands in your shell history.

rekey replaces the key and keeps every binding, so nothing needs re-binding. The old key is moved aside rather than deleted — no backup this tool writes contains key material, so destroying it here would be unrecoverable. The identity stops working until you register the new public key on the account.

Moving to another machine

sshid backup --encrypt                 # on the old machine
sshid restore ~/sshid-backup-<date>.tar.gz.enc     # on the new one

Name a path if you want one; without one it writes to your home directory and tells you the full path and size. sshid export [file] does the same for the rules-only form, which stays on stdout when you give it no path so it can still be piped.

This carries the private keys, unlike export, which is rules and labels only. The bundle is written mode 0600, and without --encrypt it says plainly that anyone who reads the file can act as every account those keys reach.

restore puts the keys back, rewrites key paths for the new machine, and relocates folder bindings that lived under the old home — a folder binding is an absolute path, so left alone it would be present, look right, and match nothing. It will not overwrite an identity that already exists unless you pass --force, and --dry-run shows what it would do.

Undoing things

Every change is snapshotted first, and sshid undo restores the last one.

sshid rename <old> <new> renames an identity — the key directory, every binding, the ssh alias and the gh mapping all move with it, and repositories follow automatically.

sshid unbind <name> --all removes every binding but keeps the key; --org or --dir removes just one. sshid forget removes the identity and still keeps the key, because an unbound key costs nothing and deleting one cannot be undone — --delete-key does that, and asks you to type the name back to confirm.

sshid only rewrites the block it owns, between two markers in ~/.gitconfig. Everything else in that file is read, never written. adopt is the one exception, since it has to absorb rules written by hand: it records what every repository resolves to before and after, refuses to install a result that would drop any unrelated setting, and rolls back if a single repository would resolve differently.

Requirements

macOS, and git 2.36 or newer. Below 2.36 git ignores hasconfig rules silently and every repository falls back to the baseline key, so sshid doctor checks the version first.

MIT licensed.