Skip to main content

Universal .env variable manager — read, write, encrypt, delegate across services and devices

Project description

getv — Universal .env Variable Manager

PyPI version Python versions License: Apache-2.0 Downloads Tests

Read, write, encrypt, and delegate environment variables across services and devices.

📑 Table of Contents

Why getv?

Every project reinvents .env parsing. getv provides one library for:

  • Reading/writing .env files with comment preservation
  • Profile management — named configs for devices, LLM providers, databases
  • App defaults — per-app profile selection (~/.getv/defaults/APP.conf)
  • Integrations — plugins for SSH, LiteLLM, Ollama, Docker, curl, Pydantic
  • Secret masking — automatic detection and masking of passwords/keys in logs
  • Encryption — Fernet-based encryption of sensitive values for safe transport
  • Format export — dict, JSON, shell, docker-compose, pydantic BaseSettings
  • CLI — manage profiles from the command line

Install

pip install getv                   # core
pip install "getv[crypto]"         # + encryption (Fernet)
pip install "getv[pydantic]"       # + pydantic BaseSettings export
pip install "getv[all]"            # everything

v0.2.1 — New: getv grab (clipboard API key auto-detection), integrations, app defaults, 9 examples

Quick Start

Python API

from getv import EnvStore, ProfileManager

# Single .env file
store = EnvStore("~/.myapp/.env")
store.set("DB_HOST", "localhost").set("DB_PORT", "5432").save()
print(store.get("DB_HOST"))  # "localhost"

# Profile manager — multiple named configs
pm = ProfileManager("~/.getv")
pm.add_category("devices", required_keys=["RPI_HOST", "RPI_USER"])
pm.add_category("llm", required_keys=["LLM_MODEL"])

pm.set("devices", "rpi3", {
    "RPI_HOST": "192.168.1.10",
    "RPI_USER": "pi",
    "RPI_PASSWORD": "secret",
    "RPI_PORT": "22",
})

pm.set("llm", "groq", {
    "LLM_MODEL": "groq/llama-3.3-70b-versatile",
    "GROQ_API_KEY": "gsk_xxx",
})

# Merge profiles on top of base config
base = {"APP_NAME": "myapp", "RPI_HOST": "default"}
cfg = pm.merge_profiles(base, devices="rpi3", llm="groq")
# cfg["RPI_HOST"] == "192.168.1.10" (overridden by device profile)
# cfg["LLM_MODEL"] == "groq/llama-3.3-70b-versatile"

# App-specific defaults
from getv.app_defaults import AppDefaults
defaults = AppDefaults("myapp")
defaults.set("llm", "groq").set("devices", "rpi3")
# Later: cfg = pm.merge_profiles(base, **defaults.as_profile_kwargs())

CLI

# Set variables
getv set devices rpi3 RPI_HOST=192.168.1.10 RPI_USER=pi RPI_PASSWORD=secret

# Get a single variable
getv get devices rpi3 RPI_HOST
# → 192.168.1.10

# List all categories
getv list
#   devices/ (2 profiles)
#   llm/ (3 profiles)

# List profiles in a category (secrets masked)
getv list devices
#   rpi3: RPI_HOST=192.168.1.10, RPI_USER=pi, RPI_PASSWORD=secr***

# Show all variables (unmasked)
getv list devices rpi3 --show-secrets

# Export to different formats
getv export devices rpi3 --format json
getv export devices rpi3 --format shell
getv export devices rpi3 --format pydantic
getv export llm groq --format docker

# Encrypt sensitive values (Fernet)
getv encrypt devices rpi3
# → Generated key: ~/.getv/.fernet.key
# → Encrypted sensitive values in devices/rpi3

# Decrypt
getv decrypt devices rpi3

# Delete a profile
getv delete devices old-rpi

# Execute commands with profile environment
getv exec llm groq -- python my_script.py
getv exec devices rpi3 -- ssh pi@host uname -a

# SSH to devices using profile
getv ssh rpi3                    # interactive shell
getv ssh rpi3 "uname -a"        # run remote command

# Make authenticated API calls
getv curl groq https://api.groq.com/openai/v1/models
getv curl openai https://api.openai.com/v1/models -X POST -d '{"model":"gpt-4"}'

