env-wizard walks you through .env.example one variable at a time β inline hints
as you go, and your AI (cloud or local) on standby for anything unclear. Then it
writes .env for you, safely.
Config in TOML/YAML/JSON? Same walkthrough, real file written, every comment kept.
No example file? It scans your code (JS/TS, Python, Rust, Go, Ruby, PHP) instead.
Hand it to a coding agent, or pick the method for your OS β then run
env-wizard inside any repo that has a .env.example.
Working with Claude Code, Codex, or another coding agent? Just hand it this prompt:
Install env-wizard from https://github.com/alphonse-terrier/env-wizard
Manual install instructions (macOS / Linux / Windows / Cargo)
Homebrew (recommended):
brew tap alphonse-terrier/env-wizard
brew install env-wizardOr download a prebuilt binary
# Apple Silicon (M1/M2/M3β¦). For an Intel Mac, swap aarch64 β x86_64.
curl -L https://github.com/alphonse-terrier/env-wizard/releases/download/v0.6.0/env-wizard-v0.6.0-aarch64-apple-darwin.tar.gz | tar xz
sudo mv env-wizard-v0.6.0-aarch64-apple-darwin/env-wizard /usr/local/bin/Homebrew (if you use it):
brew tap alphonse-terrier/env-wizard
brew install env-wizardPrebuilt static binary (x86_64, no dependencies):
curl -L https://github.com/alphonse-terrier/env-wizard/releases/download/v0.6.0/env-wizard-v0.6.0-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo mv env-wizard-v0.6.0-x86_64-unknown-linux-musl/env-wizard /usr/local/bin/Install with Cargo (needs the Rust toolchain):
cargo install --git https://github.com/alphonse-terrier/env-wizardWorks everywhere Rust runs:
cargo install --git https://github.com/alphonse-terrier/env-wizardBuild from a local clone
git clone https://github.com/alphonse-terrier/env-wizard
cd env-wizard
cargo install --path .Verify a download against
SHA256SUMSon the release page.
cd my-freshly-cloned-project
env-wizardThat's it. π
The first time you press ? for a hint, env-wizard asks which AI to use β pick
one from a short list (Claude, OpenAI, local Ollama, LM Studio, OpenRouter, Groq,
or your own) and it's remembered from then on. No config file to write by hand,
no API to wire up. See Choose your AI.
Jump to: Demo Β· Features Β· Scan your code Β· Config templates Β· Choose your AI Β· Usage Β· How it works Β· FAQ
Here's what a real run looks like:
The
π‘ Hintis your AI's answer, rendered cleanly in the terminal β headings, bullets, and commands, with the raw Markdown stripped away. Need something specific? Type your question after the?(e.g.? what format is expected?) and the AI answers it directly.
Prefer plain text? Here's the same run as a transcript
$ env-wizard
env-wizard
At each prompt, type:
β Enter β accept the suggested default
β ? β ask the AI for a hint
β ? β¦ β ask the AI a specific question about this variable
β (nothing)β leave this variable empty
β q β quit without saving
Change the AI provider anytime with `env-wizard config`.
# Secret used to sign session cookies (32+ chars)
? SECRET_KEY βΊ ?
π‘ Hint
SECRET_KEY
This signs your session cookies. Generate one with:
openssl rand -hex 32
β’ Must be at least 32 characters
β’ Keep it secret β put it in .env, never commit it
? SECRET_KEY βΊ 9f2c8aβ¦ β you paste the real value
β SECRET_KEY Β· 9f2c8aβ¦
β Wrote .env- π§ Guided, not guesswork β every variable's
.env.examplecomment shows inline as a hint, before you ever call the AI. - π€ Your AI, your rules β hints come from whatever provider you pick: Claude, OpenAI, a local Ollama model, or any OpenAI-compatible endpoint. Nothing is hardcoded, and env-wizard stores no API keys.
- π Private by design β your
.envvalues are never sent to any AI, cloud or local. And with a local provider (Ollama, LM Studio athttp://localhost:11434) the whole prompt stays on your machine β nothing leaves at all. - π§ Repo-aware hints β the AI is fed your README, common config files, and every place the variable appears in the code, so its advice is specific β not generic.
- π Catches drift β
env-wizard scanaudits your.env.exampleagainst what's actually used in the code (8 languages), and can even work with no example at all. - π Not just
.envβ TOML, YAML, and JSON config templates get the same guided walkthrough, and only the values you change are ever touched. - πΎ Safe, tidy writes β confirms before overwriting an existing
.env, keeps a.env.bak, carries your.env.examplecomments over each variable, and writes the file0600(owner-only) on Unix. - πͺΆ One small binary β written in Rust. No runtime, no daemon, starts instantly.
.env.example files drift: someone adds process.env.STRIPE_SECRET_KEY to the code and
forgets the example. env-wizard reads the source itself β JS/TS, Python, Rust, Go,
Ruby, PHP β to catch that before it costs you twenty minutes of debugging.
Show details
$ env-wizard scan
Used in code but missing from .env.example (2):
β’ REDIS_URL src/cache.py:1
β’ STRIPE_SECRET_KEY src/server.js:2
Declared in .env.example but not found in code (1):
β’ OLD_FEATURE_FLAGThree ways to use it:
| Command | What it does |
|---|---|
env-wizard scan |
Audit, read-only: what's used in code but missing from the example (with file:line), and what's declared but unused. |
env-wizard scan --check |
Same audit, but exits with status 1 if any drift is found β drop it in CI to catch a .env.example that's fallen out of sync. |
env-wizard (no .env.example present) |
Fallback: derives the variable list straight from the code and runs the wizard anyway. |
env-wizard --from-code |
Augment: prompts for the example's variables plus any extras found in the code. |
Before falling back to code detection, env-wizard also tries common example filename
aliases when --input is omitted: .env.sample, .env.dist, .env.template, and
env.example (no leading dot).
Detected patterns:
| Language | Detected |
|---|---|
| JS / TS | process.env.FOO, process.env["FOO"], import.meta.env.FOO |
NestJS (@nestjs/config) |
configService.get("FOO"), .get<Type>("FOO"), .getOrThrow("FOO") |
| Zod env schemas (zod / t3-env / znv) | FOO: z.string() β any SCREAMING_SNAKE_CASE key mapped to a zod validator |
| Python | os.environ["FOO"], os.environ.get("FOO"), os.getenv("FOO") |
| Rust | env::var("FOO"), env!("FOO"), option_env!("FOO") |
| Go | os.Getenv("FOO"), os.LookupEnv("FOO") |
| Ruby | ENV["FOO"], ENV.fetch("FOO") |
| PHP | getenv("FOO"), $_ENV["FOO"] |
| C# | Environment.GetEnvironmentVariable("FOO") |
| Java / Kotlin | System.getenv("FOO") |
Heuristic (regex, v1): computed keys like
process.env[someVar]can't be detected reliably. The NestJS and Zod patterns lean on conventions rather than a fixed API β aconfigServicereceiver name, azimport alias,SCREAMING_SNAKE_CASEkeys β so a differently-named config service or a non-conventional schema won't be picked up. As everywhere else, real.envfiles are never read β only source code. (src/scan.rs)
Not every project configures itself through the environment. If yours ships a
structured example instead β config.example.toml, settings.sample.yaml,
appsettings.example.json β env-wizard walks that the same way it walks
.env.example: one field at a time, dotted path shown as the prompt (database.host),
the example's value as the default, and its comment (or JSONC ////* */ comment) as
the hint.
Show details
$ env-wizard
Using config.example.toml (no .env.example found).
# Hostname to connect to
? database.host (localhost) βΊ db.prod
β database.port Β· 5432
β database.enabled Β· true
β Wrote config.tomlOnly the values you actually change are touched β everything else (comments, key order, indentation, untouched siblings) comes out byte-for-byte identical to the example. Only scalar fields (string/number/bool) are prompted; arrays and nested tables you don't edit are carried over as-is.
Detection is automatic: env-wizard reads the file and detects the format from its
actual content (TOML/YAML/JSON), falling back to the file extension only when the
content itself isn't decisive (e.g. empty). That means a misnamed template
(config.example.json that's actually TOML) or one with no extension at all
(config.example) still works. A template is still recognized by its filename
(example/sample/dist/template) the same way it is for .env.example
aliases β dotenv examples still take priority if both exist, and dotenv-shaped
filenames (.env, .env.example, β¦) are never reinterpreted as a structured
format no matter what their content looks like. You can always be explicit with
--input/-o. --from-code and env-wizard scan are dotenv-only, since a code
scanner has nothing to say about config keys.
The first time you press ?, env-wizard asks which AI to use and remembers your
choice. Two kinds are supported:
| Kind | What it is | Presets |
|---|---|---|
| CLI command | Pipes the prompt to a local/cloud CLI. Manages its own auth β no keys stored. | Claude (claude -p), Ollama (ollama run <model>), or any custom command |
| OpenAI-compatible HTTP | base_url + model + an env var for the API key. |
OpenAI, local Ollama (http://localhost:11434/v1), LM Studio, OpenRouter, Groq⦠|
Show details β changing your provider
Two equivalent ways β pick whichever you like:
env-wizard config # re-run the interactive pickerβ¦or edit the config file by hand. Its location is, in order: $ENV_WIZARD_CONFIG if
set, else $XDG_CONFIG_HOME/env-wizard/config.toml, else your OS config dir β
~/.config/env-wizard/config.toml on Linux, ~/Library/Application Support/env-wizard/config.toml on macOS, %APPDATA%\env-wizard\config.toml on Windows:
kind = "command" # "command" | "openai"
label = "Claude (CLI)" # shown while fetching a hint
[command] # when kind = "command"
program = "claude"
args = ["-p"]
prompt_via = "arg" # "arg" (append prompt) | "stdin" (pipe prompt)
# [openai] # when kind = "openai"
# base_url = "https://api.openai.com/v1"
# model = "gpt-4o-mini"
# api_key_env = "OPENAI_API_KEY" # empty = no auth (e.g. local Ollama)Run it at the root of a repo that has a .env.example:
env-wizardShow details
At each prompt:
| Input | Effect |
|---|---|
Enter |
Accept the shown default |
? / /hint |
Ask the AI for a hint, then re-prompt |
? <question> / /ask <question> |
Ask the AI a specific question about this variable |
| (empty) | Leave the variable empty |
q |
Quit without writing |
Options:
| Flag | Description |
|---|---|
-i, --input <PATH> |
Example file to read. Omit it to auto-detect: dotenv aliases (.env.example, .env.sample, .env.dist, .env.template, env.example) first, then a .toml/.yaml/.json config template |
-o, --output <PATH> |
File to write. Defaults to .env for a dotenv example, or the template's name with the marker stripped for a config template (config.example.toml β config.toml) |
-y, --yes |
Accept all defaults and overwrite without confirming |
--no-ai |
Disable the AI hint feature (no calls to a provider) |
--from-code |
Also prompt for variables discovered in the code (dotenv only) |
Commands:
| Command | Description |
|---|---|
env-wizard |
Run the interactive .env filler |
env-wizard config |
Choose or change the AI provider |
env-wizard scan |
Audit code usage vs .env.example (add --check to exit 1 on drift, for CI) |
env-wizard completions <shell> |
Print a shell completion script (bash, zsh, fish, elvish, powershell) |
A quick look at the request β hint pipeline:
Show details
.env.example βββΆ parse βββΆ prompt loop βββΆ .env
β
type "?" βββ€
βΌ
gather repo context (README + configs + grep)
βΌ
your AI provider (CLI or HTTP)
βΌ
render the hint cleanly in the terminal
Common questions about privacy, AI providers, and platform support:
Show details
What exactly is sent to the AI, and does it leave my machine?
Only what's needed for a hint: the variable name, its .env.example comment, and repo
context (your README, common config files, and code lines that mention the variable).
The values you type and your existing .env are never sent. With a local
provider (Ollama, LM Studio) even that context stays on your machine β only a cloud
provider receives it over the network. env-wizard has no telemetry and stores no API
keys; providers use their own configured credentials.
Could my existing .env secrets be sent to the AI?
No. When building context, env-wizard deliberately skips real dotenv files
(.env, .env.local, .env.production, β¦) β only template files like .env.example
are ever read. So values already in your .env are never included in a prompt.
(See is_secret_env_file in src/repo.rs.)
I don't have an AI CLI installed β is env-wizard still useful?
Yes. The whole guided flow (inline comment hints, defaults, safe writing) works
without any AI. If you press ? and the chosen provider isn't reachable, you get a
clear error and the wizard simply continues. You can also run with --no-ai.
Which platforms are supported?
Prebuilt binaries and Homebrew cover macOS and Linux. Windows works via
cargo install (the code is cross-platform) but isn't part of the release binaries
yet β feedback welcome.
- Nothing extra for the Homebrew or prebuilt-binary installs. A Rust toolchain
(
cargo) is only needed for the Cargo / from-source methods. - For the
?hint only: the provider you pick must be reachable β the chosen CLI on yourPATH, or the HTTP endpoint up with its API key set. If not, everything else still works and the hint reports a clear error.
If env-wizard saved you the twenty minutes of .env archaeology, a β on the repo
helps other people find it too.
Issues and PRs are welcome! Before opening a PR, please run:
cargo test
cargo clippy --all-targetsMIT Β© Alphonse Terrier
