Skip to main content

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_HOME so auth (auth.json) and the session db are per-identity, and injects the composed config via OPENCODE_CONFIG. State, cache, and your shared ~/.config/opencode stay shared, so the redirect does not leak into other XDG-aware tools (e.g. nvim) launched from inside opencode. opencode auth login is done once per identity. Set isolate_config = true for 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.toml or a reference to an external JSON/JSONC file (e.g. your company's providers); env is independent, so an env-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 for auth.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_HOME untouched).
  • your shared ~/.config/opencode still applies as opencode's global base. Set isolate_config = true on an identity to suppress it: ocenv then points XDG_CONFIG_HOME at 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.

Download files

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

Source Distribution

ocenv-0.1.0.tar.gz (45.0 kB view details)

Uploaded Source

Built Distribution

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

ocenv-0.1.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file ocenv-0.1.0.tar.gz.

File metadata

  • Download URL: ocenv-0.1.0.tar.gz
  • Upload date:
  • Size: 45.0 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for ocenv-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b5bacb07df3193a534c2fc200da8bbeff6ea8cbf04bda845ec63e1f338e125c2
MD5 e08fc87a0ec830c31c8b0145ed7d468a
BLAKE2b-256 97342789519fcd3f9c1880030e744050347692c17cf7588a4c139d0495c3679e

See more details on using hashes here.

File details

Details for the file ocenv-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: ocenv-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.2 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for ocenv-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5b76ab159ca053760f9ade7bdf5e7bb2dbb80f601addba0a287e007a873876ab
MD5 74ec9605971a3f891cdd5cddffe92a51
BLAKE2b-256 18cb148b1984fdbe82924abe3258cda2dfe74917038fc77c01f6ec5732352c4d

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 Sentry Error logging StatusPage Status page