# Set app-specific defaults
getv use myapp llm groq
getv use myapp devices rpi3

# Show app defaults
getv defaults              # list all apps
getv defaults myapp       # show myapp defaults

Profile Directory Structure

~/.getv/                       ← GETV_HOME (configurable)
├── .fernet.key                ← encryption key (chmod 600)
├── defaults/                  ← per-app default profile selections
│   ├── fixpi.conf             → llm=groq, devices=rpi3
│   ├── prellm.conf            → llm=openrouter
│   └── marksync.conf          → llm=ollama-local
├── devices/
│   ├── rpi3.env
│   ├── rpi4-prod.env
│   └── nvidia.env
├── llm/
│   ├── groq.env
│   ├── openrouter.env
│   └── ollama-local.env
└── ollama/
    ├── local.env
    └── gpu-server.env

Each .env file is a standard KEY=VALUE file:

# ~/.getv/devices/rpi3.env
RPI_HOST=192.168.1.10
RPI_USER=pi
RPI_PASSWORD=secret
RPI_PORT=22

App Defaults

Each app remembers which profile to use — so fixpi uses Groq while marksync uses Ollama:

# Set defaults (one-time)
getv use fixpi llm groq
getv use fixpi devices rpi3
getv use prellm llm openrouter
getv use marksync llm ollama-local

# Check what's configured
getv defaults
#   fixpi: devices=rpi3, llm=groq
#   marksync: llm=ollama-local
#   prellm: llm=openrouter

# In your app startup code:
from getv import AppDefaults, ProfileManager
defaults = AppDefaults("fixpi")
pm = ProfileManager("~/.getv")
cfg = pm.merge_profiles({}, **defaults.as_profile_kwargs())

Integrations

getv ships with plugins for common tools:

SSH

# Setup once
getv set devices rpi3 RPI_HOST=192.168.1.10 RPI_USER=pi RPI_PASSWORD=raspberry

# Connect
getv ssh rpi3                       # interactive shell
getv ssh rpi3 "uname -a"            # remote command
from getv.integrations.ssh import SSHEnv
ssh = SSHEnv.from_profile("rpi3")
ssh.run("uname -a", capture=True)            # subprocess
params = ssh.as_paramiko_kwargs()            # for paramiko

LiteLLM

# Setup providers
getv set llm groq LLM_MODEL=groq/llama-3.3-70b-versatile GROQ_API_KEY=gsk_xxx
getv set llm openrouter LLM_MODEL=openrouter/google/gemini-2.0-flash-exp:free OPENROUTER_API_KEY=sk-or-xxx

# Switch at runtime
getv exec llm groq -- python my_script.py
getv exec llm openrouter -- python my_script.py
from getv.integrations.litellm import LiteLLMEnv
llm = LiteLLMEnv.from_profile("groq")
llm.activate()  # sets os.environ
# or: litellm.completion(**llm.as_completion_kwargs(), messages=[...])

Ollama

getv set ollama gpu-server OLLAMA_API_BASE=http://192.168.1.50:11434 OLLAMA_MODEL=qwen2.5-coder:14b
getv exec ollama gpu-server -- ollama run qwen2.5-coder:14b
from getv.integrations.ollama import OllamaEnv
oll = OllamaEnv.from_profile("gpu-server")
oll.activate()  # sets OLLAMA_API_BASE in env
print(oll.litellm_model())  # "ollama/qwen2.5-coder:14b"

Docker

getv export llm groq --format docker > /tmp/groq.env
docker run --env-file /tmp/groq.env my-llm-app:latest
from getv.integrations.docker import DockerEnv
denv = DockerEnv.from_profiles(llm="groq", devices="rpi3")
denv.write_env_file("/tmp/docker.env")
print(denv.compose_environment())  # docker-compose block

curl

# API call with auth from profile
getv curl groq https://api.groq.com/openai/v1/models
getv curl openai https://api.openai.com/v1/models

Pydantic Settings

from getv.integrations.pydantic_env import load_profile_into_env
load_profile_into_env("llm", "groq")  # inject into os.environ
settings = MySettings()               # pydantic reads from env

Subprocess / Pipe

