ocm CLI
ocm is a small CLI that attaches your local OpenCode TUI to a repo hosted on an OpenCode Manager. Prompts execute on the Manager's filesystem against a single shared OpenCode server, while your laptop terminal hosts the TUI.
Quickstart
- Get your Manager URL — the web UI address where your OpenCode Manager is running (e.g.,
https://manager.example.com) - Copy your internal token — open Settings → OpenCode Config → Server maintenance, expand Manager Internal Token, and copy the token
- Install the CLI —
pnpm add -g @opencode-manager/ocm-cli - Log in —
ocm login https://your-manager-url(paste the token when prompted) - List repos —
ocm listto see repos configured on the Manager - Attach —
ocm use <repo-id>to start an OpenCode session attached to that repo
Compatibility
| ocm | OpenCode Manager | Local OpenCode |
|---|---|---|
| 0.3.x | >= 0.19.0 | >= 2.0.15, < 3 |
ocm 0.3.0 requires a local OpenCode 2 client (>= 2.0.15, same major), is configured through the OpenCode 2 cli.json plugins list, attaches through the repo-scoped Manager route /api/opencode-proxy/repos/:repoId, and moves sessions with OpenCode 2 session export/import. When the Manager does not serve the repo-scoped route (it answers 404), ocm and /ocm-move stop before attaching or pushing with:
OpenCode Manager at <url> is too old for ocm 0.3.0; upgrade the Manager to >= 0.19.0
Keep ocm 0.2.x for OpenCode Manager < 0.19.0 and OpenCode 1.x: install it pinned with
pnpm add -g @opencode-manager/ocm-cli@0.2, and pin the plugin entry to
@opencode-manager/ocm-cli@0.2 in your OpenCode 1.x TUI config so it does not resolve to
the latest release. ocm is published together with each OpenCode Manager release.
When the Manager rejects the stored token (401), returns another error, or cannot be
reached while ocm prepares the repo-scoped route, ocm and /ocm-move stop before
attaching with a message naming the cause.
Architecture Overview
| Component | Where it runs | Role |
|---|---|---|
ocm CLI | Laptop / local shell | Lists repos, attaches opencode against the Manager proxy, mirrors $PWD up/down |
| Manager backend | Manager server | Exposes repo metadata + token-protected OpenCode proxy + tarball mirror endpoints |
| Manager web UI | Manager server | Reads from the shared OpenCode server; sessions created via ocm appear normally |
There is no per-repo OpenCode process. All sessions share one OpenCode server on the Manager, with file-level isolation via the location directory (x-opencode-directory / location[directory]).
1. Install
The CLI is published as @opencode-manager/ocm-cli. There are four install paths.
Option A — install via OpenCode's plugin loader (recommended)
Add the package to your OpenCode 2 CLI config and OpenCode will fetch it on next start. The package exposes a ./tui entrypoint, and OpenCode resolves that entrypoint automatically from the package name. The package postinstall script self-installs a ~/.local/bin/ocm symlink for local plugin installs, so the ocm binary becomes available on your PATH automatically.
// ~/.config/opencode/cli.json
{
"$schema": "https://opencode.ai/v2/cli.json",
"plugins": ["@opencode-manager/ocm-cli"]
}
The next time OpenCode starts it will run bun install for the plugin. The installer stays quiet so it does not break the TUI layout; after the plugin loads, OpenCode shows a one-time toast confirming where ocm was linked.
If ~/.local/bin is not on your PATH, add this to your shell rc:
export PATH="$HOME/.local/bin:$PATH"
The @opencode-manager/ocm-cli package entry registers /ocm-move, a TUI command that keeps the local session and copies the active session to the Manager after pushing the current repo state. Run it from inside a local OpenCode session after ocm login and after the repo exists on the Manager (ocm push --create if needed).
Option B — global package manager install
If you don't use the OpenCode plugin loader, install globally:
pnpm add -g @opencode-manager/ocm-cli
This puts ocm on your PATH via the package manager's own bin shim. The ~/.local/bin symlink is skipped for global installs.
Option C — vendored, self-contained
The package needs no install step: dist/ocm.js bundles everything, and dist/tui.js bundles everything except @opencode/plugin/tui, @opentui/core, @opentui/solid, and solid-js, which OpenCode provides at runtime. ocm install copies the package into your OpenCode config directory, registers the plugin in cli.json, and links the binary onto your PATH:
pnpm dlx @opencode-manager/ocm-cli install
If ocm is already installed (Option A or B), run the same command directly:
ocm install
The command is idempotent: re-run it to upgrade after a new release, or run the wrapper script below to vendor a local build. It writes the plugin as ./plugin/ocm-cli/dist, a directory entry — OpenCode 2 loads tui.js from a directory entry and skips entries that name a file directly.
| Flag | Effect |
|---|---|
--dir <path> | Install into a different OpenCode config directory (default: $OPENCODE_CONFIG_DIR, else $XDG_CONFIG_HOME/opencode, else ~/.config/opencode) |
--no-link | Skip the ~/.local/bin/ocm symlink |
--force | Replace a non-symlink ocm already at the link path |
From this repository, build the package and run the wrapper script instead:
pnpm --filter @opencode-manager/ocm-cli build
./ocm-cli/scripts/install.sh
Option D — from this repository (dev)
pnpm install
pnpm --filter @opencode-manager/ocm-cli build
# postinstall creates ~/.local/bin/ocm symlink
2. Log in
Use the URL where your Manager web UI is accessible:
ocm login https://your-manager-url
# paste your Manager internal token when prompted
The token is stored in a platform-specific token store: the macOS Keychain (service opencode-manager, account = manager URL) on macOS, or ~/.config/opencode-manager/credentials.json at mode 0600 on Linux. On Linux the token is plaintext JSON protected only by file permissions. Run ocm status to see the active store. The manager URL itself is persisted to ~/.config/opencode-manager/state.json.
Windows is not supported: the CLI falls back to the same file store, but the 0600 mode is not enforced there and hidden token entry requires bash.
View, copy, or rotate your internal token from Settings → OpenCode Config → Server maintenance → Manager Internal Token (Settings cog in the sidebar). A token exists by default: the disclosure shows it behind an eye toggle with a copy button. Click the refresh icon to rotate it; a second click confirms, invalidating the previous token and marking an OpenCode server restart as pending.
3. Commands
ocm Attach to the Manager repo matching $PWD's OpenCode
project id, or (outside a git repo) fall back to the
last selected repo
ocm login <url> [token] Save manager URL + token (token via stdin if omitted)
ocm logout Forget saved token and state
ocm status Show current manager URL, repo, and whether token is set
ocm list List ready repos from the manager
ocm use <repoId|name> Attach to a specific repo and remember it as last
ocm push [repoId] [--force] [--create] [--yes] [--full] Mirror $PWD to the matching Manager repo (fast bundle/patch sync by default)
ocm pull [repoId] [--force] [--full] Mirror the matching Manager repo over $PWD (fast bundle/patch sync by default)
ocm install [--dir <path>] [--force] [--no-link] Vendor the CLI + TUI plugin into the OpenCode config dir
ocm --help Show this help
How bare ocm resolves the target
- If
$PWDis inside a git repo, compute its OpenCode project id (the same identity OpenCode uses: the normalized origin remote hash, else the cached<git-common-dir>/opencodeid, else the sorted first root commit). If exactly one ready Manager repo shares that project id, attach to it and remember it aslast. - If multiple Manager repos share it, fail with a hint to use
ocm use <repoId>, listing each match's id, kind (repo or worktree), branch, and path. - If
$PWDis inside a git repo but no Manager repo matches, launch localopencode; the last selected repo is not consulted. - Only when
$PWDis outside a git repo doesocmfall back to the previously used repo (last), and launch localopencodewhen there is nolast.
Attach command equivalent
Under the hood, ocm execs (OpenCode 2 connects with --server and reads the password from OPENCODE_PASSWORD):
OCM_REMOTE_MANAGER_URL=https://manager.example.com \
OCM_REMOTE_REPO_NAME=my-repo \
OPENCODE_PASSWORD=<manager-token> \
opencode --server https://manager.example.com/api/opencode-proxy/repos/42
OPENCODE_PASSWORD is set only on this local opencode client invocation that ocm execs, from the stored Manager token; you do not need to export it yourself. Never put OPENCODE_PASSWORD in the OpenCode Manager server environment: the Manager strips it from its own env because it would override the Manager-managed OpenCode server password.
The repo-scoped proxy mount sets the repo directory as the default request location
(the x-opencode-directory header, a location[directory] query, the session list
directory filter, and a location in a JSON body), so the child TUI can run from any
local working directory. It is convenience scoping, not isolation: routes addressed by
session ID are not restricted to the repo, and the Manager token grants full access to
the OpenCode API through the unscoped /api/opencode-proxy/* route as well. The child takes over the terminal
(stdio: inherit); closing the TUI exits ocm but leaves the Manager-side session
intact.
When the TUI plugin is installed, these internal child-process variables add a REMOTE <host> · <repo> indicator to the bottom of Manager-attached TUI windows. Local launches show no indicator.
Mirror commands
ocm push uses a fast git bundle + working-tree patch by default to sync $PWD to the matching Manager repo. Pass --full to use the legacy tarball mirror (skipping node_modules, dist, .next, .venv, __pycache__, .turbo, and anything matched by .gitignore). If the fast path fails, ocm prompts before reverting to the tarball mirror (and proceeds automatically when there is no TTY to prompt).
ocm pull uses a fast git bundle + working-tree patch by default to sync the matching Manager repo over $PWD. Pass --full to use the legacy tarball mirror. If the fast path fails, ocm prompts before reverting to the tarball mirror (and proceeds automatically when there is no TTY to prompt).
TUI /ocm-move
When the TUI plugin entry is installed, /ocm-move is available in local OpenCode sessions. It replaces the Manager repo's working tree with your local one (commits, staged, unstaged, and untracked files; gitignored files on the Manager are preserved). The Manager's current checkout is never switched: if it is on your branch the repo is replaced in place; otherwise your branch goes into a sibling worktree (<repo>-<branch>, registered as its own Manager repo), created on demand if it does not exist yet. When multiple Manager repos match, the one already on your branch is chosen; otherwise a select dialog lets you pick the destination. A confirmation dialog gates the move before any push, states where the state will land, and lists any server-side work (uncommitted changes or commits not present locally) that will be discarded there; the push itself is forced. It then exports the active session through the local OpenCode 2 server (session.export), rewrites local repo directories and file attachment URIs to the Manager repo directory, imports the session's messages through the Manager proxy (session.import), and sends a synthetic reminder (session.synthetic) to the remote session (best-effort, never fails the move). The local session is retained. On success you can choose to warp — exit the local TUI and attach to the moved session on the Manager immediately — or keep the local copy with the previous toast behavior.
--forceskips the dirty-working-tree check onpulland the safety bail onpush.--create(onpush) creates or reuses a Manager repo when no project match is found.--yesskips the interactive create confirmation.
4. Environment variables
The CLI's environment and token inputs:
| Variable | Description |
|---|---|
OPENCODE_MANAGER_URL | Manager base URL (e.g., https://manager.example.com). Not currently consumed by the CLI — use ocm login. |
OPENCODE_PASSWORD | Set by ocm on the local opencode child only (value = Manager token). Client-side only; never set it in the Manager server environment, which strips it. |
OCM_REMOTE_MANAGER_URL | Internal child-process context set by ocm attach; controls the remote TUI indicator. Not a login setting. |
OCM_REMOTE_REPO_NAME | Internal child-process context set by ocm attach; labels the remote TUI indicator. Not a login setting. |
OCM_TOKEN | Read-only override for the stored token; takes priority over the platform token store. ocm login never writes it, and ocm logout cannot remove it. Because the manager URL still comes from state.json, CI must run ocm login first, so this override does not yet avoid writing a token to disk. |
Token store entry under <manager url> | Token used for Bearer auth on Manager API calls and Basic auth on the OpenCode proxy. macOS Keychain (service opencode-manager) or ~/.config/opencode-manager/credentials.json (mode 0600) on Linux. |
5. Related endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/api/internal/opencode-workspaces | GET | List ready repos with directory + originUrl |
/api/internal/repos/:repoId/mirror/begin | POST | Begin a chunked tarball upload (pass repoId 0 with create to create the repo) |
/api/internal/repos/:repoId/mirror/parts/:uploadId/:index | PUT | Upload one tarball chunk |
/api/internal/repos/:repoId/mirror/commit | POST | Commit the uploaded chunks into the repo dir |
/api/internal/repos/:repoId/mirror/uploads/:uploadId | DELETE | Abort an upload and clean up staging |
/api/internal/repos/:repoId/mirror | GET | Stream a tarball of the repo dir (the --full pull) |
/api/internal/repos/:repoId/mirror/bundle | POST | Upload a git bundle (fast push) |
/api/internal/repos/:repoId/mirror/bundle | GET | Download a git bundle (fast pull) |
/api/internal/repos/:repoId/mirror/patch | GET | Snapshot branch, HEAD, and working-tree patch |
/api/internal/repos/:repoId/mirror/patch | POST | Apply a working-tree patch (fast push) |
/api/internal/repos/:repoId/mirror/head | GET | Read the server branch, HEAD, and dirty state |
/api/internal/repos/:repoId/mirror/contains/:sha | GET | Check whether a commit is contained in the server HEAD |
/api/internal/repos/:repoId/mirror/target | GET | Plan the repo/worktree a branch maps to |
/api/internal/repos/:repoId/mirror/target | POST | Ensure that target repo/worktree exists |
/api/opencode-proxy/* | ALL | Token-protected proxy from Manager to single OpenCode server |
/api/opencode-proxy/repos/:repoId/* | ALL | Token-protected proxy that defaults the request location to the repo directory |