onenv

Environment variables, backed by 1Password.

Replaces .env files with per-key 1Password items. Scriptable CLI with structured JSON output.

git clone https://github.com/doublej/onenv && cd onenv && bun install.ts

Features

.env replacement

Same KEY=value ergonomics. Values live in a 1Password vault instead of a file on disk.

onenv run -- <cmd>

Reads the project’s .onenv.json, fetches enabled secrets, injects them as env vars, execs the command. Secrets exist only in the child process.

Pipeable writes

onenv set <ns> <KEY> --value-stdin reads the secret from stdin instead of an interactive prompt. Chain with op read, pbpaste, or aws ssm get-parameter; the value does not enter shell history.

Drift detection

onenv check compares process.env against the project’s stored secrets via SHA-256 fingerprints. Reports match / differ / missing / shell-only and exits non-zero on drift. Values are never printed.

Fingerprint comparison

onenv get <ns> <KEY> --fingerprint returns sha256:<hex>. Diff two environments or confirm a rotation reached prod without exposing the value.

Atomic bulk import

onenv import --overwrite snapshots every key it would touch, writes the new payload, and rolls back the batch if any single write fails.

JSON file workflow

onenv import flattens GCP service accounts, OAuth tokens, and kubeconfigs into per-leaf 1Password items. onenv run --file group:VAR materializes the rebuilt JSON to a 0600 tempfile and exposes its path. --from-stdin imports without writing the source JSON to disk.

Per-project namespaces

.onenv.json declares which namespaces a project pulls. A frontend project does not see backend keys it did not request. Cross-namespace key collisions are a hard error, not silent last-write-wins.

Non-TTY guardrails

onenv unset refuses to delete from a non-interactive context unless --yes is passed. onenv set and onenv edit require --value-stdin when stdin is not a TTY; piped values error out instead of hanging at the password prompt.

Disable without deleting

onenv disable hides a key from run and export but keeps it in 1Password. enable restores. State lives in ~/.config/onenv-manager/state.json.

Agent primer

onenv prime emits the full CLI spec — every command, flag, error code, and state file — as XML, Markdown, or JSON. Pipeable into agent context.

Service-account auth

OP_SERVICE_ACCOUNT_TOKEN accepts a literal token or an op:// reference. The first call resolves and caches at ~/.config/onenv-manager/op-token (mode 0600); subsequent calls are silent. Re-resolves on auth failure.

@-refs

Positional shorthand against the last namespace list: @1, @2, @last. Avoids retyping long namespace names.

JSON output

Every command emits machine-readable JSON when piped or with --json. Errors return a structured envelope with code, category, retryable flag, hint, and suggestion.

Getting started

1

Install

Requires the 1Password CLI and Bun. The installer handles config and optional .env migration.

brew install 1password-cli bun
op signin
op vault create onenv

git clone https://github.com/doublej/onenv && cd onenv
bun install.ts
2

Add a secret

Interactive prompt. The value does not appear in shell history or process listings.

onenv set aws AWS_ACCESS_KEY_ID
3

Configure your project

Writes a per-project .onenv.json declaring which namespaces this project pulls. Commit it — metadata, not secrets.

cd my-project
onenv init
4

Run with secrets injected

Replaces source .env && node app.js. Secrets exist only in the child process’s environment.

onenv run -- node app.js
5

Pipe values from another store

--value-stdin reads the secret from a pipe so the value does not appear in shell history or process listings. Useful for migrating from another secret store.

op read "op://Private/AWS/credential" | onenv set aws AWS_SECRET_ACCESS_KEY --value-stdin
pbpaste | onenv edit cloudflare CLOUDFLARE_API_TOKEN --value-stdin
6

Check for drift

onenv check compares the current shell environment against the project’s stored secrets via SHA-256. Exits non-zero on mismatch. Safe in a precommit hook or shell prompt: no values are printed.

onenv check
onenv get aws AWS_SECRET_ACCESS_KEY --fingerprint
# sha256:f5a1...
7

Materialize a JSON file

For tools that expect a path instead of env vars (GCP, kubeconfigs, OAuth). The tempfile lives under XDG_RUNTIME_DIR with mode 0600 and is removed on child exit, SIGINT, or SIGTERM.

onenv import google /tmp/sa.json --group sa
onenv run --file sa:GOOGLE_APPLICATION_CREDENTIALS -- python app.py

Why

.env files sit unencrypted next to source code, drift between machines, and end up in git status. Sharing them means Slack DMs and stale copies on multiple laptops.

onenv keeps the same KEY=value ergonomics, but values live in a 1Password vault: biometric or service-account auth, full audit log, and atomic rotation. Mutations can be gated behind a macOS permission dialog by setting ONENV_CONFIRM_MUTATIONS=1.