# Run any command with profile env injected
getv exec llm groq -- python my_script.py
getv exec devices rpi3 -- ansible-playbook deploy.yml

# Shell eval
eval $(getv export llm groq --format shell)

Grab — Clipboard API Key Detection

Copy an API key → run getv grab → auto-detected, saved.

# 1. Copy API key from console.groq.com (Ctrl+C)
# 2. Run:
getv grab

# Output:
# Detected:  groq (GROQ_API_KEY)
# Key:       gsk_abc1...9jkl
# Source:    Prefix match
# Domain:    console.groq.com
# Category:  llm
# Profile:   ~/.getv/llm/groq.env
# Saved to /home/user/.getv/llm/groq.env

# Options:
getv grab --dry-run           # detect only, don't save
getv grab --category api      # override category
getv grab --provider myname   # override provider name
getv grab --no-browser        # skip browser history check

Supported prefixes (auto-detected)

Prefix Provider Env Var Category
sk-ant- Anthropic ANTHROPIC_API_KEY llm
sk-or- OpenRouter OPENROUTER_API_KEY llm
sk- / sk-proj- OpenAI OPENAI_API_KEY llm
gsk_ Groq GROQ_API_KEY llm
key- Mistral MISTRAL_API_KEY llm
xai- xAI XAI_API_KEY llm
pplx- Perplexity PERPLEXITY_API_KEY llm
nvapi- NVIDIA NVIDIA_API_KEY llm
hf_ HuggingFace HF_API_KEY llm
r8_ Replicate REPLICATE_API_TOKEN llm
ghp_ GitHub GITHUB_TOKEN tokens
glpat- GitLab GITLAB_TOKEN tokens
AKIA AWS AWS_ACCESS_KEY_ID cloud
dop_v1_ DigitalOcean DIGITALOCEAN_TOKEN cloud
tskey- Tailscale TAILSCALE_API_KEY tokens
SG. SendGrid SENDGRID_API_KEY email
sk_live_ / sk_test_ Stripe STRIPE_API_KEY payments

Detection priority

  1. Key prefix — covers ~90% of cases (instant)
  2. Browser history — Chrome/Firefox SQLite (last 10 min)
  3. User prompt — fallback
# Python API
from getv.integrations.clipboard import ClipboardGrab

grab = ClipboardGrab()
result = grab.detect()  # reads clipboard, returns GrabResult or None

if result:
    print(result.provider, result.env_var)
    result.save()  # writes to ~/.getv/llm/groq.env

One-liner Examples

Popular API Tokens

# OpenAI
export OPENAI_API_KEY=$(getv get llm openai OPENAI_API_KEY) && python my_script.py

# GitHub
git clone https://$(getv get git github GH_TOKEN)@github.com/user/repo.git

# AWS
export AWS_ACCESS_KEY_ID=$(getv get aws prod AWS_ACCESS_KEY_ID) && \
export AWS_SECRET_ACCESS_KEY=$(getv get aws prod AWS_SECRET_ACCESS_KEY) && \
aws s3 ls

# Docker Hub
echo $(getv get docker hub DOCKERHUB_TOKEN) | docker login --username user --password-stdin

# Slack
curl -X POST -H 'Authorization: Bearer '$(getv get chat slack SLACK_BOT_TOKEN) \
  -H 'Content-type: application/json' --data '{"text":"Hello"}' \
  https://slack.com/api/chat.postMessage

# Multiple env vars
eval "$(getv export llm openai --format shell)" && python my_script.py

# Docker compose
getv export app production --format env > .env && docker-compose up

# Direct API calls
getv curl openai https://api.openai.com/v1/models
getv curl groq https://api.groq.com/openai/v1/chat/completions -X POST -d '{"model":"llama3-70b"}'

Security

Automatic Secret Detection

Keys matching these patterns are automatically masked in display/logs:

PASSWORD, PASSWD, SECRET, TOKEN, API_KEY, APIKEY, PRIVATE_KEY, ACCESS_KEY, ACCESS_TOKEN, AUTH, CREDENTIAL

from getv.security import mask_dict, is_sensitive_key

