Environment setup, handled.
Envoy keeps your secrets where they belong: in every project that needs them, and out of every commit that doesn't. Set it once in postinstall and forget it — every fresh clone gets a complete .env automatically, while a read-only --check gate stops real secrets from sneaking into version control.
# Onboard — pull your real values into this project's .env
yarn dlx @allons-y/envoy
# ✨ Created /your/project/.env
# Guard — fail the commit if a real secret slipped into a tracked file
envoy --check
# ⚠ Possible secret detected in .env.example:
# STRIPE_SECRET_KEY contains a value matching your local ~/.envEvery project starts the same way: copy .env.example → .env, then hunt through Notion, 1Password, Slack history, or your own memory for the actual values. In a monorepo it's worse — five packages, five .env.example files, the same ritual repeated for each one.
If you keep a root ~/.env with your real values (and you should), envoy bridges the gap. It reads your .env.example as a template, pulls matching keys from ~/.env, and writes a complete .env alongside it — preserving every comment and blank line in the process. Then it stays on as a guardrail, making sure those real values never end up somewhere they can be committed.
Envoy isn't a one-time script whose existence you forget — it's a standing part of every project's lifecycle. Wire it in once and it covers the whole loop:
| Stage | How envoy runs | What you get |
|---|---|---|
| Onboard | postinstall: "envoy" |
Every clone gets a complete, populated .env — zero manual setup |
| Develop | envoy --dry-run |
Preview exactly what would be written before anything touches disk |
| Commit | envoy --check |
A read-only gate that blocks real secrets from reaching the repository |
Set up the postinstall hook once and onboarding is genuinely set it and forget it: new contributors run their package manager and get a working .env — no doc to follow, no values to track down, no "works on my machine."
- Template-driven —
.env.exampledefines the shape;~/.envsupplies the values - Monorepo-aware — recursively finds every
.env.exampleunder your project root, skippingnode_modules - Comment-preserving — blank lines and
# commentsin.env.exampleare written through untouched - Incremental — keys absent from
~/.envfall back to the example value, so you can adopt it gradually - Two safety gates — refuses to write into a git-tracked
.env, and--checkstops real secrets from being committed - Preview mode —
--dry-runshows exactly what would be written without touching the filesystem - MCP tool — expose
copy_envandcheck_envto any MCP-compatible host (Claude Desktop, Claude Code, etc.)
Standardizing a tool across every repo is a big ask, so envoy is built to earn that trust:
- Local-first — reads
~/.envon your machine; no network calls, no accounts, no SaaS, no telemetry - Zero config — nothing to author or maintain; sensible defaults out of the box
- Idempotent — skips any
.envthat already exists, so running it on every install is completely safe - Non-destructive — never overwrites without
--force, and missing keys fall back to the example value - Read-only where it counts —
--dry-runand--checknever modify a single byte on disk - No lock-in — it's just
.envfiles; stop using envoy any time and nothing breaks - Small and tested — the source is short, readable, and fully covered by tests
Envoy reads from a root ~/.env file on your machine. If you don't have one yet, create it and add any values you want shared across projects:
# ~/.env
DATABASE_URL=postgres://localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_...
OPENAI_API_KEY=sk-...Any key that isn't in ~/.env will fall back to the value in your .env.example, so you can add keys incrementally — you don't need to migrate everything up front.
Run envoy once in any project directory without adding it as a dependency:
yarn dlx @allons-y/envoy
npx @allons-y/envoy
pnpm dlx @allons-y/envoy
bunx @allons-y/envoyInstall as a dev dependency to use envoy in scripts, hooks, or CI:
yarn add --dev @allons-y/envoy # Yarn Berry
npm install --save-dev @allons-y/envoy
pnpm add --save-dev @allons-y/envoy
bun add --dev @allons-y/envoyRun in any project root. Envoy will find every .env.example recursively and create the corresponding .env.
envoy| Flag | Alias | Description |
|---|---|---|
--force |
-f |
Overwrite existing .env files |
--dry-run |
-n |
Preview changes without writing any files |
--root <path> |
-r |
Use a custom root env file (default: ~/.env) |
--dir <path> |
-d |
Directory to scan (default: current directory) |
--skip-audit |
-s |
Skip the git safety checks |
--check |
-c |
Validate committed files for leaked secrets (read-only) |
--help |
-h |
Show help |
Examples:
# Preview what would be created, without writing anything
envoy --dry-run
# Regenerate .env files from scratch
envoy --force
# Use a team-shared env file instead of ~/.env
envoy --root ./secrets/.env.shared
# Scan a specific directory
envoy --dir packages/api
# Validate that no secrets have leaked into committed files
envoy --checkThe highest-value place to run envoy is your project's postinstall script. Every contributor who clones the repo and runs their package manager gets a fully populated .env automatically — no onboarding doc to follow, no values to track down.
{
"scripts": {
"postinstall": "envoy"
}
}This is the whole "set it and forget it" promise: because envoy skips any .env that already exists, running it on every install is completely safe and idempotent. Contributors who already have a .env never have their values touched; everyone else gets a working one for free. You configure it once and never think about onboarding again.
npm vs Yarn
Use postinstall for this hook regardless of which package manager your project uses. While npm supports a prepare lifecycle script that only runs during local development, Yarn Berry does not support prepare — postinstall is the correct choice for both.
Library authors
If your package is published to npm, a bare postinstall will run for every consumer who installs your package as a dependency — which is not what you want. Use pinst to strip the hook from your published tarball:
yarn add --dev pinst{
"scripts": {
"postinstall": "envoy",
"prepack": "pinst --disable",
"postpack": "pinst --enable"
}
}pinst --disable removes postinstall from package.json before packing, so the published tarball consumers receive contains no hook. pinst --enable restores it locally afterward.
Envoy treats your secrets as a one-way street: they flow in from ~/.env to the projects that need them, and envoy actively stops them flowing out into version control. Two complementary gates enforce this — one when envoy writes, one when you commit:
- At write time, envoy refuses to populate a
.envthat git is already tracking, so it can never push secrets into a file headed for the repository. - At commit time,
envoy --checkfails the commit if a real secret has leaked into a staged or tracked file.
Together they close the loop: there's no point at which a real value envoy manages can slip into a commit unnoticed.
--check turns envoy into a read-only safety gate. It never writes or modifies any file — it only looks for real secrets that have made their way into files you're about to commit:
envoy --checkWhen run, envoy:
- Determines which files to scan — files staged for commit if any are staged, otherwise all tracked files (so it works both as a git hook and as an ad-hoc audit)
- Cross-references their contents against the values in your root
~/.env - Reports any file containing a verbatim copy of a known secret and exits non-zero
- Exits zero, silently when nothing is wrong — CI- and pre-commit-friendly
⚠ Possible secret detected in .env.example:
STRIPE_SECRET_KEY contains a value matching your local ~/.env
Because it only scans staged or tracked files, anything covered by .gitignore (like your real .env) is never flagged. To keep false positives low, only exact, full value matches count, and obvious placeholders — short values, <your-key>, changeme, sk_test_…, *_here, and similar — are ignored.
Without a ~/.env, there are no known values to compare against, so envoy falls back to a lightweight heuristic: it warns when a secret-shaped key (*_KEY, *_SECRET, *_TOKEN, *_PASSWORD) holds a non-placeholder value.
The zero-exit-on-clean contract means it stays invisible until the day it saves you. Wire it into your commit step via your package.json:
{
"scripts": {
"precommit": "envoy --check"
}
}…or a git hook manager like husky or lefthook:
# lefthook.yml
pre-commit:
commands:
check-env:
run: envoy --checkEvery time envoy writes a .env file it runs two git safety checks automatically:
1. Git tracking check — if .env is already committed to the repository, envoy refuses to overwrite it and exits with a non-zero code:
🚨 Blocked /your/project/.env — this file is tracked by git. Remove it from
version control before proceeding:
git rm --cached /your/project/.env
Writing secrets into a tracked file would put them one git push away from exposure. Envoy will not do this under any circumstances without --skip-audit.
2. Gitignore check — if .env is not covered by any .gitignore rule, envoy writes the file but prints a warning:
⚠️ /your/project/.env is not covered by .gitignore — add it to prevent
accidentally committing secrets
Both checks use git's own plumbing (git check-ignore and git ls-files) so nested .gitignore files, global ignores, and .git/info/exclude are all respected. In directories that aren't git repositories the checks are skipped silently.
Use --skip-audit to bypass both checks — for example, in a non-git environment where git isn't available:
envoy --skip-auditEnvoy ships an MCP server so AI tools can call copy_env and check_env directly. Add it to your host's config:
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"envoy": {
"command": "npx",
"args": ["-y", "@allons-y/envoy/mcp"]
}
}
}Claude Code (.claude/settings.json):
{
"mcpServers": {
"envoy": {
"command": "npx",
"args": ["-y", "@allons-y/envoy/mcp"]
}
}
}The copy_env tool accepts dir, force, dry_run, root_env_path, and skip_audit — the same options as the CLI. A read-only check_env tool is also exposed (accepting dir and root_env_path), mirroring envoy --check for AI-driven pre-commit validation.
Copying values (envoy)
- Scans
dirrecursively for.env.examplefiles (ignoringnode_modules) - For each one, checks whether a
.envalready exists alongside it (skips unless--force) - Runs git safety checks — blocks if
.envis tracked; warns if it isn't gitignored - Reads
~/.env(or--root) into a key → value map - Walks every line in
.env.example:- Comments and blank lines are written through unchanged
KEY=VALUElines whereKEYexists in~/.envget the root value substitutedKEY=VALUElines whereKEYis absent fall back to the example value
- Writes the result to
.envnext to the example file
Validating commits (envoy --check)
- Asks git which files are staged — or, if none are, every tracked file
- Reads
~/.env(or--root) into the set of real values to protect - Flags any scanned file containing one of those values verbatim — or, with no
~/.env, any secret-shaped key holding a non-placeholder value - Exits non-zero on a finding, or zero and silent when clean — all without writing anything
No network calls. No config files. No global state.
Envoy is intentionally minimal today — it does one thing and does it well. The bigger vision is to make it the single tool that bridges your personal secrets and every project you work in, without friction or foot-guns. That means smarter value handling, better control over what gets scanned and where, and tighter integration with the safety checks developers already rely on.
Follow along or share your ideas in the roadmap discussion.
- Node.js >= 24.0.0
