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 (find, 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 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 --remote exec uv:0.10 -- uv --version
Running a package

The binary is cached in the object store, so subsequent calls with the same version skip the download and don't need --remote. Nothing else persists: no candidate symlink, no current pointer.

TIP

Use ocx 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 --remote exec nodejs:24 bun:1.3 -- bun --version
Running multiple packages together

Installing

Running ocx 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 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 --remote install uv:0.10
Installing a package

Once installed, ocx find --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 find --candidate uv:0.10
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 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. Pair it with ocx ci export to inject environment variables into the runner:

sh
ocx package pull uv:0.10
ocx ci export uv:0.10

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 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 install --select combines installation and selection in one step:

sh
ocx --remote install --select corretto:21
ocx find --current corretto
Installing and selecting a version

To switch between already-installed versions, or to remove the active pointer without uninstalling, use ocx select and ocx 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 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 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 install uv:0.10
ocx uninstall uv:0.10
Uninstalling a package

If the package was also selected, pass --deselect to remove the current pointer in the same step, or run ocx 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 env resolves those declarations relative to the installed path and prints them as a table. ocx exec injects them into a clean child process automatically.

sh
ocx env corretto:21
Package environment

For shell profile integration, ocx shell env emits shell-specific export statements designed for eval. Pass --current to reference the floating pointer rather than a pinned version — the shell picks up new versions automatically whenever you run ocx select:

sh
eval "$(ocx shell env --current corretto)"
Shell profile integration

Persistent shell environment

Add eval "$(ocx shell env --current <package>)" to your ~/.bashrc or ~/.zshrc. Because --current resolves at shell startup, this line stays valid across upgrades — run ocx install --select <package>:<new-version> and open a new terminal. No edit to the profile 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 env nodejs:24 bun:1.3
Composing environments from multiple packages

See the ocx env reference and ocx shell env reference for the full flag set, including --candidate mode for pinning the resolved path to a specific installed version.

Shell Profile

Adding eval "$(ocx shell env --current <package>)" to your shell startup works for one or two tools. By the time you have five — a compiler, a runtime, a build system, a linter, and a formatter — your .bashrc has five nearly-identical lines to maintain. Adding a tool means editing the profile; removing one means remembering which line to delete.

ocx shell profile replaces those manual lines with a declarative manifest. You tell ocx which packages belong in your shell, and ocx resolves all of them at startup in a single pass.

Add packages after installing them:

sh
ocx install --select uv:0.10
ocx install --select nodejs:24
ocx shell profile add uv:0.10 nodejs:24
Managing shell profile packages

ocx shell profile list shows what is in the profile and whether each entry's symlink is healthy:

sh
ocx shell profile list

At shell startup, the ocx env file calls ocx shell profile load to resolve every profiled package and emit the right exports. Broken entries — packages that were deselected or uninstalled since they were added — are silently skipped so a stale entry never blocks your shell from starting.

To stop loading a package, remove it from the profile. The package itself stays installed:

sh
ocx shell profile remove nodejs:24

Resolution modes

By default, profiled packages resolve via the candidates/{tag} symlink — pinned to the specific tag you installed. To use the floating current symlink instead (follows ocx select), use --current:

sh
ocx install --select uv:0.10
ocx shell profile add --current uv:0.10

For the content-addressed object store path instead, use --content. This resolves to the digest-based path in ~/.ocx/objects/ — precise and self-verifying, but the path changes whenever the package is reinstalled at a different version.

Next Steps

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

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

Browsing available packages