data = {"RPI_HOST": "10.0.0.1", "RPI_PASSWORD": "secret123"}
print(mask_dict(data))
# {"RPI_HOST": "10.0.0.1", "RPI_PASSWORD": "secr***"}

Encryption for Transport

from getv.security import generate_key, encrypt_store, decrypt_store

key = generate_key()
data = {"RPI_HOST": "10.0.0.1", "RPI_PASSWORD": "secret"}
encrypted = encrypt_store(data, key, only_sensitive=True)
# {"RPI_HOST": "10.0.0.1", "RPI_PASSWORD": "ENC:gAAA..."}

original = decrypt_store(encrypted, key)
# {"RPI_HOST": "10.0.0.1", "RPI_PASSWORD": "secret"}

Format Export

Format Function Output
dict store.as_dict() {"KEY": "val"}
JSON to_json(data) {"KEY": "val"}
Shell to_shell_export(data) export KEY='val'
Docker to_docker_env(data) KEY=val
.env to_env_file(data) KEY=val
Pydantic to_pydantic_settings(data) Python class source
Pydantic model to_pydantic_model(data) BaseSettings instance

CLI Reference

Command Description
getv set CATEGORY PROFILE KEY=VAL... Create/update a profile
getv get CATEGORY PROFILE KEY Get a single value
getv list [CATEGORY [PROFILE]] List categories, profiles, or vars
getv delete CATEGORY PROFILE Delete a profile
getv export CATEGORY PROFILE --format FMT Export (json/shell/docker/env/pydantic)
getv encrypt CATEGORY PROFILE Encrypt sensitive values
getv decrypt CATEGORY PROFILE Decrypt values
getv exec CATEGORY PROFILE -- CMD... Run command with profile env
getv use APP CATEGORY PROFILE Set app default profile
getv defaults [APP] Show app defaults
getv ssh PROFILE [CMD] SSH to device from profile
getv curl PROFILE URL Authenticated API call
getv grab [--dry-run] Auto-detect API key from clipboard and save

Examples

See examples/ directory:

File Description
01_quick_start.py Centralized .env management
02_ssh_from_profile.py SSH/SCP with paramiko/fabric
03_litellm_multi_provider.py Switch LLM providers
04_ollama_config.py Ollama local/remote/Docker
05_docker_env.py Docker env files & compose
06_app_defaults.py Per-app default profiles
07_pipe_and_shell.sh Shell integration & pipes
08_pydantic_settings.py Pydantic Settings bridge
09_grab_api_key.py Clipboard API key auto-detection

Environment Variables

Variable Default Description
GETV_HOME ~/.getv Base directory for profiles

Adopted by

Projects using getv for .env management:

  • fixpi — SSH + LLM diagnostic agent
  • prellm — LLM preprocessing proxy
  • code2logic — Code analysis engine
  • amen — Intent-iterative AI gateway
  • marksync — Markdown sync server
  • curllm — LLM-powered web automation

Development

git clone https://github.com/wronai/getv.git
cd getv
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest  # 128 tests

License

Apache License 2.0 - see LICENSE for details.

Author

Created by Tom Sapletta - tom@sapletta.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

getv-0.2.3.tar.gz (45.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

getv-0.2.3-py3-none-any.whl (41.2 kB view details)

Uploaded Python 3

File details

Details for the file getv-0.2.3.tar.gz.

File metadata

  • Download URL: getv-0.2.3.tar.gz
  • Upload date:
  • Size: 45.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for getv-0.2.3.tar.gz
Algorithm Hash digest
SHA256 97b0aae633f4622cbc98346dd3294e16ceba78e4a437825b1807bae6f874f935
MD5 3fc0094f7b14c681e12030fa64a375e5
BLAKE2b-256 b3d5a37a22da7ce4949f28d31c933b2c0436a4116d3954038d342cc85f40e515

See more details on using hashes here.

File details

Details for the file getv-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: getv-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 41.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for getv-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 70cf9b05bbfab55ba9f3702887a8be100261e4f55e2371501d191d309d5aa9e3
MD5 2288911fb41ca74c303281beafac186c
BLAKE2b-256 c7ed758f1c0f27b24d798a22e26522ecba0107ff3be2e8132dc781ba3ba74f1f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page