Help us improve
Share bugs, ideas, or general feedback.
From envlit-skill
Use when you need to load project-specific environment variables or the user needs help with envlit CLI usage, `.envlit/*.yaml` profiles, dynamic flags, PATH operations, lifecycle hooks, or environment restore/state-tracking issues involving `envlit`, `el`, or `eul`.
npx claudepluginhub luocfprime/envlit --plugin envlit-skillHow this skill is triggered — by the user, by Claude, or both
Slash command
/envlit-skill:envlitThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
envlit lets you define named **environment profiles** in YAML files, then load/unload them with a single shell command. It tracks what it changed and intelligently restores your original environment, preserving any manual changes you made in between.
Applies 10 pre-set color/font themes or generates custom ones for slides, documents, reports, and HTML landing pages.
Share bugs, ideas, or general feedback.
envlit lets you define named environment profiles in YAML files, then load/unload them with a single shell command. It tracks what it changed and intelligently restores your original environment, preserving any manual changes you made in between.
CUDA_VISIBLE_DEVICES, model paths, backends per experiment.envlit/ folder committed to gitpip install envlit
# Add to ~/.bashrc or ~/.zshrc (once):
eval "$(envlit init)"
envlit init outputs shell function definitions for el (load) and eul (unload). Without this line, el/eul are not available.
Customize the alias names:
eval "$(envlit init --alias-load myload --alias-unload myunload)"
| Command | What it does |
|---|---|
el | Load .envlit/default.yaml |
el <profile> | Load .envlit/<profile>.yaml |
el <profile> --flag value | Load with dynamic flag overrides |
el --config /path/to/any.yaml | Load an arbitrary config file (bypasses .envlit/ lookup) |
eul | Unload and restore original environment |
envlit state | Print tracked variables in KEY=VALUE format |
envlit state --from-env | Print current shell values for tracked vars |
envlit state > .env | Export tracked vars to a .env file |
envlit doctor | Diagnose installation and config issues |
--config / -c flagel and eul accept --config (short: -c) to load any YAML file regardless of location:
el --config ~/shared/base.yaml # load by path, no profile name
el dev --config /tmp/override.yaml # profile name is ignored when --config is given
envlit load --config path/to/config.yaml # underlying command also supports it
envlit state output formatOutput is one KEY=VALUE per line, sorted alphabetically. Values containing spaces, tabs, $, backticks, quotes, or backslashes are wrapped in double quotes with special characters escaped. Plain values (only letters, digits, /, :, ., -, _, etc.) are output bare:
API_URL=https://api.example.com
DEBUG=true
PATH=/home/user/.local/bin:/usr/bin:/bin
PROJECT_DIR="/home/user/my project"
In the example above, PATH has no special characters so it is unquoted; PROJECT_DIR contains a space so it is double-quoted.
.envlit/<profile>.yaml)Config files live in a .envlit/ directory in your project root.
# Optional: inherit from another profile
extends: "./base.yaml"
# Environment variables
env:
# 1. String shorthand — equivalent to op: set
DEBUG: "true"
API_URL: "https://api.example.com"
# 2. Inline flag — CLI option defined alongside the variable it controls
CUDA_VISIBLE_DEVICES:
flag: ["--cuda", "-g"] # CLI option names (list of strings)
default: "0" # value used when flag is not passed
ML_COMPUTE_BACKEND:
flag: ["--backend", "-b"]
default: "c"
map: # map CLI input → env var value
c: "CPU"
g: "GPU"
t: "TPU"
# 3. Single operation (dict syntax)
PYTHONPATH:
op: prepend
value: "./src"
OLD_VAR:
op: unset
# 4. Operation pipeline (list)
PATH:
- op: remove
value: "/deprecated/bin"
- op: prepend
value: "./bin"
- op: append
value: "/opt/tools"
# Shell variable expansion (default behaviour)
DATA_DIR: "${HOME}/data"
# Literal dollar sign — use interpolate: false
PRICE_TAG:
op: set
value: "$9.99"
interpolate: false
# null (YAML null) is shorthand for op: unset
REMOVED_VAR: null
# Lifecycle hooks — each hook has a name (string) and script (bash string)
# script can be a single line or a multiline block using YAML's | scalar
hooks:
pre_load:
- name: "Check docker"
script: "command -v docker >/dev/null || echo 'Warning: docker not found'"
- name: "Multi-line setup"
script: |
echo 'Starting setup...'
mkdir -p /tmp/myapp
echo 'Done'
post_load:
# post_load runs AFTER env vars are exported — can reference newly set vars
- name: "Show status"
script: "echo 'Loaded. DEBUG=${DEBUG}, MODE=${PROJECT_MODE}'"
pre_unload:
- name: "Cleanup"
script: "echo 'Unloading...'"
post_unload:
- name: "Confirm"
script: "echo 'Environment restored'"
Hook execution order during load:
pre_load hooks run first (env vars NOT yet set — see pre-load state)post_load hooks run (env vars ARE set — safe to reference $MY_VAR)Hook execution order during unload:
pre_unload hooks run (env vars still set from the load)post_unload hooks run (env vars are unset/restored)| Operation | When to use | Required fields |
|---|---|---|
set | Set a value (also the string shorthand) | value |
unset | Remove a variable entirely | — |
prepend | Insert at the front of a : separated list | value |
append | Insert at the back of a : separated list | value |
remove | Remove an entry from a : separated list | value |
All operations support an optional separator field (default :):
env:
PYTHONPATH:
op: prepend
value: "./src"
separator: ":" # explicit (same as default)
interpolate field (on single dict ops only, default true):
true — $VAR and ${VAR} in the value expand at shell runtimefalse — value is treated as a literal string; $ is never expandedImportant:
interpolateis only recognised on a single dict operation ({op: set, value: ..., interpolate: false}). It is not supported inside a list pipeline — list steps always use double-quote (interpolate: true) mode. If you need a literal$in a pipeline, restructure to a single dict op.
env:
# $HOME expands when the shell sources the generated script
BIN_DIR:
op: set
value: "${HOME}/.local/bin"
interpolate: true # default, can be omitted
# $9.99 stays literally as $9.99
PRICE:
op: set
value: "$9.99"
interpolate: false
# WRONG — interpolate is ignored in list pipelines:
# BAD_EXAMPLE:
# - op: set
# value: "$9.99"
# interpolate: false # has no effect here
null shorthand: A YAML null value is equivalent to op: unset:
env:
OLD_API_KEY: null # same as: op: unset
Env var name constraint: names must match
[a-zA-Z_][a-zA-Z0-9_]*. Names with hyphens (e.g.MY-VAR) are rejected.
Flags are defined inline inside env: entries. The variable name is the key; add a flag: field to make it a CLI option:
# .envlit/dev.yaml
env:
CUDA_VISIBLE_DEVICES:
flag: ["--cuda", "-g"]
default: "0"
ML_COMPUTE_BACKEND:
flag: ["--backend", "-b"]
default: "c"
map:
c: "CPU"
g: "GPU"
t: "TPU"
el dev # CUDA_VISIBLE_DEVICES=0, ML_COMPUTE_BACKEND=CPU
el dev --cuda 2 # CUDA_VISIBLE_DEVICES=2, ML_COMPUTE_BACKEND=CPU
el dev -g 0,1 -b g # CUDA_VISIBLE_DEVICES=0,1, ML_COMPUTE_BACKEND=GPU
el dev --help # Shows all flags including --cuda and --backend
Inline flag fields (all inside the env: entry):
flag — CLI option name(s): a single string ("--cuda") or list (["--cuda", "-g"])default — value used when the flag is not passed on the CLI. A value passed on the CLI always overrides the default.map (optional) — dict mapping CLI input → env var value. Unknown values are passed through as-is (no error).Deprecated: The top-level
flags:section (withtarget:field) still works but emits a deprecation warning. Migrate flags intoenv:entries as shown above.
# .envlit/dev.yaml
extends: "./base.yaml" # path relative to this file
env:
DEBUG: "true" # overrides base
Merge rules:
env: child wins (shallow merge per key)flags: child wins (shallow merge per key)hooks: lists are concatenated (base hooks run first)Multi-level chaining is supported: dev.yaml → staging.yaml → base.yaml all resolve correctly. Each file's extends is resolved before merging, so the full ancestor chain is flattened in order.
envlit uses a Compare-and-Swap algorithm so it never clobbers manual changes:
el dev # envlit sets DEBUG=true
export DEBUG=verbose # you change it manually
eul # envlit restores DEBUG to original value
# (NOT to "verbose" — envlit saw you changed it)
Three scenarios on unload:
eulworks only in the same shell session asel. Opening a new terminal loses the tracking state.
Running el twice without eul: Safe. The CAS algorithm detects that each variable's value matches what the previous el set (no manual interference), keeps the original pre-first-load value, and just updates current. eul restores to the state before the first el in the session.
eul with no prior el: If eul is run in a shell where el was never called (no tracking state exists), it is a no-op — it outputs a comment and makes no changes.
No parent-directory traversal: el only looks for .envlit/ in the current working directory. It does not walk up to parent directories. Run el from your project root (where .envlit/ lives), or use --config to point at a file by path.
.envlit/
├── base.yaml # shared: API URLs, common tools
├── dev.yaml # extends base, debug flags
└── prod.yaml # extends base, prod settings
# base.yaml
env:
APP_NAME: "myapp"
LOG_FORMAT: "json"
hooks:
post_load:
- name: "Banner"
script: "echo 'Loaded ${APP_NAME}'"
# dev.yaml
extends: "./base.yaml"
env:
DEBUG: "true"
LOG_LEVEL: "debug"
API_URL: "http://localhost:8000"
# prod.yaml
extends: "./base.yaml"
env:
DEBUG: "false"
LOG_LEVEL: "warn"
API_URL: "https://api.example.com"
| YAML value | Shell sees | Notes |
|---|---|---|
"$HOME/data" | /Users/you/data | Shell expands $HOME (interpolate: true) |
"${PATH:-/usr/bin}" | default expansion | Full ${VAR:-default} syntax supported |
"price $9.99" | price $9.99 (⚠ $9 expands!) | Use interpolate: false for literal $ |
"Use \ls`"` | Use `ls` | Backticks auto-escaped in double-quote mode |
'has "double" quotes' | has "double" quotes | YAML single-quote wrapping |
${VAR:-${OTHER}}(nested expansions) are not supported — the inner}terminates the match early.
| Symptom | Likely cause | Fix |
|---|---|---|
el: command not found | Shell not initialized | Add eval "$(envlit init)" to .bashrc/.zshrc, then source ~/.zshrc |
| Config not loading | Wrong filename | File must be .envlit/<profile>.yaml (or .yml), in the current directory |
Dynamic flags missing from --help | YAML syntax error in env: flag entry | Check indentation; flag: must be inside an env: entry; run envlit doctor |
Variables not restored after eul | Different shell session | eul only works in the shell that ran el |
Invalid environment variable name | Hyphen/space in YAML key | Rename key to match [a-zA-Z_][a-zA-Z0-9_]* |
| Hook not running | Wrong key name | Hook keys are pre_load, post_load, pre_unload, post_unload (underscore) |
Run envlit doctor for automated diagnosis.
pip install envlit, eval "$(envlit init)", create .envlit/default.yamlflag/target/map), valid env var nameseul only works in same shell sessionenv: (not deprecated top-level flags:); verify flag is a list, map keys match what user passes on CLIseparator defaults to :