Skip to content

Getting Started

This guide walks through the core ocx workflow — from running your first package to managing multiple versions. It assumes ocx is already installed. Each section builds on the previous one, but they can be read independently.

First-time setup

On a fresh install, your local index is empty — ocx won't find any packages. Pass --remote to query the registry directly. Once a package is installed, commands that operate on it (which, select, env, exec) work without the flag. To populate your local index for browsing and offline use, run ocx index update with the packages you need.

Quick Start

Most tools require a separate install step before you can use them for the first time. You configure a source, install the tool, then run it. For a one-off task, that overhead is wasteful.

ocx package exec collapses that into a single command. If the package is not in the local object store, it is downloaded automatically. Then the command runs inside an isolated environment containing only the package's declared variables — no ambient PATH pollution.

sh
ocx package exec "uv:0.10.0" -- uv --version
Running a package

The binary is cached in the object store, so subsequent calls with the same version skip the download. Nothing else persists: no candidate symlink, no current pointer.

TIP

Use ocx package exec for one-off tasks or in CI where you want a reproducible, isolated invocation. For tools you reach for every day, read on.

Running multiple packages in a single invocation

Pass multiple packages before --. Their environments are merged in declaration order so all tools are accessible inside the subprocess.

A real-world example: Bun is a JavaScript runtime that complements Node.js. Bringing both runtimes together with a single command means both node and bun are on PATH without any manual setup:

sh
ocx package exec "nodejs:24.0.0" "bun:1.3.0" -- bun --version
Running multiple packages together

Installing

Running ocx package exec re-resolves the package on every invocation. For tools you use across multiple sessions — compilers, interpreters, build utilities — you want a persistent, named install that is available offline and at a known, stable path.

ocx package install downloads the package into the content-addressed object store and creates a candidate symlink at ~/.ocx/symlinks/{registry}/{repo}/candidates/{tag}. That path never changes after installation. If two tags resolve to the same binary build, only one object lives on disk.

sh
ocx package install "uv:0.10.0"
Installing a package

Once installed, ocx package which --candidate returns the stable candidate path without re-resolving the package — useful for embedding in build scripts, IDE configs, or CI pipelines that need a fixed path pinned to a specific version:

sh
ocx package install "uv:0.10.0"
ocx package which --candidate "uv:0.10.0"
Finding an installed package

Like apt install vs a one-shot download

apt install cmake persists the tool system-wide. A one-shot download script runs it and leaves nothing behind. ocx package install is the former — persistent, versioned, offline-ready — but without root, without a system-wide footprint, and with multiple versions coexisting side by side.

CI workflows: ocx package pull

In CI pipelines, symlink management is unnecessary — you just need the binary at a known path. ocx package pull downloads directly into the object store without creating candidate or current symlinks. To inject resolved environment variables into the runner, use ocx --format json package env for machine-readable output, or ocx package env --shell=sh to emit export statements directly into the runner's environment.

See the object store and install symlinks sections of the user guide for the full three-store architecture behind this.

Switching Versions

Installing a package creates a candidate path that includes the tag: candidates/21. When you upgrade to version 25, the new candidate lands at candidates/25. Any config referencing candidates/21 keeps working — but also stays pinned forever, requiring a manual edit on every upgrade.

ocx package select introduces a second symlink level: current. It is a tag-free, floating pointer to whichever candidate you last declared active. Update it once with select, and every path referencing current picks up the new version automatically.

ocx package install --select combines installation and selection in one step:

sh
ocx package install --select "corretto:21.0.0"
ocx package which --current "corretto:21.0.0"
Installing and selecting a version

To switch between already-installed versions, or to remove the active pointer without uninstalling, use ocx package select and ocx package deselect:

Switching and removing the active version

Same pattern as SDKMAN and nvm

