ocenv
Run multiple "flavors" of opencode side by side - personal vs. work auth, company-specific providers, per-project config - from a single declarative file instead of a pile of hand-maintained shell aliases.
opencode's OPENCODE_CONFIG is a single file pointer, not a stack, so you cannot express
"always include my company's providers and this project's config". ocenv replaces the
aliases with a model of identities, layers, and profiles, composes layered
config, and launches opencode in a fully isolated environment.
Concepts
- Identity - an isolated opencode footprint. Redirects
XDG_DATA_HOMEso auth (auth.json) and the session db are per-identity, and injects the composed config viaOPENCODE_CONFIG. State, cache, and your shared~/.config/opencodestay shared, so the redirect does not leak into other XDG-aware tools (e.g.nvim) launched from inside opencode.opencode auth loginis done once per identity. Setisolate_config = truefor a hermetic identity that also suppresses the shared global config. - Layer - a named, reusable fragment contributing a config payload and/or environment
variables. The config payload is either inline in
ocenv.tomlor a reference to an external JSON/JSONC file (e.g. your company's providers);envis independent, so anenv-only layer is a valid, reusable secrets/env bundle. - Profile - the launchable unit: an identity + an ordered list of layers + extra env + a working directory. Launching folds the layers' config and env into one, and starts opencode.
Layer/profile env composes left-to-right (later wins), with profile.env applied last.
ocenv's own managed vars (XDG_DATA_HOME, OPENCODE_CONFIG, and XDG_CONFIG_HOME for a
hermetic identity) are pinned last of all, so env cannot accidentally break isolation - if a
layer/profile tries to set one, it is ignored and ocenv warns.
Layers are merged with opencode's own semantics (objects deep-merge, arrays replace, and
instructions is concatenated + de-duplicated), and injected via OPENCODE_CONFIG - which
opencode loads below project config, so a project's own .opencode config still wins,
exactly as normal.
Getting started
Requires Python 3.13+, uv, and opencode on your PATH.
# install
uv sync
# 1. create a config
mkdir -p ~/.config/ocenv
cp examples/ocenv.toml ~/.config/ocenv/ocenv.toml # then edit to taste
# 2. see what a profile resolves to (no opencode launched)
uv run ocenv --profile work show
# 3. inspect the isolated environment
uv run ocenv --profile work env
# 4. authenticate the identity once
uv run ocenv --profile work opencode -- auth login
# 5. launch opencode with the composed, isolated environment
uv run ocenv --profile work opencode
Set a profile for the whole shell with the OCENV_PROFILE environment variable so you can
drop the flag:
export OCENV_PROFILE=work
uv run ocenv opencode
CLI
Profile selection is a global option (aws-cli style) and must come before the command:
ocenv --profile <p> opencode [-- <opencode args...>] # compose + launch opencode
ocenv --profile <p> show # print the composed config as JSON
ocenv --profile <p> env # print the resolved env (export lines)
ocenv list # list profiles + identities
Everything after -- is passed straight through to opencode:
ocenv --profile work opencode -- run "summarize this repo"
ocenv --profile work opencode -- debug config # opencode's own resolved config
--profile / -p also reads from the OCENV_PROFILE env var. Point ocenv at a specific
config file with --config <path>; otherwise it is discovered at
$XDG_CONFIG_HOME/ocenv/ocenv.toml (falling back to ~/.config/ocenv/ocenv.toml).
Configuration
ocenv.toml has three sections - layers, identities, and profiles:
[layers.personal-base.config] # inline opencode fragment
model = "anthropic/claude-opus-4-8"
[layers.company-providers]
file = "~/workspaces/acme/.opencode/providers.json" # external JSON/JSONC file
[layers.company-secrets.env] # env-only layer, reusable bundle
JIRA_URL = "https://jira.acme.example"
[identities.work]
root = "~/.local/share/ocenv/identities/work" # optional; defaults to a managed path
isolate_config = true # optional; suppress shared global config
[profiles.work]
identity = "work"
layers = ["personal-base", "company-providers", "company-secrets"] # order: later wins
cwd = "~/workspaces/acme"
[profiles.work.env]
SOME_TOKEN = "..." # applied after all layer env
See the examples/ directory for complete, commented configurations.
How isolation works
opencode resolves its storage locations from the raw XDG_* env vars, and those are
inherited by every process opencode spawns (the bash tool, $EDITOR, an embedded terminal,
nvim). Overriding all four would therefore redirect those tools too - so ocenv keeps the
redirect deliberately narrow:
XDG_DATA_HOME→<identity>/data. This is the only store forauth.json(and the session db), and opencode offers no other override for it, so this redirect is unavoidable. It is the one leak that remains:nvim's data (plugins) is redirected if you launch it from inside opencode, though its config/state/cache are untouched.OPENCODE_CONFIG→ the composed config file. This is opencode-specific (not an XDG var, so no leak) and loads below project config, preserving "project wins".- state and cache are left shared (
XDG_STATE_HOME/XDG_CACHE_HOMEuntouched). - your shared
~/.config/opencodestill applies as opencode's global base. Setisolate_config = trueon an identity to suppress it: ocenv then pointsXDG_CONFIG_HOMEat a managed empty dir (this redirect is inherited by child tools, so it is opt-in).
ocenv also scrubs opencode's other config-pointer env vars (OPENCODE_CONFIG_CONTENT,
OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_PROJECT_CONFIG) from the launched environment, so
a stray value in your shell cannot leak config into an otherwise isolated identity.
OPENCODE_CONFIG is not scrubbed because ocenv sets it itself.
Development
uv sync
uv run pytest # unit + integration tests
Integration tests exec the real opencode binary and assert against opencode debug config
/ opencode debug paths; they skip automatically when opencode is not installed.
See DESIGN.md for the full design rationale and opencode config-resolution
details.
Metadata
Release files for ocenv 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ocenv-0.1.0.tar.gz | 45.0 kB | Without provenance | Details for ocenv-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ocenv-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for ocenv-0.1.0-py3-none-any.whl |
Total release size: 58.1 kB
Release files / ocenv-0.1.0.tar.gz
| Download URL | ocenv-0.1.0.tar.gz |
|---|---|
| Size | 45.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b5bacb07df3193a534c2fc200da8bbeff6ea8cbf04bda845ec63e1f338e125c2
|
|
BLAKE2b-256 checksum How to use checksums |
97342789519fcd3f9c1880030e744050347692c17cf7588a4c139d0495c3679e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / ocenv-0.1.0-py3-none-any.whl
| Download URL | ocenv-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5b76ab159ca053760f9ade7bdf5e7bb2dbb80f601addba0a287e007a873876ab
|
|
BLAKE2b-256 checksum How to use checksums |
18cb148b1984fdbe82924abe3258cda2dfe74917038fc77c01f6ec5732352c4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for ocenv 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.