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

envsniff

v0.1.1

Published

Scan codebases for environment variables and generate .env.example files

Readme

envsniff

envsniff

Stars Last Commit License Python

Scan codebases for environment variables, detect undocumented vars, and generate .env.example files — with or without AI-written descriptions.

No more "what env vars do I need to run this?" — envsniff finds them all and documents them for you.

envsniff usage demo


Table of Contents


Why envsniff

Most teams document environment variables manually — and it's always out of date. envsniff fixes this by reading the code directly.

| Tool | Gap | | ---------------- | ---------------------------------------------------------- | | dotenv-linter | Lints .env files only — does not scan source code | | sync-dotenv | Syncs .env.env.example, requires an existing .env | | detect-secrets | Security-focused; finds secrets, not documentation | | env-checker | Runtime validation, not a scanner |

envsniff is the only tool that scans your source code, finds every os.getenv() / process.env.X / os.Getenv() call, and generates a documented .env.example from scratch.


Architecture

src/envsniff/
├── models.py           # Immutable dataclasses (SourceLocation, EnvVarFinding, ScanResult)
├── errors.py           # Exception hierarchy
├── config.py           # .envsniff.toml / pyproject.toml loader
├── scanner/
│   ├── engine.py       # Orchestrates walk → dispatch → deduplicate
│   ├── file_walker.py  # .gitignore-aware recursive walk (pathspec)
│   ├── registry.py     # Maps file extensions to plugins
│   ├── type_inferrer.py
│   └── plugins/        # One file per language
├── env_example/
│   ├── parser.py       # Reads existing .env.example preserving structure
│   ├── merger.py       # new / stale / existing classification
│   └── writer.py       # Atomic write (temp → rename)
├── describer/
│   ├── types.py        # Name → InferredType rules
│   ├── fallback.py     # Heuristic descriptions (no API needed)
│   ├── cache.py        # SHA-256 keyed JSON cache
│   └── ai.py           # Multi-provider AI descriptions (Anthropic, OpenAI, Gemini, Ollama)
├── hooks/
│   ├── precommit.py    # Staged-files-only scan
│   └── ci.py           # Full scan with JSON output
└── cli/
    ├── main.py         # Click commands: scan / generate / check
    └── formatters.py   # table / json / markdown renderers

How it works

flowchart TD
    A[Your codebase] --> B[File Walker]
    B -->|respects .gitignore| C[Scanner Registry]
    C --> D1[Python plugin\nos.getenv / os.environ]
    C --> D2[JavaScript plugin\nprocess.env.X]
    C --> D3[Go plugin\nos.Getenv]
    C --> D4[Shell plugin\n$VAR / $VARNAME]
    C --> D5[Docker plugin\nENV / ARG]
    D1 & D2 & D3 & D4 & D5 --> E[Scanner Engine\ndeduplication + type inference]
    E --> F{Command}
    F -->|scan| G[Formatted output\ntable / json / markdown]
    F -->|generate| H[.env.example Parser]
    H --> I[Merger\nnew / stale / existing]
    I --> J[AI Describer\noptional]
    J --> K[Atomic Writer\n.env.example]
    F -->|check| L{All vars documented?}
    L -->|yes| M[exit 0 ✓]
    L -->|no| N[exit 1 ✗\nlist undocumented vars]

The merge strategy

When running envsniff generate, existing human-written comments are always preserved:

| Var state | Action | | ------------------------------------ | ---------------------------------------------------- | | In code + in .env.example | Keep as-is (preserve human description) | | In code, missing from .env.example | Add with # Added by envsniff | | In .env.example, not in code | Comment out with # UNUSED (not found in codebase): |


Install

macOS / Linux

pip install envsniff

Windows

pip install envsniff

Via npm / npx (any OS)

npx envsniff scan .

Local development (after cloning)

# macOS / Linux
git clone https://github.com/harish124/envsniff.git
cd envsniff
pip install -e .

# Windows
git clone https://github.com/harish124/envsniff.git
cd envsniff
pip install -e .

The -e flag installs in editable mode — changes to the source take effect immediately without reinstalling.


Usage

scan

Find all environment variables in a codebase:

