# Copyright 2026 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="The open source AI coding agent" HOMEPAGE="https://opencode.ai https://github.com/anomalyco/opencode" inherit git-r3 EGIT_REPO_URI="https://github.com/anomalyco/opencode.git" BUN_VERSION="1.3.14" BUN_URI="https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}" NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" LICENSE="MIT" SLOT="0" KEYWORDS="~amd64" IUSE="" # git-r3 fetches happen outside the sandboxed phases, but curl (bun download) # and `bun install` both need network access during src_unpack, which Portage # blocks by default under FEATURES=network-sandbox. This is REQUIRED or the # build will silently produce a broken/stub binary instead of failing loudly. RESTRICT="network-sandbox" RDEPEND="sys-apps/ripgrep !dev-util/opencode-bin" BDEPEND=" app-arch/unzip net-misc/curl !dev-util/opencode-bin " S="${WORKDIR}/${P}" src_unpack() { git-r3_src_unpack local arch case ${ARCH} in amd64) arch="x64" ;; arm64) arch="arm64" ;; *) die "Unsupported architecture: ${ARCH}" ;; esac local bun_archive="bun-linux-${arch}.zip" local bun_dir="${T}/bun" mkdir -p "${bun_dir}" || die einfo "Downloading bun ${BUN_VERSION} for building..." curl -fL -o "${T}/${bun_archive}" "${BUN_URI}/${bun_archive}" || die "Failed to download bun" cd "${bun_dir}" || die unzip -q "${T}/${bun_archive}" || die "Failed to extract bun" mv "${bun_dir}/bun-linux-${arch}/bun" "${bun_dir}/bun" || die "Failed to move bun binary" chmod +x "${bun_dir}/bun" || die export PATH="${bun_dir}:${PATH}" # Sanity: make sure we're actually picking up the bun we just fetched, # not some stale one already on PATH. local bun_bin bun_bin="$(command -v bun)" || die "bun not found on PATH after setup" [[ "${bun_bin}" == "${bun_dir}/bun" ]] || die "Wrong bun resolved: ${bun_bin}" cd "${S}" || die git config --global --add safe.directory "${S}" || die # packages/console, packages/stats, and packages/enterprise are the # web dashboard workspaces - unrelated to the opencode CLI we're # building. Their package.json pins @solidjs/start to a specific # ephemeral pkg.pr.new PR-preview build. That preview build has since # been rotated out / expired and now 403s, which makes a full # workspace-root `bun install` fail outright (bun does exit non-zero # for this, but be aware: if this check is ever removed and the # failure goes uncaught, the compile step below can still emit a # valid-looking-but-unbundled executable). Drop these workspaces # entirely so install never needs to resolve that dependency. rm -rf packages/console packages/stats packages/enterprise || die rm -f bun.lock || die cat > "${S}/packages/opencode/models.json" <<- 'MODELS_EOF' || die { "opencode": { "id": "opencode", "env": ["OPENCODE_API_KEY"], "npm": "@ai-sdk/openai-compatible", "api": "https://opencode.ai/zen/v1", "name": "OpenCode Zen", "doc": "https://opencode.ai/docs/zen", "models": { "big-pickle": { "id": "big-pickle", "name": "Big Pickle", "family": "big-pickle", "attachment": false, "reasoning": true, "tool_call": true, "interleaved": { "field": "reasoning_content" }, "structured_output": true, "temperature": true, "limit": { "context": 200000, "output": 128000 }, "cost": { "input": 0, "output": 0, "cache_read": 0, "cache_write": 0 } }, "deepseek-v4-flash-free": { "id": "deepseek-v4-flash-free", "name": "DeepSeek V4 Flash Free", "family": "deepseek-flash-free", "attachment": false, "reasoning": true, "tool_call": true, "interleaved": { "field": "reasoning_content" }, "structured_output": true, "temperature": true, "knowledge": "2025-05", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "limit": { "context": 1000000, "output": 384000 }, "cost": { "input": 0, "output": 0, "cache_read": 0 } }, "ring-2.6-1t-free": { "id": "ring-2.6-1t-free", "name": "Ring 2.6 1T Free", "family": "ring-1t-free", "attachment": false, "reasoning": true, "tool_call": true, "interleaved": { "field": "reasoning_content" }, "temperature": true, "knowledge": "2025-06", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "limit": { "context": 262000, "output": 66000 }, "cost": { "input": 0, "output": 0 } } } } } MODELS_EOF export MODELS_DEV_API_JSON="${S}/packages/opencode/models.json" einfo "Installing npm dependencies from ${NPM_REGISTRY}..." bun install --ignore-scripts --registry "${NPM_REGISTRY}" \ || die "bun install failed - check network access (RESTRICT=network-sandbox) and registry reachability" einfo "Building opencode..." cd packages/opencode || die # NOTE: do NOT pass --skip-install here. That flag assumes a prior CI step # already ran `bun install --os="*" --cpu="*"` for @opentui/core, # @parcel/watcher, and @ff-labs/fff-bun across every target platform. # Skipping it without that prior step can leave native bindings the TUI # and parser worker need missing/wrong, which makes `Bun.build({compile})` # silently emit an unbundled (bare bun) executable instead of failing. bun run script/build.ts --single --skip-embed-web-ui \ || die "Build failed" local out_bin="${S}/packages/opencode/dist/opencode-linux-${arch}/bin/opencode" [ -x "${out_bin}" ] || die "Expected compiled binary not found at ${out_bin}" # Guard against getting a bare bun passthrough binary instead of the real # bundled app. `--version`/`--help` alone don't catch this reliably since # bare bun answers those too; the real tell is bun's top-level usage # banner, which only appears on a bare/no-arg invocation. local bare_output bare_output="$(printf '' | timeout 5 "${out_bin}" 2>&1 | head -n1)" case "${bare_output}" in *"fast JavaScript runtime"*) die "Compiled binary is a bare bun passthrough, not opencode - build did not bundle the entrypoint correctly" ;; esac einfo "Generating JSON schema..." bun run script/schema.ts schema.json || die "Schema generation failed" } src_configure() { :; } src_compile() { :; } src_install() { local arch case ${ARCH} in amd64) arch="x64" ;; arm64) arch="arm64" ;; *) die "Unsupported architecture: ${ARCH}" ;; esac local binary_dir="${S}/packages/opencode/dist/opencode-linux-${arch}" newbin "${binary_dir}/bin/opencode" "opencode.real" # CRITICAL: Bun's --compile feature appends the bundled JS payload as # custom data past the normal ELF sections - it is not standard debug # info or unneeded symbols. Portage's default auto-strip (FEATURES=strip) # does not understand this format and will truncate/corrupt that # payload, silently turning the installed binary back into bare bun # (it will run, but behave as if nothing was ever bundled into it). # dostrip -x exempts this specific file from stripping. dostrip -x /usr/bin/opencode.real insinto /usr/share/opencode doins "${S}/packages/opencode/schema.json" cat > "${ED}/usr/bin/opencode" <<-EOF #!/bin/sh export OPENCODE_DISABLE_MODELS_FETCH=1 export OPENCODE_DISABLE_AUTOUPDATE=1 exec /usr/bin/opencode.real "\$@" EOF chmod +x "${ED}/usr/bin/opencode" || die }