Switch shell environments with one word. No binaries, no hooks to approve,
no .env files rotting in your repos.
$ staging ✓ Loaded staging (5 vars) $ production ! Load production env? (y/n): y ✓ Loaded production (12 vars) $ unsetenv ✓ Cleared production environment
Env files live in ~/.envs/, one per environment, outside every
repository. Each one automatically becomes a shell command — drop in
production.env and production is now a command.
No registry to update, no config to edit, nothing to register. The filename
is the configuration.
Here's the actual problem it was built for. In a monorepo, the environment
you need depends on where you are in the tree and what branch
you're on. Directory alone doesn't cut it —
growth/feature-x and payments/new-api live in
the same checkout and want completely different credentials.
So env files carry tags in their header, and the shell works it out on
cd and on branch switch:
# ~/.envs/growth_staging.env # dir: ~/monorepo # branch: growth/* API_KEY=sk-staging-123
$ cd ~/monorepo # on branch growth/feature-x ✓ Auto-loaded growth_staging (dir:~/monorepo, branch:growth/feature-x) $ production # context-aware: resolves to growth_production › Context: growth ✓ Loaded growth_production (5 vars) $ git switch payments/new-api ✓ Auto-loaded payments_staging (branch:payments/new-api) $ cd ~/Desktop ✓ Left mapped directory, clearing payments_staging
That last line is the one that matters. Leaving the directory unloads the environment instead of leaving it lying around — so a terminal you opened three days ago and forgot about isn't still holding credentials for a repo you've since wandered out of.
Tagged environments auto-load. Production never gets tagged.
You reach it on purpose — by name, or through context resolution — and you
can mark it
protected
so loading it needs a typed confirmation.
That's the entire safety model, and it's deliberately not clever. Anything
that can happen implicitly — on a cd, on a branch switch,
without you asking for it — only ever reaches environments where being
wrong is cheap. Anything expensive needs you to actually do something.
Every alternative design fails the same way. If production can auto-load,
then some sequence of git switch and
cd silently arms a shell with production credentials — and
you will discover which sequence at the worst imaginable moment, probably
while running something with the word "delete" in it.
Auto-loading means hooking directory and branch changes, which means this code runs constantly. So resolution is tiered, and the common cases cost nothing:
Zero overhead. Nothing to match, nothing to check.
Resolved without invoking git at all.
Git gets read once (~1ms), then cached.
Shelling out to git rev-parse on every prompt is the classic way
a tool like this becomes the reason your terminal feels sluggish and you
can't work out why. Reading git only when the answer genuinely depends on
the branch keeps it off the hot path for everyone not using that feature.
| envswitch | direnv | dotenv | aws-vault | |
|---|---|---|---|---|
| Scope | Whole shell | Per directory | Per app (runtime) | AWS creds only |
| staging → prod | production |
Edit .envrc + reload |
Restart app | aws-vault exec prod |
| Monorepo + branches | Auto-load by dir + branch pattern | One file per dir, no branch awareness | N/A | N/A |
| Files in repo | No — everything in ~/.envs |
Yes — .envrc in each project |
Yes — .env in project root |
No |
| Install | Source one file | Binary + shell hook + allow per dir | Language-specific package | Binary + keychain setup |
| Prod protection | Built-in confirmation | None | None | Session-based |
| Config to add one | None — add a file, get a command | Edit + direnv allow |
Edit app config | Edit ~/.aws/config |
direnv is the closest thing and it's genuinely good — but it's directory-scoped and repo-resident by design. It answers "what does this folder need", and it drops a file in the folder to say so. In a monorepo those are precisely the two properties you don't want: one folder, many answers, and nothing secret going anywhere near a commit.