envsniff scan .
envsniff scan ./src --format json
envsniff scan . --format md
envsniff scan . --exclude "tests/*" --exclude "*.sh"

Example output:

┌──────────────────┬─────────┬──────────┬─────────┬─────────────────┐
│ Name             │ Type    │ Required │ Default │ Locations       │
├──────────────────┼─────────┼──────────┼─────────┼─────────────────┤
│ DATABASE_URL     │ URL     │ yes      │ —       │ app.py:12       │
│ API_KEY          │ SECRET  │ yes      │ —       │ client.py:8     │
│ DEBUG            │ BOOLEAN │ no       │ false   │ settings.py:3   │
│ PORT             │ INTEGER │ no       │ 8080    │ server.py:1     │
└──────────────────┴─────────┴──────────┴─────────┴─────────────────┘
Scanned 42 files · Found 4 variables

generate

Create or update .env.example:

envsniff generate .
envsniff generate . --output .env.example
envsniff generate . --ai          # prompts for provider + model interactively
envsniff generate . --no-ai       # skip AI, use heuristic descriptions
envsniff generate . --ai --ai-provider openai --ai-model gpt-4o   # non-interactive

Example .env.example output:

# Added by envsniff
#
# Description: PostgreSQL/MySQL connection string
# Example:     postgres://user:pass@localhost/dbname
#
DATABASE_URL=

# Added by envsniff
#
# Description: Api API key
#
API_KEY=

# Added by envsniff
#
# Description: Enable debug mode (true/false)
# Example:     false
#
DEBUG=false

# Added by envsniff
#
# Description: Port number the server listens on
# Example:     8080
#
PORT=8080

Heuristic descriptions are always generated — no --ai flag needed. The # Example: line is omitted when no example value is known.

check

Use in CI or pre-commit to enforce documentation:

envsniff check .                  # exit 1 if undocumented vars exist
envsniff check . --fail-on-stale  # also exit 1 if stale vars in .env.example
envsniff check . --strict         # fail on any issue

Supported languages

Python JavaScript TypeScript Go Shell Docker

| Language | Patterns detected | | ----------------------- | ---------------------------------------------------------- | | Python | os.getenv("X"), os.environ.get("X"), os.environ["X"] | | JavaScript / TypeScript | process.env.X, process.env["X"] | | Go | os.Getenv("X"), os.LookupEnv("X") | | Shell | $VAR, ${VAR} — skips local variables (VAR=value without export), shell specials ($$, $?, $1$9) | | Dockerfile | ENV VAR=value, ARG VAR=default |


Technologies

Python Click tree--sitter Anthropic OpenAI Google Gemini Ollama

| Library | Purpose | | ---------------------------------------------------------------------------- | ------------------------------------------------------ | | Click | CLI framework | | tree-sitter | AST-based source code parsing per language | | questionary | Interactive terminal prompts with arrow-key navigation | | pathspec | .gitignore-aware file walking | | anthropic | Anthropic Claude AI descriptions | | openai | OpenAI / Grok / Perplexity AI descriptions | | google-generativeai | Google Gemini AI descriptions | | ollama | Local Ollama AI descriptions |


AI descriptions

All AI providers are bundled with envsniff — no extra install step. Pass --ai and you will be prompted to choose a provider and model interactively:

$ envsniff generate . --ai

? Which AI provider?
  1. Anthropic (Claude)
❯ 2. OpenAI (GPT)
  3. Google Gemini
  4. Ollama (local)

  Tip: check the official openai documentation for available models.
? Which model? (e.g. gpt-4o-mini): gpt-4o

Use arrow keys to select a provider, Enter to confirm. Model name is required — leaving it blank will error.

Or skip the prompts by passing flags directly (useful for CI):

envsniff generate . --ai --ai-provider openai --ai-model gpt-4o

Set the API key for your chosen provider before running:

| Provider | API key env var | Notes | | --------- | ------------------- | ----------------------------------------------------- | | Anthropic | ANTHROPIC_API_KEY | Default provider | | OpenAI | OPENAI_API_KEY | Also works for Grok, Perplexity via OPENAI_BASE_URL | | Gemini | GEMINI_API_KEY | | | Ollama | — | Local, no key needed |

  • Batches up to 20 vars per API call
  • Caches results in ~/.cache/envsniff/descriptions.json — API is never called twice for the same var
  • Falls back to heuristic descriptions if the provider is unavailable

