openmods
Source-level mods for open-source harnesses. Your stock harness is never modified: the modded build lives under ~/.openmods and its launcher sits first on PATH, so `opencode` runs the modded build while it is on and the stock one otherwise.
Every command also answers openmods help <command>. This page and that text come from the same source.
Commands
| list | Mods in the registry: every harness each one supports, and the release it is for there. |
| info | A mod's details, and for each harness it supports: the release, the patches, and every file it touches. |
| install | Build a harness with these mods and switch it on. |
| uninstall | Remove mods and rebuild without them. |
| status | Which build your command runs right now, per harness. |
| on | Make your command run the modded build again. |
| off | Make your command run your stock harness, keeping the modded build. |
| update | Pull the registry and rebuild if anything you have installed changed. |
list
openmods list [--<harness>]
Pulls the registry first, so a mod published a minute ago is listed; offline, it lists what this machine fetched last, and says so. One line per mod, owner/name, then each harness it supports with the release its patches are for; an asterisk marks where you have it installed. Unpublished mods from ~/.openmods/local are listed too, marked (local).
--<harness>Only mods that support this harness, e.g. --codex.--jsonPrint the mods as JSON, one entry per mod and harness.$ openmods list $ openmods list --codex # only mods for Codex
info
openmods info <owner>/<mod> [--<harness>]
Read this before installing. The touched-files list comes from the patches themselves, not from the README.
Also lists, per harness, the mods it cannot be installed together with: ones that change the same lines of the release or lines right next to them, and ones its author marked as conflicting.
--<harness>Only this harness.--jsonMachine-readable output, including the touched files.$ openmods info shouryamaanjain/space-invaders $ openmods info shouryamaanjain/space-invaders --codex
install
openmods install <owner>/<mod> [<owner>/<mod> ...] [--<harness> ...] openmods install <path to a harness clone> [--name <mod>]
Which harness: name it with a flag, --opencode or --codex, or --harness <id>; several flags install on several harnesses. Without one, a mod that supports a single harness uses it, and a mod that supports several asks with a selector that lists only the harnesses it supports, and which of them you have. With no terminal to ask at, it prints the flags to choose from.
If you do not have the harness, it says so: the selector marks it as not installed, and a mod for that harness alone gets a notice. It then shows the harness's official installer (for OpenCode, curl -fsSL https://opencode.ai/v2/install | bash) and asks before running it. No leaves everything as it was; with no terminal it prints the command instead. Your stock harness is what `openmods off` switches back to.
Picks one release for all your mods on that harness: the one you are on if every mod has a version for it, else the newest release they all have a version for. It asks first when that moves your mods to another release, older or newer, and when a first mod is older than the harness you have (your own stays as it is). Mods that share no release are refused, naming the ones to uninstall if you want the new one; that choice is yours. Then it clones the harness once (blobless), checks out that release, applies each mod's version for it in order, builds with the exact toolchain that release pins, copies the build aside, and writes the launcher to ~/.openmods/bin. The first time, that folder is added to the front of PATH in your shell config, and if a harness installer later adds its own PATH line after it, the openmods line is moved back to the end so modded builds stay first.
Mods stack on one checkout. Two mods that change the same lines of the release, or lines right next to each other, cannot be combined: that is worked out from the patches before anything is built, and the install is refused with the mods and lines named. Your current build keeps running. A mod that still fails to apply, or a build that fails, also leaves the previous build in place, and it says which mod failed and what still runs.
Installing a mod that is already installed reinstalls it. A mod that was switched off comes back on. If a clone is running with `openmods dev`, it asks before switching to the modded build.
Given the path of a harness clone instead, it packs your commits on top of the release as a local mod, named after the branch unless --name says otherwise, and installs that: the way an author tries a mod exactly as users will run it.
Nothing of OpenMods goes into the build: it is the release plus your mods. The launcher starts it with the harness's own switches where modded and stock would clash (for Codex: --no-daemon, and -c to turn off its update notice and feedback upload).
--no-pathDo not edit your shell config to put ~/.openmods/bin on PATH.--yesAnswer yes to its questions (another release, ending dev mode, running a missing harness's official installer), for scripts; without a terminal and without --yes, it stops at a question and changes nothing. On several harnesses, every question comes before any build.--name <mod>With a clone: the local mod's name, instead of the branch name.--owner <you>With a clone: your GitHub handle, when git's github.user and the GitHub CLI do not say.--registry <dir>Use another registry checkout.$ openmods install shouryamaanjain/space-invaders # asks which harness $ openmods install shouryamaanjain/space-invaders --opencode $ openmods install . # in your OpenCode clone: try your commits as a mod
uninstall
openmods uninstall <owner>/<mod> [<owner>/<mod> ...] [--<harness> ...]
When the mod is installed on several harnesses and none is named with a flag, it asks which, listing only the ones it is installed on.
When the last mod for a harness is removed, the built binary and the patched commits go, the checkout is reset to the stock release, and the launcher stays, starting your stock harness. The checkout itself stays as a cache so the next install does not clone and install dependencies again; delete ~/.openmods/harnesses/<id> to reclaim the space.
$ openmods uninstall shouryamaanjain/space-invaders $ openmods uninstall shouryamaanjain/space-invaders --codex
status
openmods status
Shows the modded build (release plus mods, on or off), the stock binary it would fall back to, any mod that is installed but built out, a clone running with `openmods dev`, a mod removed from OpenMods, and any news the launcher found (an update waiting, or a release some mods hold back). Says so if this terminal would not run the launcher: ~/.openmods/bin is not on PATH, or another build comes first. Ends with where the mods list comes from and which OpenMods is running: its version, or the checkout it runs from. With a state file that cannot be read, it still says those.
--jsonThe state file as JSON, with any dev clones.$ openmods status
on
openmods on [harness | --<harness>]
Switches back from your stock harness to the modded build, after `openmods off`. Instant: the build was kept, nothing is rebuilt. With a harness, named as an id or a flag (codex, --codex or --harness codex), only that harness. To add a mod, use `openmods install`.
$ openmods on $ openmods on --codex # just Codex
off
openmods off [harness | --<harness>]
The launcher starts your stock harness instead of the modded build. Instant, and the build is kept for `openmods on`. With a harness, named as an id or a flag (codex, --codex or --harness codex), only that harness. To remove a mod, use `openmods uninstall`.
$ openmods off $ openmods off --codex # just Codex
update
openmods update [harness | --<harness>]
Moves you to the newest release that every mod you have on has a version for, using each mod's version for it. The registry's release check adds those versions when a mod still applies and typechecks on a new harness release. A rebuild happens only when that release, or a mod's patches for it, changed; otherwise it says the build is already up to date. When a newer release is out but some of your mods have no version for it yet, it names them. The launcher tells you when an update is waiting and runs this on a yes. It also updates OpenMods itself when a newer release of it is out (a vX.Y.Z tag of the registry; code merged since is never used), and says from which version to which; the only other thing that changes the program is running the installer again.
--forceRebuild even if nothing changed.$ openmods update $ openmods update --codex --force
For mod authors
| dev | Make the harness command run your clone from source, to try a mod as you write it. |
| pack | Turn your commits on top of a harness release into a mod folder. |
| check | Does a mod apply, typecheck, or build against a release? |
dev
openmods dev [harness-clone] [--name <mod>] openmods dev --stop [--<harness>]
Run it in your clone of a harness (or pass its path). From then on the harness command, e.g. opencode, runs that clone straight from source in whatever project you start it: edit, then start it again to see the change, with nothing to commit, pack or build. It installs the clone's dependencies once, with the toolchain its release pins, and reports a version like 2.0.18+my-mod-dev.
--stop switches back to your modded build, or to the stock harness when you have none. Installing, switching mods on or off, or an update that rebuilds also ends it, and says so.
--name <mod>The name in the version it reports; defaults to the branch name.--<harness>Which harness, when the clone's remote does not say, or which one --stop is for.--stopStop running the clone.$ cd ~/code/opencode && openmods dev # then just run opencode $ openmods dev --stop
pack
openmods pack <harness-checkout> --name <mod> --registry <your registry fork> [--owner <you>] [--note <what changed>] [--harness <id>] [--base <tag>] [--force] openmods pack <harness-checkout> --name <mod> --local
Run it against your clone of the harness. It finds the release tag below your commits, runs git format-patch, and writes the mod as owner/name: a shared mod.json and README under mods/<owner>/<name>, and the harness's own folder, mods/<owner>/<name>/<harness>, with support.json and the patches. Pack again from another harness's clone to add support for that harness to the same mod. The release becomes a version of the mod on that harness, with its patches in a folder named after the release tag. Packing at another release adds a version and keeps the others; packing at a release it already has needs --force and replaces only that version.
Each version records which update of the mod it holds. Pack numbers it: the next update when the changed lines differ from the latest update, the same one when they do not, so a rebase onto another release is not a new update. --note says what the update changed; users see it when they are offered the update.
It writes into the registry you name with --registry, your fork's checkout, and refuses to write into the copy in ~/.openmods that the CLI updates itself from.
Warns if a patch touches a lockfile, which is usually a build side effect and would make the mod conflict with every other mod that does the same.
--name <mod>Mod name: lowercase letters, digits and hyphens.--owner <you>Your GitHub handle. Defaults to git's github.user, then the GitHub CLI's login.--localWrite to ~/.openmods/local instead: installable now, published never.--harness <id>Which harness, when the clone's remote does not say.--base <tag>The release tag to diff against, when there are several below HEAD.--out <dir>Write somewhere else entirely.--forceReplace the version for this release if the mod already has one.--note <text>One line on what this update changed, shown to users.$ openmods pack . --name my-mod --registry ../openmods # from your harness clone, into your fork of the registry $ openmods pack . --name my-mod --registry ../openmods --force --note "fixes the resize crash" # an update
check
openmods check <mod-folder | owner/mod --<harness>> [--ref <tag>] [--typecheck | --build] [--json] openmods check --harness <id> --ref <tag> [--typecheck | --build] [--json]
Clones the harness into a workspace in your temp folder, reused between runs, checks out the release, and applies the mod's patches, as an install does. --typecheck then runs the harness's typecheck (minutes); --build runs its full build (long). CI runs the typecheck, on pull requests and against each new harness release; authors run the build themselves before opening a pull request.
With --harness and no mod, it builds the stock harness: what the manual harness build workflow runs to prove a harness definition, or to confirm it after a release changed the build recipe. Exits non-zero on any failure.
--ref <tag>Release to test against; defaults to the one the version is for.--at <tag>Check the mod's version for this release instead of its newest.--typecheckRun the harness's typecheck after applying.--buildRun the harness's full build after applying.--harness <id>Check the stock harness with no mod.--workspace <dir>Reuse this checkout instead of a temporary one.--jsonResult as JSON, for CI.$ openmods check shouryamaanjain/space-invaders --opencode --build $ openmods check mods/shouryamaanjain/space-invaders/codex --at rust-v0.156.1 --typecheck # an older version $ openmods check --harness codex --ref rust-v0.156.0 --build
Options
--registry <dir>Registry checkout to use. Default: the checkout the CLI runs from, else ~/.openmods/registry, cloned from github.com/shouryamaanjain/openmods on first use.--jsonMachine-readable output, where a command supports it.--helpThis text. `openmods help <command>` shows one command.Environment
OPENMODS_HOMEWhere everything lives. Default ~/.openmods.OPENMODS_REGISTRYA registry checkout, same as --registry; or the git URL ~/.openmods/registry is cloned from on first use.OPENMODS_NO_CHECK=1The launcher does not look for news in the background. `openmods update` still updates.OPENMODS_REVOKED_URLWhere to fetch the list of mods removed from OpenMods. Default: revoked.json of the registry openmods cloned, else the official one. Empty: no fetch; the registry checkout's own list is used. A list named here is fetched by every command, the ones that pull the registry included.CARGO_BUILD_JOBSRust build jobs at once. Default: one per 2.5 GB of memory, at most one per CPU.NO_COLOR, CINo colour; with CI set, build output streams as it is instead of the progress view.BUN_INSTALL_CACHE_DIR, BUN_RUNTIME_TRANSPILER_CACHE_PATHWhere Bun's caches go, instead of ~/.openmods/cache.ZIG_GLOBAL_CACHE_DIRWhere Zig's build cache goes, instead of ~/.openmods/cache/zig.Files
~/.openmods/bin/openmodsThe openmods command itself, which runs the CLI from ~/.openmods/cli on its own Bun.~/.openmods/cliThe program itself: the newest release of OpenMods (a vX.Y.Z tag of the registry) when it was installed or last updated. Only the installer and `openmods update` replace it, and update says from which version to which.~/.openmods/registryThe registry: the mods list, their patches, the build recipes. `list`, `info`, `install` and `update` pull it; pulling never changes the program.~/.openmods/bin/<binary>The launcher for a harness: the modded build while on, your stock one while off or before you install a mod. It stays put so a shell that remembers commands (bash, zsh) keeps finding it.~/.openmods/launchers.jsonWhen each launcher first appeared, so a terminal opened before that is told to run `hash -r`.~/.openmods/harnesses/<id>/srcThe harness checkout, patched, kept as a cache.~/.openmods/harnesses/<id>/buildsThe current modded build, kept until a newer one succeeds; the one before it, and any build a running session was started from, stay until they are no longer needed.~/.openmods/toolchains/The Bun the CLI runs on, and the exact toolchain each harness release pins.~/.openmods/cache/Bun's package and transpiler caches. Safe to delete.~/.openmods/logs/The output of recent builds, one file each, for when a build fails.~/.openmods/timings.jsonHow long each build step took here, for the time left and the update question.~/.openmods/local/<owner>/<mod>Your own unpublished mods, laid out like the registry.~/.openmods/updates/<id>What the launcher's last look found, shown on the next start; <id>.seen lists what it has already said.~/.openmods/revoked.jsonThe last list of mods removed from OpenMods, fetched by the commands that do not pull the registry (the others get it with the pull, unless it fails or OPENMODS_REVOKED_URL names another list). A build with one of them no longer runs.~/.openmods/busy/One marker per running openmods command, so the launcher's background look does not pull the registry while a command reads from it.~/.openmods/dev.jsonWhich clone `openmods dev` runs, per harness.~/.openmods/state.jsonWhat is installed, and on, per harness.