{"_id":"@allons-y/envoy","_rev":"2-dde96466122b409cd9cb40a29c25124e","name":"@allons-y/envoy","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@allons-y/envoy","version":"1.0.0","keywords":["cli","env","environment"],"author":{"url":"https://allons-y.llc","name":"Cassondra Roberts","email":"castastrophe@users.noreply.github.com"},"license":"Apache-2.0","_id":"@allons-y/envoy@1.0.0","maintainers":[{"name":"castastrophe","email":"castastrophe+npm@hey.com"}],"homepage":"https://github.com/castastrophe/envoy#readme","bugs":{"url":"https://github.com/castastrophe/envoy/issues"},"bin":{"envoy":"cli.js","envoy-mcp":"mcp.js"},"dist":{"shasum":"1f8e96efe27775da535eebb21d3319dba2fe6b22","tarball":"https://registry.npmjs.org/@allons-y/envoy/-/envoy-1.0.0.tgz","fileCount":7,"integrity":"sha512-N4fwnmO/vbIBm569q9ATblpjOL4Yzp7lnL0DdMJhex0MNRVq4ai6nDQcq52zmMmuzPa9aDny+aSw7XYpUbfDTw==","signatures":[{"sig":"MEUCIQDno2jA5V5ckP87xTNYfDFj6AYsUgzL/feunogW6t84dAIgRumQBPMAVOIM5vaO1aQVUsxVHA47yndqvfACzO8tuks=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29910},"main":"index.js","type":"module","engines":{"node":">=24.0.0"},"funding":[{"url":"https://github.com/sponsors/castastrophe","type":"github"},{"url":"https://www.buymeacoffee.com/castastrophe","type":"buy-me-a-coffee"}],"gitHead":"b2a8259d628d3842e25a27fb471e0c183084fa95","scripts":{"test":"ava","prepack":"pinst --disable","release":"semantic-release","coverage":"c8 yarn test","postpack":"pinst --enable","postinstall":"husky && envoy"},"_npmUser":{"name":"castastrophe","email":"castastrophe+npm@hey.com"},"repository":{"url":"git+https://github.com/castastrophe/envoy.git","type":"git"},"_npmVersion":"11.6.2","description":"Copies values from a root .env into a project .env using .env.example as a template","directories":{},"_nodeVersion":"24.13.0","dependencies":{"zod":"^4.3.6","chalk":"^5.6.2","yargs":"^18.0.0","@modelcontextprotocol/sdk":"^1.28.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"packageManager":"yarn@4.13.0","devDependencies":{"c8":"^11.0.0","ava":"^7.0.0","husky":"^9.1.7","pinst":"^3.0.0","eslint":"^10.1.0","prettier":"^3.8.1","lint-staged":"^16.3.3","@commitlint/cli":"^19.0.0","semantic-release":"^24.0.0","@semantic-release/git":"^10.0.0","@semantic-release/npm":"^12.0.2","prettier-package-json":"^2.8.0","@semantic-release/github":"^11.0.0","@semantic-release/changelog":"^6.0.0","@commitlint/config-conventional":"^19.0.0","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/release-notes-generator":"^14.1.0"},"_npmOperationalInternal":{"tmp":"tmp/envoy_1.0.0_1774568852315_0.05193036075032742","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@allons-y/envoy","version":"1.1.0","description":"Copies values from a root .env into a project .env using .env.example as a template","license":"Apache-2.0","author":{"name":"Cassondra Roberts","email":"castastrophe@users.noreply.github.com","url":"https://allons-y.llc"},"repository":{"type":"git","url":"git+https://github.com/castastrophe/envoy.git"},"bugs":{"url":"https://github.com/castastrophe/envoy/issues"},"type":"module","main":"index.js","bin":{"envoy":"cli.js","envoy-mcp":"mcp.js"},"scripts":{"coverage":"c8 yarn test","prepack":"pinst --disable","postinstall":"husky && envoy","postpack":"pinst --enable","release":"semantic-release","test":"ava"},"dependencies":{"@modelcontextprotocol/sdk":"^1.28.0","chalk":"^5.6.2","yargs":"^18.0.0","zod":"^4.3.6"},"devDependencies":{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@semantic-release/changelog":"^6.0.0","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/git":"^10.0.0","@semantic-release/github":"^12.0.0","@semantic-release/npm":"^13.0.0","@semantic-release/release-notes-generator":"^14.1.0","ava":"^7.0.0","c8":"^11.0.0","eslint":"^10.1.0","husky":"^9.1.7","lint-staged":"^16.3.3","pinst":"^3.0.0","prettier":"^3.8.1","prettier-package-json":"^2.8.0","semantic-release":"^25.0.0"},"keywords":["cli","env","environment"],"engines":{"node":">=24.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"funding":[{"type":"github","url":"https://github.com/sponsors/castastrophe"},{"type":"buy-me-a-coffee","url":"https://www.buymeacoffee.com/castastrophe"}],"packageManager":"yarn@4.13.0","gitHead":"fcf98c4b24bed32e6af38131021016c5adef4ac3","_id":"@allons-y/envoy@1.1.0","homepage":"https://github.com/castastrophe/envoy#readme","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-dDv5PK+HTc5ZvjOdHqOtt1dBh+XbbsW0sQC6VwSir15IbVfWHpo+4Y8dUbVEy963a60YvF7lRT4woxxk1YCynA==","shasum":"c0555825d9263c01940c3f9f8232957b1ebb893e","tarball":"https://registry.npmjs.org/@allons-y/envoy/-/envoy-1.1.0.tgz","fileCount":7,"unpackedSize":35079,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCZlu4LFPJrmGEKEp5XpE/Bmv5DCptWNoC6P2J4xAyX3QIhAK0La4IZzM6FM6mmmHwsSzYLo/kiYgL+IAaEnC6t42mt"}]},"_npmUser":{"name":"castastrophe","email":"castastrophe+npm@hey.com"},"directories":{},"maintainers":[{"name":"castastrophe","email":"castastrophe+npm@hey.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/envoy_1.1.0_1774568866163_0.38998181474247784"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-26T23:47:32.173Z","modified":"2026-03-26T23:47:46.478Z","1.0.0":"2026-03-26T23:47:32.461Z","1.1.0":"2026-03-26T23:47:46.327Z"},"bugs":{"url":"https://github.com/castastrophe/envoy/issues"},"author":{"name":"Cassondra Roberts","email":"castastrophe@users.noreply.github.com","url":"https://allons-y.llc"},"license":"Apache-2.0","homepage":"https://github.com/castastrophe/envoy#readme","keywords":["cli","env","environment"],"repository":{"type":"git","url":"git+https://github.com/castastrophe/envoy.git"},"description":"Copies values from a root .env into a project .env using .env.example as a template","maintainers":[{"name":"castastrophe","email":"castastrophe+npm@hey.com"}],"readme":"<div align=\"center\">\n  <img width=\"250\" src=\"https://github.com/castastrophe/envoy/blob/main/logo-envoy.png?raw=true\">\n</div>\n<h1 align=\"center\">Envoy</h1>\n<p align=\"center\">\n  <b>Environment setup, handled.</b>\n</p>\n\n<div align=\"center\">\n\n[![Tests][github-image]][github-url]\n[![NPM version][npm-image]][npm-url]\n[![Coverage][coverage-image]][coverage-url]\n[![Conventional Commits][conventional-commits-image]][conventional-commits-url]\n\n</div>\n\nCopy the values you've already stored in `~/.env` into every project's `.env` — automatically, accurately, and without touching secrets you haven't defined.\n\n```sh\nyarn dlx @allons-y/envoy\n# ✨ Created /your/project/.env\n```\n\n## The problem\n\nEvery 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.\n\nIf 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.\n\n## Features\n\n- **Template-driven** — `.env.example` defines the shape; `~/.env` supplies the values\n- **Safe by default** — skips any `.env` that already exists; use `--force` to overwrite\n- **Security-first** — will not copy secrets if `.env` is currently tracked by git; warns the user if it isn't included in `.gitignore`\n- **Monorepo-aware** — recursively finds every `.env.example` under your project root, skipping `node_modules`\n- **Non-destructive** — keys absent from `~/.env` fall back to the example value, so nothing is lost\n- **Comment-preserving** — blank lines and `# comments` in `.env.example` are written as-is\n- **Preview before you commit** — `--dry-run` shows exactly what would be written without touching the filesystem\n- **MCP tool** — expose `copy_env` to any MCP-compatible host (Claude Desktop, Claude Code, etc.)\n- **No magic** — the source is small, readable, and fully tested\n\n## Installation\n\n### Prerequisites\n\nEnvoy 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:\n\n```sh\n# ~/.env\nDATABASE_URL=postgres://localhost:5432/mydb\nSTRIPE_SECRET_KEY=sk_test_...\nOPENAI_API_KEY=sk-...\n```\n\nAny 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.\n\n### Try it without installing\n\nRun envoy once in any project directory without adding it as a dependency:\n\n```sh\nyarn dlx @allons-y/envoy\nnpx @allons-y/envoy\npnpm dlx @allons-y/envoy\nbunx @allons-y/envoy\n```\n\n### Add to a project\n\nInstall as a dev dependency to use envoy in scripts, hooks, or CI:\n\n```sh\nyarn add --dev @allons-y/envoy   # Yarn Berry\nnpm install --save-dev @allons-y/envoy\npnpm add --save-dev @allons-y/envoy\nbun add --dev @allons-y/envoy\n```\n\n## Usage\n\n### CLI\n\nRun in any project root. Envoy will find every `.env.example` recursively and create the corresponding `.env`.\n\n```sh\nenvoy\n```\n\n| Flag            | Alias | Description                                    |\n| --------------- | ----- | ---------------------------------------------- |\n| `--force`       | `-f`  | Overwrite existing `.env` files                |\n| `--dry-run`     | `-n`  | Preview changes without writing any files      |\n| `--root <path>` | `-r`  | Use a custom root env file (default: `~/.env`) |\n| `--dir <path>`  | `-d`  | Directory to scan (default: current directory) |\n| `--skip-audit`  | `-s`  | Skip the git safety checks                     |\n| `--help`        | `-h`  | Show help                                      |\n\n**Examples:**\n\n```sh\n# Preview what would be created, without writing anything\nenvoy --dry-run\n\n# Regenerate .env files from scratch\nenvoy --force\n\n# Use a team-shared env file instead of ~/.env\nenvoy --root ./secrets/.env.shared\n\n# Scan a specific directory\nenvoy --dir packages/api\n```\n\n### Security checks\n\nEvery time envoy writes a `.env` file it runs two git safety checks automatically:\n\n**1. Git tracking check** — if `.env` is already committed to the repository, envoy refuses to overwrite it and exits with a non-zero code:\n\n```\n🚨 Blocked /your/project/.env — this file is tracked by git. Remove it from\n   version control before proceeding:\n   git rm --cached /your/project/.env\n```\n\nWriting 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`.\n\n**2. Gitignore check** — if `.env` is not covered by any `.gitignore` rule, envoy writes the file but prints a warning:\n\n```\n⚠️  /your/project/.env is not covered by .gitignore — add it to prevent\n    accidentally committing secrets\n```\n\nBoth 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.\n\nUse `--skip-audit` to bypass both checks — for example, in a non-git environment where git isn't available:\n\n```sh\nenvoy --skip-audit\n```\n\n### Postinstall hook\n\nThe highest-value place to run envoy is in 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.\n\n```json\n{\n\t\"scripts\": {\n\t\t\"postinstall\": \"envoy\"\n\t}\n}\n```\n\nBecause envoy skips any `.env` that already exists, running it repeatedly is completely safe. Contributors who already have a `.env` won't have their values touched.\n\n**npm vs Yarn**\n\nUse `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.\n\n**Library authors**\n\nIf 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`](https://github.com/typicode/pinst) to strip the hook from your published tarball:\n\n```sh\nyarn add --dev pinst\n```\n\n```json\n{\n\t\"scripts\": {\n\t\t\"postinstall\": \"envoy\",\n\t\t\"prepack\": \"pinst --disable\",\n\t\t\"postpack\": \"pinst --enable\"\n\t}\n}\n```\n\n`pinst --disable` removes `postinstall` from `package.json` before packing, so the published tarball consumers receive contains no hook. `pinst --enable` restores it locally afterward.\n\n### MCP tool\n\nEnvoy ships an MCP server so AI tools can call `copy_env` directly. Add it to your host's config:\n\n**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):\n\n```json\n{\n\t\"mcpServers\": {\n\t\t\"envoy\": {\n\t\t\t\"command\": \"npx\",\n\t\t\t\"args\": [\"-y\", \"@allons-y/envoy/mcp\"]\n\t\t}\n\t}\n}\n```\n\n**Claude Code** (`.claude/settings.json`):\n\n```json\n{\n\t\"mcpServers\": {\n\t\t\"envoy\": {\n\t\t\t\"command\": \"npx\",\n\t\t\t\"args\": [\"-y\", \"@allons-y/envoy/mcp\"]\n\t\t}\n\t}\n}\n```\n\nThe `copy_env` tool accepts `dir`, `force`, `dry_run`, `root_env_path`, and `skip_audit` — the same options as the CLI.\n\n## How it works\n\n1. Scans `dir` recursively for `.env.example` files (ignoring `node_modules`)\n2. For each one, checks whether a `.env` already exists alongside it (skips unless `--force`)\n3. Runs git safety checks — blocks if `.env` is tracked; warns if it isn't gitignored\n4. Reads `~/.env` (or `--root`) into a key → value map\n5. Walks every line in `.env.example`:\n    - **Comments and blank lines** are written through unchanged\n    - **`KEY=VALUE` lines** where `KEY` exists in `~/.env` get the root value substituted\n    - **`KEY=VALUE` lines** where `KEY` is absent fall back to the example value\n6. Writes the result to `.env` next to the example file\n\nNo network calls. No config files. No global state.\n\n## Requirements\n\n- Node.js >= 24.0.0\n\n## License\n\n[Apache 2.0](./LICENSE) © [Cassondra Roberts](https://allons-y.llc)\n\n[github-image]: https://github.com/castastrophe/envoy/actions/workflows/test.yml/badge.svg?branch=main\n[github-url]: https://github.com/castastrophe/envoy/actions/workflows/test.yml\n[npm-image]: https://img.shields.io/npm/v/@allons-y/envoy.svg\n[npm-url]: https://www.npmjs.com/package/@allons-y/envoy\n[conventional-commits-image]: https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg\n[conventional-commits-url]: https://conventionalcommits.org/\n[coverage-image]: https://img.shields.io/nycrc/castastrophe/envoy\n[coverage-url]: https://github.com/castastrophe/envoy/blob/main/.nycrc\n","readmeFilename":"README.md"}