Privacy

envsniff scrubs code snippets before sending anything to the AI provider. Default values (the second argument in calls like os.environ.get("KEY", "value")) are stripped using regex so only the variable name and call structure are transmitted:

# What's in your code:
db = os.environ.get("DATABASE_URL", "postgres://user:secret@prod/db")

# What the AI receives:
os.environ.get("DATABASE_URL")

What is never sent: .env file contents, actual secret values, or any string literals used as defaults.

Disclaimer — known limitation: The scrubbing works on single-line calls. If a default value spans multiple lines, the regex cannot reliably detect it and that line may be sent as-is:

# Multi-line call — default value on its own line may not be scrubbed:
url = os.environ.get(
    "DATABASE_URL",
    "postgres://user:secret@prod/db"   # ← this line could be sent
)

This is an inherent limitation of line-by-line text scrubbing. The fix would require full AST-level analysis, which is not currently implemented. To be safe, avoid hardcoding real secrets as default values in your source code — use placeholder strings like "postgres://localhost/mydb" instead.


Pre-commit hook

Add to .pre-commit-config.yaml:

repos:
  - repo: https://github.com/harish124/envsniff
    rev: v0.1.0
    hooks:
      - id: envsniff-check

This blocks commits that introduce undocumented environment variables.


GitHub Action

Add envsniff to any repository in seconds. On every pull request it scans your code, updates .env.example, and commits the result automatically — no manual step needed.

# .github/workflows/envsniff.yml
name: Sync .env.example

on:
  pull_request:
    branches: [main, master]

jobs:
  envsniff:
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
          fetch-depth: 0

      - uses: harish124/envsniff@v1
        with:
          path: "."
          commit: "true" # auto-commit updated .env.example
          fail-on-drift: "false" # set "true" to block merges on undocumented vars
          commit-message: "chore: sync .env.example [skip ci]"

Action inputs

| Input | Default | Description | | ---------------- | -------------------------- | --------------------------------------------------------- | | path | . | Directory to scan | | commit | true | Commit updated .env.example back to the PR branch | | fail-on-drift | false | Block the merge when new undocumented variables are found | | commit-message | chore: sync .env.example | Commit message for the auto-commit | | python-version | 3.12 | Python version used to install envsniff |

Action outputs

| Output | Description | | ---------------- | ----------------------------------------------------------------------- | | new-vars | Comma-separated list of newly found variables not yet in .env.example | | stale-vars | Comma-separated list of variables in .env.example no longer in code | | scanned-files | Number of source files scanned | | drift-detected | true when new undocumented variables were found |

Requires: Repository Settings → Actions → General → Workflow permissions → Read and write permissions

A ready-to-copy workflow file is available at .github/examples/envsniff.yml.


CI integration

# .github/workflows/ci.yml
- name: Check env vars are documented
  run: envsniff check . --strict

For machine-readable output in CI logs:

envsniff check . --format json
status: fail
new_vars[1]: NEW_SECRET
stale_vars[0]:
scanned_files: 42

Configuration

Create .envsniff.toml in your project root (or add [tool.envsniff] to pyproject.toml):

[tool.envsniff]
exclude = ["tests/*", "scripts/*", "*.sh"]
output = ".env.example"
ai = false
ai_provider = "anthropic"   # anthropic | openai | gemini | ollama
ai_model = ""               # leave empty to be prompted, or set a specific model

Output formats

scan and check support --format table (default), --format json, and --format md.

JSON — for scripting and CI:

findings[1]:
  - name: DATABASE_URL
    inferred_type: URL
    is_required: true
    default_value: null
    language: python
    locations[1]{file,line}:
      app.py,12
scanned_files: 42
errors[0]:

Markdown — for documentation:

| Name         | Type    | Required | Default |
| ------------ | ------- | -------- | ------- |
| DATABASE_URL | URL     | yes      | —       |
| DEBUG        | BOOLEAN | no       | false   |