opencode — rmk40/opencode fork (the aai channel)
The current focus of this fork is to dramatically improve the mobile and web experience of opencode. Long-running coding sessions deserve a UI that's pleasant to drive from a phone — iOS PWA support, sane mobile chrome, fast tab switching, calm progress indicators, mobile-first input ergonomics. That is where the active development is going for the foreseeable future.
The fork's intent is to mirror upstream OpenCode, not diverge from it:
- The core stays compatible. No API breaking changes. No schema
breaking changes. Sessions, plugins, and configurations that work
against upstream
opencodework against this fork. - The fork carries patches for bugs and key issues that haven't landed upstream yet, plus the mobile/web UI work that motivated the fork in the first place.
- Some of this work is intended to flow back upstream. Where a fix or feature is generally useful, the goal is to land it in upstream OpenCode rather than keep it fork-only forever. The fork exists so the work can ship and evolve without waiting on upstream's review cadence.
Contributions welcome under that criteria — bug fixes, mobile/web
UX improvements, and patches that maintain upstream compatibility. Open
an issue or PR against the actualyze
branch.
- Repository:
rmk40/opencode - Release branch:
actualyze - Release channel:
aai - Tag format:
vX.Y.Z-aai.N(e.g.v1.14.24-aai.6) - npm package:
@rmk40/opencode - npm registry:
https://npm.pkg.github.com(GitHub Packages) - GitHub releases:
rmk40/opencode/releases(always--prerelease) - Distribution scope: CLI only. No Homebrew, AUR, Docker, desktop bundles, signing, or notarization.
GitHub Packages requires a GitHub personal access token with the
read:packages scope, even for public packages. Set up ~/.npmrc once:
@rmk40:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKENThen install globally with whichever package manager you use:
# npm
npm install -g @rmk40/opencode@aai
# bun
bun install -g @rmk40/opencode@aai
# pnpm
pnpm install -g @rmk40/opencode@aaiOne-off install without editing ~/.npmrc:
NODE_AUTH_TOKEN=YOUR_GITHUB_TOKEN \
npm install -g @rmk40/opencode@aai \
--registry=https://npm.pkg.github.comThe aai dist-tag always points at the newest published fork version.
Every release publishes per-platform archives plus SHA256SUMS to GitHub
Releases. Pick your platform from the
releases page
or use gh:
gh release download --repo rmk40/opencode \
--pattern 'opencode-*' --pattern 'SHA256SUMS' \
--dir ./opencode-release
( cd ./opencode-release && sha256sum -c SHA256SUMS )Linux archives are .tar.gz, macOS and Windows archives are .zip. The
contents are the binary plus its support files; extract somewhere on your
PATH.
Available targets:
| Platform | Architectures |
|---|---|
| macOS | arm64, x64, x64-baseline |
| Linux (glibc) | x64, x64-baseline, arm64 |
| Linux (musl) | x64, x64-baseline, arm64 |
| Windows | x64, x64-baseline, arm64 |
The -baseline variants target older CPUs (no AVX2). The -musl
variants are for Alpine and other musl-libc distros.
The fork build of opencode knows it came from this fork — the binary
has the fork repo, npm package, and registry baked in at build time, so
opencode upgrade queries @rmk40/opencode@aai on
https://npm.pkg.github.com instead of upstream npmjs.
opencode upgradeIt picks the upgrade method that matches how you installed it: npm,
bun, or pnpm (for global installs), or the GitHub releases fallback.
Upstream-only paths (brew, scoop, choco, the upstream curl
installer) are explicitly disabled in fork builds — they would resolve
to the upstream package and silently downgrade you off the fork channel.
If you see an auth error, your ~/.npmrc token has expired or lacks
read:packages; the upgrade command will tell you what to fix.
Run opencode web to start the local server and open the browser UI.
The root route (/) opens the native dashboard, and the legacy home
screen remains available at /home.
By default, server commands bind to 127.0.0.1. Set
OPENCODE_SERVER_PASSWORD before binding to non-loopback hosts such as
0.0.0.0 or enabling mDNS. Pass --allow-insecure-no-auth (or set
server.allowInsecureNoAuth in opencode.json) only when you
intentionally accept unauthenticated network access.
If embedded UI assets are missing or disabled with
OPENCODE_DISABLE_EMBEDDED_WEB_UI, the server fails closed with a
local 503 page; it does not proxy to the upstream hosted UI.
# Show the latest fork release
gh release view --repo rmk40/opencode
# Resolve the aai dist-tag against GitHub Packages
NODE_AUTH_TOKEN=YOUR_GITHUB_TOKEN \
npm view @rmk40/opencode@aai version \
--registry=https://npm.pkg.github.comBoth should report the same X.Y.Z-aai.N version. If they don't, a
publish failed mid-flight; see Failure modes.
This fork tracks upstream dev and adds a long-lived release branch
plus a self-contained release pipeline. Nothing about the upstream
release infrastructure is reused.
flowchart LR
Upstream["anomalyco/opencode (dev)"] -->|merge as needed| Actualyze["rmk40/opencode (actualyze)"]
Actualyze -->|tag vX.Y.Z-aai.N| CI["fork-release-artifacts.yml"]
CI --> Release["GitHub Release (prerelease)"]
CI --> Pkgs["@rmk40/opencode (GitHub Packages)"]
Release --> Users["users via gh release download"]
Pkgs --> Users
actualyzeis the long-lived release branch. Merge upstreamdevinto it; never recreate or reset it.- Tags drive releases. Push
vX.Y.Z-aai.Nfrom a commit reachable fromactualyzeand CI does the rest. workflow_dispatchis a manual fallback for testing CI without cutting a real release. Manual runs always produce drafts; tag pushes always produce published prereleases.
X.Y.Z is the upstream version this build is based on. aai.N is the
fork's monotonic suffix — once aai.N is published, it is never reused.
flowchart LR
subgraph InScope["In scope"]
direction TB
I1["CLI binaries"]
I2["Linux x64 / arm64<br/>glibc and musl"]
I3["macOS x64 and arm64"]
I4["Windows x64 and arm64"]
I5["Baseline variants<br/>for older CPUs"]
I6["GitHub Releases<br/>prerelease only"]
I7["GitHub Packages npm<br/>@rmk40 scope"]
I8["Captured models.dev<br/>api.json snapshot"]
end
subgraph OutOfScope["Out of scope"]
direction TB
O1["npmjs<br/>registry.npmjs.org"]
O2["Homebrew, AUR,<br/>Scoop, Chocolatey"]
O3["Docker / GHCR images"]
O4["Desktop app<br/>Tauri / Electron"]
O5["Code signing<br/>and notarization"]
O6["Updater channel<br/>promotion"]
end
If you need anything in the right column, that is a separate change. Do not bolt it onto the fork pipeline ad-hoc.
The pipeline is implemented by three files:
.github/workflows/fork-release-artifacts.yml— the workflow.script/fork-release-artifacts.sh— all the actual logic.- The root
package.jsonscriptrelease:fork— the canonical entrypoint.
Always invoke the helper through the package script. Direct calls to
script/fork-release-artifacts.sh or packages/opencode/script/build.ts
are unsupported.
bun run release:fork -- <subcommand> [...args]Subcommands:
| Subcommand | What it does |
|---|---|
validate |
Validates context, derives version/channel, writes job outputs. |
build |
Runs the upstream CLI build, captures models.dev, validates artifacts. |
package |
Stages release archives (.tar.gz / .zip) and writes SHA256SUMS. |
release |
Creates the GitHub release and uploads assets (binaries first, sums last). |
npm-package |
Stages wrapper + per-target @rmk40/* packages and tarballs locally. |
npm-publish |
Preflights, publishes, verifies; --ignore-scripts --tag aai. |
self-test |
Offline regression suite. Touches no network, no registry. |
flowchart LR
Tag["Push tag<br/>vX.Y.Z-aai.N"] --> Build["build-cli<br/>(release:fork build)"]
Manual["workflow_dispatch"] --> Build
Build -->|opencode-cli-dist.tar<br/>+ metadata| Release["release<br/>(release:fork release)"]
Build --> Publish["publish-npm<br/>(release:fork npm-publish)"]
Release --> GH["GitHub Release<br/>(prerelease)"]
Publish --> GP["@rmk40/opencode@aai<br/>GitHub Packages"]
build-cliruns the upstreampackages/opencode/script/build.ts, smoke-tests the runner-native binaries, runs Alpine musl smoke tests insidealpine:3.20(after installinglibstdc++andlibgcc), and hands offopencode-cli-dist.tarto the downstream jobs. The dist tar preserves executable bits across theactions/upload-artifactboundary, which raw directory uploads do not.releaserequirescontents: write, runs only on a tag push or wheninputs.create_release == true, and publishes the release as prerelease. Tag pushes target the triggering tag; manual runs pass--draft --target $GITHUB_SHA.publish-npmrequirespackages: write, runs only on a tag push or wheninputs.publish_npm == true, and depends on bothbuild-cliandreleaseso a failed release cannot leave npm published without a corresponding GitHub release.
The whole workflow runs on Node.js 24
(FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" plus explicit
actions/setup-node@v4 { node-version: "24" }).
git checkout actualyze
# ...do work, merge upstream as needed...
git push fork actualyze
git tag v1.14.24-aai.5
git push fork v1.14.24-aai.5CI then:
- Builds and smoke-tests every target.
- Publishes the GitHub release as a prerelease.
- Publishes
@rmk40/[email protected]and per-target packages to GitHub Packages, applying dist-tagaai.
Verify after CI is green:
gh release view v1.14.24-aai.5 --repo rmk40/opencode \
--json tagName,isDraft,isPrerelease,assets
NODE_AUTH_TOKEN=YOUR_GITHUB_TOKEN \
npm view @rmk40/opencode@aai version \
--registry=https://npm.pkg.github.comManual fallback (only when needed, e.g. retesting CI behavior without cutting a real release):
gh workflow run fork-release-artifacts.yml \
--repo rmk40/opencode \
--ref actualyze \
-f upstream_version=1.14.24 \
-f suffix=aai.5 \
-f create_release=true \
-f publish_npm=truebash -n script/fork-release-artifacts.sh
bun run release:fork -- self-test
ruby -e 'require "yaml"; YAML.load_file(".github/workflows/fork-release-artifacts.yml"); puts "ok"'
bun -e "JSON.parse(await Bun.file('package.json').text()); console.log('ok')"
git diff --checkFor local debugging of package / release / npm-publish without
running CI:
export FORK_RELEASE_SKIP_CONTEXT_CHECK=1
export FORK_RELEASE_ALLOW_LOCAL_DIST=1These overrides are intended for local debugging and must not appear in CI runs.
The fork build bakes three install-identity values into the binary at
build time so opencode upgrade always queries the fork:
OPENCODE_REPO=rmk40/opencode
OPENCODE_NPM_PACKAGE=@rmk40/opencode
OPENCODE_NPM_REGISTRY=https://npm.pkg.github.com
script/fork-release-artifacts.sh build exports these and aborts if
any is empty. They flow into the binary via
packages/opencode/src/installation/version.ts and are consumed by
packages/opencode/src/installation/index.ts. The release channel is
always OPENCODE_CHANNEL=aai.
The build also unsets OPENCODE_BUMP, OPENCODE_RELEASE, and GH_REPO
before invoking build.ts to prevent upstream version-bump or publish
side effects from leaking into a fork build.
| Package | Role |
|---|---|
@rmk40/opencode |
Wrapper. Resolves the right per-target package at install. |
@rmk40/opencode-darwin-arm64 |
Per-target binary. |
@rmk40/opencode-darwin-x64 |
Per-target binary. |
@rmk40/opencode-linux-x64 |
Per-target binary. |
@rmk40/opencode-linux-arm64 |
Per-target binary. |
@rmk40/opencode-linux-x64-musl |
Per-target binary (Alpine). |
@rmk40/opencode-windows-x64 |
Per-target binary. |
| ... | One per target listed under Install. |
Wrapper metadata:
bin: { opencode: "./bin/opencode" }scripts.postinstall: "node ./postinstall.mjs"optionalDependenciesis set to every scoped per-target package at the same version. The wrapper bin andpostinstall.mjsresolve the right one dynamically by readingoptionalDependenciesrather than hardcoding the@rmk40scope.publishConfig.registry: https://npm.pkg.github.comrepository.url: git+https://github.com/rmk40/opencode.git
npm-publish always:
- Stages packages from the dist tar (never the working tree).
- Verifies the requested version matches the metadata.
- Preflights every
@rmk40/*@<version>againsthttps://npm.pkg.github.com. If any version already exists, fails before publishing anything. - Publishes per-target packages first, then
@rmk40/opencode, all with--ignore-scripts --tag aai. - Verifies post-publish that
@rmk40/opencode@aairesolves to the new version.
The pipeline is designed to fail closed.
| Failure | Recovery |
|---|---|
validate rejects the tag |
Delete the tag, fix the input (X.Y.Z-aai.N, no leading zeros), re-tag. |
Tag commit not reachable from actualyze |
Merge or rebase onto actualyze, re-tag from the new commit. |
build-cli fails |
Nothing was uploaded. Fix the build, push, retag. |
release fails before publishing (manual draft) |
gh release delete ... --cleanup-tag, then rerun. |
release fails after publishing |
Don't retry the same version. Bump suffix, retag. |
publish-npm fails after some packages are pushed |
Don't retry the same version (immutable). Bump suffix, retag. |
Never use the GitHub UI's "Re-run failed jobs" button on a partial publish — always start a fresh tag.
For day-to-day hacking on this repo you don't want to install a release; you want to run the CLI straight out of the working tree. The upstream tooling for that already works on the fork — the fork only changes release identity, not the dev loop.
bun installBun 1.3+ is required (packageManager pins [email protected]). The root
postinstall runs packages/opencode/script/fix-node-pty.ts to wire
up node-pty for your platform; using npm/pnpm will silently break
workspace links and patches.
bun dev # TUI in packages/opencode (the default cwd)
bun dev . # TUI in the repo root
bun dev /some/project # TUI against another directory
bun dev --help # full CLI surface, same as the built binary
bun dev serve # headless API server on :4096
bun dev serve --port 8080
bun dev web # server + open web UIbun dev is just bun run --cwd packages/opencode --conditions=browser src/index.ts.
Edits to TypeScript source are picked up on the next run with no
separate build step. The --conditions=browser flag is required so
the conditional imports in packages/opencode/package.json (#db,
#pty, #hono) resolve to the right adapters.
| Goal | Command |
|---|---|
| Web UI dev server | bun run --cwd packages/app dev (server must be up) |
| Desktop (Tauri) shell | bun run --cwd packages/desktop tauri dev |
| Console app dev server | bun dev:console |
| Storybook | bun dev:storybook |
When you need to test the actual compiled artifact (Bun.build
output) — verifying script/build.ts changes, validating the binary
that the release pipeline would ship, or just running outside Bun's
runner — build a single-target executable:
./packages/opencode/script/build.ts --single
./packages/opencode/dist/opencode-<platform>-<arch>/bin/opencode --helpThis is not the fork-pipeline build. It does not bake the fork
identity defines (OPENCODE_REPO, OPENCODE_NPM_PACKAGE,
OPENCODE_NPM_REGISTRY), so opencode upgrade from this binary
behaves like an upstream build — which for local dev is fine, you
don't want it auto-upgrading anyway.
If you specifically need to test fork upgrade behavior locally
(opencode upgrade querying @rmk40/opencode@aai), invoke the
release helper in local-debug mode:
export FORK_RELEASE_SKIP_CONTEXT_CHECK=1
bun run release:fork -- build \
--upstream-version 1.14.24 \
--suffix aai.0 \
--version 1.14.24-aai.0aai.0 is intentionally invalid for publishing (the publish regex
requires [1-9][0-9]*), so it's a safe sentinel meaning "local
debug build, never going to ship". Real aai.N suffixes belong to
CI tags.
# Server with debugger attached, separate process
bun run --inspect=ws://localhost:6499/ --cwd packages/opencode \
./src/index.ts serve --port 4096
# Attach the TUI client in another terminal
opencode attach http://localhost:4096
# Or run the TUI under the debugger directly
bun run --inspect=ws://localhost:6499/ --cwd packages/opencode \
--conditions=browser ./src/index.tsbun dev runs the server in a worker thread, which can prevent
breakpoints from binding. Use bun dev spawn to force a separate
process, or run server and TUI separately as shown above.
- Skipping
bun install— thepostinstallstep is required. - Using
npm/pnpminstead ofbun— breaks workspace and patches. - Calling
bun run packages/opencode/src/index.tswithout--conditions=browser— fails to resolve#pty/#hono/#db. - A stale
packages/opencode/dist/shadowing changes if you invoke the dist binary instead ofbun dev. Runrm -rf packages/opencode/distif in doubt. - Setting
OPENCODE_REPO/OPENCODE_NPM_PACKAGE/OPENCODE_NPM_REGISTRYfor local dev — those are only consumed by the fork release build. They do nothing forbun dev.
See CONTRIBUTING.md for the upstream-flavored
contributor docs (provider additions, PR rules, deeper debugger setup).
docs/fork-release-pipeline.md— detailed operator/agent reference for every subcommand and job.release-artifact-pipeline-plan.md— long-form design rationale.AGENTS.md— short operating notes for agents working in this repo.
The fork tracks upstream and adds release plumbing only. Contributions
that belong upstream should go upstream; the fork merges them in via
actualyze. Contributions that are fork-specific (release pipeline,
fork build identity, fork operational docs) belong on actualyze.
Standards:
- Every change to the release pipeline must keep
bun run release:fork -- self-testgreen. - Every public API or CLI behavior change should update
docs/fork-release-pipeline.mdin the same change-set. - Don't reuse a published
aai.Nsuffix. Ever.
Upstream project: anomalyco/opencode.
The fork exists to ship CLI-only release artifacts on a separate
channel; it is not a hostile fork and tracks upstream dev continuously.