SDKMAN manages Java SDKs with sdk default java 21-tem — a current symlink that re-points to the chosen version without changing $JAVA_HOME. nvm's nvm alias default 20 is the same idea for Node. ocx applies this same pattern to any binary package — including Java itself, as shown above — with no plugin to install and no tool-specific version manager to learn.

Use --current paths in shell profiles and IDE settings

Because current never changes its own path — only its target — you can reference it once and forget it. When you run ocx package install --select corretto:25, current is re-pointed. Your IDE and shell pick up the new version automatically, with no config edits.

See path resolution in the user guide for the full comparison of object-store, candidate, and current paths.

Uninstalling

ocx package uninstall removes the candidate symlink and its back-reference from the object's refs/ directory. The binary itself is kept unless --purge is given and refs/ is empty after the uninstall.

sh
ocx package install "uv:0.10.0"
ocx package uninstall "uv:0.10.0"
Uninstalling a package

If the package was also selected, pass --deselect to remove the current pointer in the same step, or run ocx package deselect separately first.

Uninstall removes the symlink, not necessarily the binary

The candidate symlink is removed immediately. The binary object in the object store is preserved unless --purge is given and no other references remain. To remove all unreferenced objects in one pass, use ocx clean.

See the object store section of the user guide for how the refs/ back-reference system makes garbage collection safe.

Environment

Tools are more than binaries. A compiler suite needs CC, CXX, and LD_LIBRARY_PATH. A runtime needs JAVA_HOME or PYTHONHOME. Keeping those variables in sync with installed paths manually is fragile and collision-prone when multiple tools share a shell.

ocx packages declare their environment variables in metadata.json. ocx package env resolves those declarations relative to the installed path and prints them as a JSON document. ocx package exec injects them into a clean child process automatically.

sh
ocx package install "corretto:21.0.0"
ocx package env "corretto:21.0.0"
Package environment

Persistent shell environment

OCX activation is handled by $OCX_HOME/env.sh, which is written by the installer and sourced from your login profile. At shell startup it invokes ocx self activate --shell=sh via the installer-written absolute path, so activation works even before ocx is on PATH. The command emits three blocks: a PATH prepend to the OCX binary directory, shell completions, and an eval "$(ocx --global env --shell=sh)" call that exports your global toolchain's environment. Because the PATH entry points at the current symlink, your profile stays valid across upgrades — no manual edit required.

Composing environments from multiple packages

Pass multiple packages to merge their environments in declaration order. Variables of type path — like PATH — are prepended to any existing value; constant variables replace it entirely. The result is a complete, composed environment with no manual merging.

nodejs:24 contributes its bin/ to PATH; bun:1.3 contributes its own bin/. Both runtimes are available inside the subprocess without any manual export:

sh
ocx package env "nodejs:24.0.0" "bun:1.3.0"
Composing environments from multiple packages

See the ocx package env reference for the full flag set, including --shell mode for emitting eval-safe export lines.

Project Toolchain

When a repository needs a fixed set of tools — a compiler, a formatter, a linter — you can declare them once and lock them to exact digests. Every contributor and every CI runner then gets the same binaries automatically.

Three commands set up the toolchain:

sh
ocx init
ocx add "cmake:4.2.0"
ocx run -- cmake --version

ocx init writes a minimal ocx.toml. ocx add appends a binding, resolves the tag to per-platform digests, and writes ocx.lock. ocx run reads ocx.lock and spawns the command with the locked toolchain's environment — no manual export or PATH manipulation needed.

See Pin a project's tools in the User Guide for groups, lifecycle commands, and CI setup.

Next Steps

The sections above cover the everyday workflow. For deeper topics:

Config file

No setup is required — OCX works out of the box without any config file. When you want to set a persistent default (like a private registry hostname), place a config.toml in $OCX_HOME/ (default: ~/.ocx/config.toml):

toml
[registry]
default = "registry.company.com"

Environment variables and CLI flags always override config values. For full details, see the Configuration reference.

ocx index catalog and ocx index list let you browse what is available in the registry before installing:

Browsing available packages