diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 063b173..81513ae 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -2,7 +2,19 @@ name: Pull Request validation on: - - pull_request + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + +concurrency: + group: pr-validation-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read jobs: pre-commit: @@ -11,20 +23,23 @@ jobs: - name: Checkout code id: checkout uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Detect changed files + id: changed-files + uses: tj-actions/changed-files@v46 + with: + files: | + Dockerfile - - name: Set up Python - id: setup-python - uses: actions/setup-python@v5 + - name: Setup toolchain with mise + uses: jdx/mise-action@v2 - - name: Run pre-commit checks - id: pre-commit - uses: cloudposse/github-action-pre-commit@v4.0.0 + - name: Run prek hooks + run: prek run --all-files --show-diff-on-failure --color=always - name: Build Docker image if Dockerfile changed + if: steps.changed-files.outputs.any_changed == 'true' run: | - if git diff --name-only origin/${{ github.base_ref }} | grep -q '^Dockerfile$'; then - echo "Dockerfile changed — building image..." - docker build -t opencode-cli-pr:latest . - else - echo "Dockerfile not changed — skipping build." - fi + docker build -t opencode-cli-pr:latest . diff --git a/Dockerfile b/Dockerfile index fca8cdc..52f284d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,22 +62,27 @@ else fi EOF -chown -Rh bun:bun "$(echo ~bun)" - FOE COPY --chmod=0555 entrypoint.sh /entrypoint.sh ARG OPENCODE_VERSION=latest +ARG AZURE_FOUNDRY_PROVIDER_VERSION=0.2.0 +ARG ENGRAM_VERSION=v1.9.1 + ENV OPENCODE_CONFIG_DIR=/etc/opencode +ENV OPENCODE_EXPERIMENTAL=1 +ENV ENGRAM_DATA_DIR=/home/bun/.local/share/opencode/engram # hadolint ignore=DL3003,SC2164 RUN <<'FOE' export BUN_INSTALL=/usr/local/bun export PROVIDER_DIR=/usr/local/provider +export OPENCODE_PLUGINS_DIR="${OPENCODE_CONFIG_DIR}/plugins" -mkdir -p "${BUN_INSTALL}" "${OPENCODE_CONFIG_DIR}" "${PROVIDER_DIR}" +mkdir -p "${BUN_INSTALL}" "${OPENCODE_CONFIG_DIR}" "${OPENCODE_PLUGINS_DIR}" "${PROVIDER_DIR}" +chmod 0777 "${OPENCODE_CONFIG_DIR}" bun install -g "opencode-ai@${OPENCODE_VERSION}" || exit 1 @@ -86,7 +91,7 @@ bun install -g "opencode-ai@${OPENCODE_VERSION}" || exit 1 # pushd /tmp -bun install "github:ophiosdev/azure-foundry-provider" || exit 1 +bun install "github:ophiosdev/azure-foundry-provider#v${AZURE_FOUNDRY_PROVIDER_VERSION}" || exit 1 cd node_modules/azure-foundry-provider || exit 1 bun build --outdir=dist src/index.ts || exit 1 mv dist "${PROVIDER_DIR}/azure-foundry-provider" @@ -94,23 +99,30 @@ rm -rf /tmp/* popd || exit 1 +engram_version="${ENGRAM_VERSION#v}" +engram_archive="engram_${engram_version}_linux_amd64.tar.gz" +engram_url="https://github.com/Gentleman-Programming/engram/releases/download/${ENGRAM_VERSION}/${engram_archive}" +curl -fsSL "${engram_url}" | tar -C /usr/local/bin -xvzf - engram +curl -fsSL 'https://raw.githubusercontent.com/Gentleman-Programming/engram/refs/tags/${ENGRAM_VERSION}/plugin/opencode/engram.ts' -o "${OPENCODE_PLUGINS_DIR}/engram.ts" + + rm -rf /root/.bun +chown -Rh bun:bun "$(echo ~bun)" + FOE -USER bun +USER bun:bun RUN mise use -g --silent python@3.12.12 go@1.24 ripgrep uv # hadolint ignore=DL3045 COPY --chown=bun:bun git-export.py git-export.py -ENV XDG_CONFIG_HOME=/home/bun/.config - RUN <<'FOE' source /etc/bash.bashrc - skills_dir="${XDG_CONFIG_HOME}/opencode/skills" + skills_dir="${OPENCODE_CONFIG_DIR}/skills" mkdir -p "${skills_dir}" skill_name="humanizer" @@ -126,8 +138,49 @@ RUN <<'FOE' python git-export.py https://github.com/sickn33/antigravity-awesome-skills/skills/changelog-automation "${skills_dir}/${skill_name}" --force rm -f git-export.py + + cat >"${OPENCODE_CONFIG_DIR}/opencode.json" <<-'EOF' +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + "engram" + ], + "mcp": { + "engram": { + "command": [ + "engram", + "mcp", + "--tools=agent" + ], + "enabled": true, + "type": "local" + }, + "sequential-thinking": { + "type": "local", + "command": [ + "bun", + "x", + "@modelcontextprotocol/server-sequential-thinking" + ] + }, + "aleph": { + "type": "local", + "command": [ + "aleph", + "--enable-actions", + "--workspace-mode", + "any", + "--tool-docs", + "concise" + ] + } + } +} +EOF + FOE + # Set BASH_ENV so non-interactive bash shells (spawned by OpenCode CLI) source /etc/bash.bashrc # This ensures mise activation and PATH are available in shell commands ENV BASH_ENV=/etc/bash.bashrc diff --git a/README.md b/README.md index 189ceb0..e3206f3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - [Important Environment Variables](#important-environment-variables) - [Verifying Authentication](#verifying-authentication) - [Azure Foundry Provider](#azure-foundry-provider) +- [Runtime Behavior](#runtime-behavior) - [Working with OpenCode from the Container](#working-with-opencode-from-the-container) - [Basic Usage Pattern](#basic-usage-pattern) - [Volume Mounts Explained](#volume-mounts-explained) @@ -27,15 +28,16 @@ - [Build and Runtime Issues](#build-and-runtime-issues) A containerized OpenCode CLI environment with bundled runtime tooling, provider integrations, -and OpenCode skills. This image is designed to give you a ready-to-run `opencode` setup for -local project work while keeping your configuration and working directory mounted from the host. +and reusable OpenCode skills. The image ships with sensible defaults so you can start working in +local projects quickly while still mounting your own workspace, credentials, and persisted user +state from the host. ## Container Architecture -- **OpenCode entrypoint**: The container starts `opencode` directly through `entrypoint.sh` +- **Prepared startup flow**: The entrypoint loads the container shell environment before launching `opencode` - **Bun-based image**: Uses `oven/bun:latest` as the base image and installs `opencode-ai` -- **Mise-managed tooling**: Activates `mise` for both interactive and non-interactive shells -- **Curated additions**: Bundles Azure Foundry provider support, helper tooling, and reusable skills +- **Consistent shell tooling**: Uses shell bootstrap and `BASH_ENV` so command execution sees the same prepared environment +- **Curated additions**: Bundles Azure Foundry support, memory/context tooling, and reusable skills ## Building the Container Image @@ -53,11 +55,13 @@ make build ### Build Arguments -Customize the image version and local tag as needed: +Customize the main image component versions and local tag as needed: ```bash docker build \ --build-arg OPENCODE_VERSION=latest \ + --build-arg AZURE_FOUNDRY_PROVIDER_VERSION=0.2.0 \ + --build-arg ENGRAM_VERSION=v1.9.1 \ -t opencode-cli:dev . ``` @@ -67,10 +71,15 @@ With `make`: make build IMAGE=opencode-cli TAG=dev OPENCODE_VERSION=latest ``` +The Dockerfile also accepts `AZURE_FOUNDRY_PROVIDER_VERSION` and `ENGRAM_VERSION` if you want to +override the bundled provider or memory-tooling version during a direct `docker build`. + ## Authentication Setup -OpenCode configuration is stored under the container user's config directory. For practical use, -mount your host home directory so configuration persists across runs. +The image includes default OpenCode configuration and helper integrations so it works out of the +box, but you should still mount host directories for practical day-to-day use. In most setups, +mounting your host home directory lets OpenCode state, credentials, and memory-related data +persist across runs. Typical run pattern: @@ -84,6 +93,7 @@ docker run -it --rm \ This gives the container access to: - `/home/bun` for OpenCode config and any persisted credentials +- `/home/bun/.local/share/opencode` through the home mount for persisted local OpenCode and memory-related state - `/work` for the project you want OpenCode to read and modify ### Environment File Option @@ -113,6 +123,10 @@ Security tips: The exact variables depend on the provider configuration you use with OpenCode. This image does not hardcode credentials, so pass provider settings at runtime with `-e` or `--env-file`. +The image also includes default configuration under `/etc/opencode`, so most users only need to +provide environment variables and volume mounts rather than build up the entire runtime setup from +scratch. + Common patterns include: - OpenAI-compatible endpoints and API keys @@ -121,8 +135,8 @@ Common patterns include: ### Verifying Authentication -Once your configuration is mounted and any required variables are provided, verify the container -starts correctly: +Once your configuration is mounted and any required variables are provided, verify that the +container starts correctly: ```bash docker run -it --rm \ @@ -132,7 +146,8 @@ docker run -it --rm \ opencode-cli:dev --help ``` -If your setup is correct, OpenCode should start without configuration-related errors. +If your setup is correct, the CLI should start without basic configuration errors. Use a real +provider-backed command when you need to verify credentials end to end. ## Azure Foundry Provider @@ -143,6 +158,19 @@ This means the container is prepared for Azure Foundry-oriented OpenCode setups you to compile the provider on first run. Provider credentials and runtime configuration are still supplied by your OpenCode config and environment variables. +The image also bundles additional helper integrations for memory and large-context workflows, so +common local coding setups can start with a useful default baseline. + +## Runtime Behavior + +The container does a small amount of runtime preparation before launching OpenCode: + +- It loads the shell environment through the entrypoint before starting the CLI +- It uses `BASH_ENV` so non-interactive shell commands inherit the prepared toolchain environment +- It enables OpenCode experimental features by default with `OPENCODE_EXPERIMENTAL=1` + +This helps keep CLI sessions and tool-invoked shell commands consistent inside the container. + ## Working with OpenCode from the Container For normal usage, mount both your home directory and the current project directory. @@ -161,7 +189,7 @@ Replace `[OPENCODE_ARGS]` with the arguments supported by your installed `openco ### Volume Mounts Explained -- `-v $HOME:/home/bun`: Persists OpenCode config and other user-level state +- `-v $HOME:/home/bun`: Persists OpenCode config, credentials, and memory/history data stored under the OpenCode data directory - `-v ${PWD}:/work`: Mounts your current project into the container working directory - `--env-file .env`: Supplies provider credentials and runtime settings without exposing them in shell history - `--rm`: Removes the container after the process exits @@ -235,6 +263,11 @@ The image currently installs or bundles the following pieces during build: - `opencode-ai` - `mise` +- shell bootstrap for interactive and non-interactive command execution +- default OpenCode configuration under `/etc/opencode` +- `engram` for persisted memory-oriented workflows +- `aleph` tooling for large-context local analysis workflows +- local MCP-backed integrations available by default - `python` - `go` - `ripgrep` @@ -251,7 +284,7 @@ a GitHub repository using a treeless, sparse clone workflow. This repo publishes container images to GitHub Container Registry from version tags. -- `create-linked-release.yml` polls an upstream repository release stream and mirrors matching tags into this repo +- `create-linked-release.yml` checks the latest matching upstream release on a schedule and creates a local tag when a new one appears - `build-and-deploy.yml` runs on `v*` tags, validates semver, and publishes the image to `ghcr.io/ophiosdev/opencode-cli` - Published tags include semver variants, a commit SHA tag, and `latest` when enabled for the default branch @@ -262,7 +295,7 @@ This repo publishes container images to GitHub Container Registry from version t - `git-export.py`: Sparse GitHub directory export helper - `Makefile`: Convenience targets for local image build and cleanup - `.github/workflows/`: PR validation, release sync, and registry publishing workflows -- `mise.toml`: Local tool definitions for linting and validation utilities +- `.mise.toml`: Local tool definitions for linting and validation utilities ## Development and Validation @@ -270,13 +303,15 @@ The repo uses `pre-commit` for lightweight validation of committed files. Configured checks include: +- General file hygiene checks from `pre-commit-hooks` - YAML linting with `yamllint` - Dockerfile linting with `hadolint` - Markdown linting with `markdownlint-cli2` - GitHub Actions validation with `actionlint` - Spelling checks with `typos` -The pull request workflow also performs a Docker build smoke test when `Dockerfile` changes. +The pull request workflow always runs pre-commit checks and also performs a Docker build smoke +test when `Dockerfile` changes. ## Troubleshooting @@ -285,6 +320,7 @@ The pull request workflow also performs a Docker build smoke test when `Dockerfi - Repeated setup prompts: ensure `-v $HOME:/home/bun` is present so config persists - Missing credentials: confirm required provider variables are passed with `-e` or `--env-file` - Startup config failures: run `opencode-cli:dev --help` first to confirm the base container starts cleanly +- Memory/history not persisting: confirm your `/home/bun` mount is present so OpenCode state survives container recreation ### File Access Issues