Inspect and manipulate .env files; secret-looking values are masked by default
Project description
envops
A single-file CLI to inspect and manipulate .env files, designed to be safe by default: values that look like secrets are masked in all output unless you explicitly ask otherwise.
Built with Python stdlib only — a plain python3 (≥3.10) runs it.
Install
uv tool install envops # or: pipx install envops / pip install envops
No uv (or pip)? It's a stdlib-only single file with a plain python3 shebang — any Python ≥3.10 can run it. Just download the raw script:
curl -fsSL https://raw.githubusercontent.com/reorx/envops/master/envops.py -o ~/.local/bin/envops && chmod +x ~/.local/bin/envops
Or run straight from a checkout without installing:
git clone https://github.com/reorx/envops && cd envops
ln -s "$PWD/envops.py" ~/bin/envops # or anywhere on your PATH
Usage
show — print key-value pairs (secrets masked)
envops show ./test.env
FOO=hello
API_SECRET=sk******ij
DATABASE_URL=po******pp
DEBUG=true
Show only certain keys; add --unsafe to expose masked values — use with caution and compromise in mind:
envops show ./test.env -k FOO BAR
envops show ./test.env -k API_SECRET --unsafe
list-keys — list keys only
envops list-keys ./test.env
copy — copy pairs from source to dest
Specify keys with -k, or --full to copy every pair. Changes made to the dest file are printed (masked):
envops copy ./test.env /path/to/dest.env -k FOO BAR
envops copy ./test.env /path/to/dest.env --full
+ BAR="quoted value"
~ FOO=hello (was old_foo)
updated /path/to/dest.env: 2 change(s)
+ means the key was added, ~ means its value was updated. Keys already equal in dest are left untouched. Unrelated lines, comments, and formatting in dest are preserved; the dest file is created if it doesn't exist.
set — set a key's value from stdin
echo value | envops set ./test.env -k FOO
pbpaste | envops set ./test.env -k API_SECRET
One trailing newline is stripped from stdin. Existing keys are updated in place; new keys are appended.
read-value — print a key's raw value
Only use this when the other commands cannot solve the problem, as it exposes the value. --unsafe is required; without it the command fails:
envops read-value ./test.env -K FOO --unsafe
Remote files over SSH
Any file argument may be an scp-style remote path ([user@]host:/path, a colon before the first slash marks it remote). Hosts, keys, and options come from your regular ssh config:
envops show foo@bar:/app/.env
envops copy /tmp/test.env foo@bar:/app/.env --full
envops copy foo@bar:/app/.env ./local.env -k DATABASE_URL
Remote handling keeps the tool's safety guarantees:
- remote content is only ever held in memory — no plaintext temp file lands on the local disk
- writes are atomic: content goes to a
mktempfile next to the target, thenmvreplaces it, so a dropped connection can't leave a half-written.env - the target's permissions are preserved (
stat -con Linux,stat -fon macOS/BSD remotes;600for newly created files) - output masking works exactly as for local files
Secret detection
A value is masked when any of these match — except pure-numeric values (timeouts, sizes, retry counts like AUTH_TOKEN_EXPIRE=604800), which are never treated as secrets:
- Key name contains
SECRET,TOKEN,PASSWORD,API_KEY,ACCESS_KEY,PRIVATE,CREDENTIAL,AUTH,SALT,SIGNING,DSN, ... (case-insensitive) — unless the key's last word marks plain config (URL,URI,ENDPOINT,HOST,PORT,DOMAIN,PATH,NAME,TELEMETRY, ...), soBETTER_AUTH_URLis not treated as a secret by its name alone - Value prefix matches known credential formats:
sk-,ghp_,glpat-,xoxb-,AKIA..., JWT (eyJ...), etc. - Random-looking token run: the value contains an unbroken alphanumeric run of ≥20 chars that mixes letters and digits with Shannon entropy ≥3.5
The entropy check works on alphanumeric runs, so structured values — hostnames (oss-cn-beijing.aliyuncs.com), bucket names (myapp-demo-snapshot), db names — are cut into short segments by - . _ / and pass in the clear.
URL-shaped values (<scheme>://..., any scheme word) are masked per segment instead of as a whole, so the recognizable parts stay readable:
- the userinfo password is always masked — weak passwords are still passwords
- random-looking runs in the path/query are masked (webhook tokens, etc.)
- scheme, username, host, and port stay in the clear
NEO4J_URL=neo4j://neo4j:12******cc@102.10.101.125:7687
SLACK_WEBHOOK=https://hooks.slack.com/services/T01ABCDEFGH/B02JKLMNOPQ/x9******ty
Masked form keeps the first and last 2 characters (sk******ij) so you can tell credentials apart without leaking them.
Env file handling
- Supports
export KEY=value, single/double quotes, inline#comments on unquoted values - Duplicate keys: last occurrence wins (dotenv semantics)
- On write, values containing spaces or special characters are double-quoted with escaping
- Writes preserve comments, blank lines, and unrelated lines byte-for-byte
Skill
For AI agents (Claude Code, etc.), this repo ships a skill at skills/envops/SKILL.md. It instructs the agent to route every .env operation through envops — never cat/read env files directly, prefer masked output, and treat --unsafe as a last resort — and includes install steps plus a one-block cheat sheet of all commands.
To use it, copy or symlink the directory into your skills folder:
ln -s "$PWD/skills/envops" ~/.claude/skills/envops # global
ln -s "$PWD/skills/envops" <project>/.claude/skills/envops # per project
Development
uv run pytest
Tests invoke the CLI as a subprocess and assert on real command behavior.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file envops-0.1.2.tar.gz.
File metadata
- Download URL: envops-0.1.2.tar.gz
- Upload date:
- Size: 16.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.4 {"installer":{"name":"uv","version":"0.11.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63a23436dc1711b23384b1db2f721e10d7832f9a937ff969e437e6a1e117b8d3
|
|
| MD5 |
80ebefb60a5026d04db273658749a394
|
|
| BLAKE2b-256 |
e868c186ee695db7c693796204765dc1095f047383387c8cd89eda2da06f248d
|
File details
Details for the file envops-0.1.2-py3-none-any.whl.
File metadata
- Download URL: envops-0.1.2-py3-none-any.whl
- Upload date:
- Size: 9.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.4 {"installer":{"name":"uv","version":"0.11.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f796bd38ca211b5d567f1abeb3eb5b059544c0a438454aca48e5c38209aa87c5
|
|
| MD5 |
94c5219ed2b1c06fc4888011b2e02623
|
|
| BLAKE2b-256 |
1432818b7ec0bd338038b39b33a69c017fa772c4853e6272735c732216344faa
|