Skip to content

envlit

A simple CLI tool to organize, load, and switch between your project's environment variable contexts.

Use Cases

  • Development environments - Switch between dev/prod configs
  • ML/AI workflows - Conveniently manage CUDA devices, model paths, backends, environment variables
  • Multi-project setup - Isolated environments per project
  • Team consistency - Share common environment configs via git, while keeping local overrides

Installation

pip install envlit

Quick Start

Initialize envlit in your shell (add to .bashrc or .zshrc):

eval "$(envlit init)"

Create a config file at .envlit/default.yaml:

dotenv: "./.env"

env:
  PROJECT_MODE: "Development"
  DEBUG: "true"

Load and unload environment variables:

el                       # Load default profile
echo $PROJECT_MODE       # Output: Development (variable set by envlit)
echo $DEBUG              # Output: true (variable set by envlit)

eul                      # Unload environment variables
echo $PROJECT_MODE       # Output: (empty - variable restored to original state)
echo $DEBUG              # Output: (empty - variable restored to original state)

Features

Smart State Tracking

Automatically detects and preserves manual environment changes between load/unload cycles. Won't overwrite variables you've modified.

Example:

el dev                    # Load dev environment (sets DEBUG=true)
export DEBUG=false        # Manually change DEBUG
export CUSTOM_VAR=foo     # Add your own variable
eul                       # Unload: restores original DEBUG, keeps CUSTOM_VAR

envlit only restores variables it managed, preserving your manual changes.

Dotenv Imports

Explicitly import one or more dotenv files from a profile:

dotenv:
  - "./.env"
  - "./.env.local"

env:
  DEBUG: "true"

Paths are resolved relative to the YAML file. Precedence is parent profile, dotenv files in list order, then the current YAML env: section. Every cross-source replacement prints a warning to stderr without printing either value. Declared files are required, and envlit does not auto-discover dotenv files.

Dynamic Flags

Define short CLI flags in YAML that map to long environment variable names and values. Reduces typing burden:

flags:
  cuda:
    flag: ["--cuda", "-g"]
    default: "0"
    target: "CUDA_VISIBLE_DEVICES"

  backend:
    flag: ["--backend", "-b"]
    default: "c"
    target: "ML_COMPUTE_BACKEND"
    map:
      c: "CPU"
      g: "GPU"
      t: "TPU"

Usage: el dev --cuda 2 -b g sets CUDA_VISIBLE_DEVICES=2 and ML_COMPUTE_BACKEND=GPU

Environment Variable Operations

envlit supports three syntaxes for setting environment variables:

1. String shortcut (for simple set operations):

env:
  DEBUG: "true"
  API_URL: "https://api.example.com"

2. Dict with single operation:

env:
  PATH:
    op: prepend
    value: "./venv/bin"

  PRODUCTION_KEY:
    op: unset

3. List of operations (pipeline):

env:
  PATH:
    - op: remove
      value: "/old/path"
    - op: prepend
      value: "./bin"
    - op: append
      value: "/usr/local/tools"

Available operations: - set - Set a value (string shortcut available) - unset - Unset a variable - prepend - Add to front of path-like variable - append - Add to end of path-like variable - remove - Remove from path-like variable

Lifecycle Hooks

Execute custom scripts at four lifecycle points:

hooks:
  pre_load:
    - name: "Validate setup"
      script: "echo 'Loading...'"
  post_load:
    - name: "Show status"
      script: "echo 'Ready!'"
  pre_unload:
    - name: "Cleanup"
      script: |
        echo "Unloading..."
        echo "Goodbye!"
  post_unload:
    - name: "Confirm"
      script: "echo 'Environment restored.'"

Config Inheritance

Extend base configurations to reduce duplication:

extends: "./base.yaml"
env:
  EXTRA_VAR: "value"

Special Characters in Values

Variable Expansion: Use shell syntax

  • $VAR or ${VAR} - Expands at runtime
  • ${VAR:-default} - With default value

Literal Dollar Sign: Use interpolate: false

PRICE:
  op: set
  value: "$100"
  interpolate: false   # $ is not expanded

Other Special Characters:

  • Backticks, quotes, backslashes are auto-escaped
  • Use YAML quote alternation for quotes:
    • "value 'with' singles"
    • 'value "with" doubles'

Examples:

Input (YAML) Output in Script Shell Interprets Use Case
$HOME $HOME /Users/you Variable expansion (interpolate: true)
${HOME} ${HOME} /Users/you Variable expansion (interpolate: true)
$100 (interpolate: false) $100 $100 Literal dollar
`cmd` \`cmd\` `cmd` Literal backtick
"quoted" \"quoted\" "quoted" Escaped quotes

For complex logic: Use hooks instead of env: section

Multiple Profiles

Switch between dev, test, and prod environments:

el dev      # Load .envlit/dev.yaml
el prod     # Load .envlit/prod.yaml

Configuration

Create .envlit/<profile>.yaml files:

# Optional: Inherit from base config
extends: "./base.yaml"

# Dynamic CLI flags - short flags map to long env var names and values
flags:
  cuda:
    flag: ["--cuda", "-g"]
    default: "0"
    target: "CUDA_VISIBLE_DEVICES"

  backend:
    flag: ["--backend", "-b"]
    default: "c"
    target: "ML_COMPUTE_BACKEND"
    map:
      c: "CPU"
      g: "GPU"
      t: "TPU"

# Environment variables
env:
  # Simple values (string shortcut for 'set' operation)
  DEBUG: "true"
  API_URL: "https://api.example.com"

  # Unset variables
  LEGACY_VAR:
    op: unset

  # Shell expansion
  DATA_PATH: "${HOME}/data"

  # Single operation (dict syntax)
  PRODUCTION_MODE:
    op: set
    value: "true"

  # Path operations (list of operations - pipeline)
  PATH:
    - op: remove
      value: "/deprecated/path"
    - op: prepend
      value: "./bin"
    - op: append
      value: "/usr/local/tools"

  # Single path operation (dict syntax)
  PYTHONPATH:
    op: prepend
    value: "./src"

# Lifecycle hooks
hooks:
  pre_load:
    - name: "Check dependencies"
      script: "command -v docker >/dev/null || echo 'Warning: docker not found'"
  post_load:
    - name: "Show environment"
      script: "echo '✓ Environment loaded'"
  pre_unload:
    - name: "Cleanup"
      script: "echo 'Unloading...'"
  post_unload:
    - name: "Confirm"
      script: "echo '✓ Environment restored'"

Commands

# Initialize shell integration
eval "$(envlit init)"

# Load environments
el                    # Load default profile
el dev                # Load dev profile
el dev --cuda 1       # Load with flags

# Unload environment
eul

# Export tracked variables to .env file
envlit state          # Show variables from envlit's state
envlit state --from-env  # Show current environment values
envlit state > .env   # Export to .env file

# Check installation
envlit doctor

Shell Integration

The init command creates shell functions that wrap envlit: - el - Load environment - eul - Unload environment

Customize aliases during initialization:

eval "$(envlit init --alias-load myload --alias-unload myunload)"

How It Works

  1. Load Phase:
  2. Captures current environment state (snapshot A)
  3. Runs pre-load hooks
  4. Exports environment variables
  5. Runs post-load hooks
  6. Captures new state (snapshot B) and saves difference

  7. Unload Phase:

  8. Runs pre-unload hooks
  9. Restores original environment from saved state
  10. Runs post-unload hooks

  11. State Tracking:

  12. Detects manual changes between snapshots
  13. Preserves user modifications during unload
  14. Only restores variables changed by envlit (Note: those changed during hooks are not tracked, and should be taken care of in hooks themselves)