204 lines
7.1 KiB
Markdown
204 lines
7.1 KiB
Markdown
# opencode Forgejo Action
|
|
|
|
A Forgejo Action that runs [opencode](https://opencode.ai) against a Nomyo (or any OpenAI-compatible) backend, triggered by comments on issues and merge requests.
|
|
|
|
Mention `/opencode` or `/oc` in a comment and opencode reads the thread, executes the requested task, and replies. For "fix" tasks it opens a merge request; for MR comments it commits to the same MR.
|
|
|
|
## Features
|
|
|
|
### Explain an issue
|
|
|
|
```
|
|
/opencode explain this issue
|
|
```
|
|
|
|
opencode reads the full thread and replies with an explanation.
|
|
|
|
### Fix an issue
|
|
|
|
```
|
|
/opencode fix this
|
|
```
|
|
|
|
opencode creates a branch, implements the change, and opens a merge request.
|
|
|
|
### Update an open MR
|
|
|
|
```
|
|
Delete the attachment from S3 when the note is removed /oc
|
|
```
|
|
|
|
opencode commits to the same MR branch.
|
|
|
|
### Review specific code lines
|
|
|
|
Comment directly on lines in the MR's "Files" tab — opencode receives the file path, line numbers, and diff hunk as context.
|
|
|
|
```
|
|
/oc add error handling here
|
|
```
|
|
|
|
## Installation
|
|
|
|
### Prerequisites
|
|
|
|
- A Forgejo instance with Actions enabled.
|
|
- A runner that can run Docker containers (the example below uses `docker-amd64` with `node:lts-bookworm`).
|
|
- A Forgejo PAT (see scopes below) and a Nomyo API key.
|
|
|
|
### Forgejo PAT scopes
|
|
|
|
Generate the token at *Settings → Applications → Manage Access Tokens* with these scopes:
|
|
|
|
| Scope | Used for |
|
|
|---|---|
|
|
| `read:repository` | Clone, fetch repo info |
|
|
| `write:repository` | Push commits and branches |
|
|
| `read:issue` | Read issue/MR comments and metadata |
|
|
| `write:issue` | Create + update comments, open MRs |
|
|
| `read:user` | Resolve actor info (optional) |
|
|
|
|
### Secrets
|
|
|
|
In *Repo Settings → Actions → Secrets* add:
|
|
|
|
| Secret | Value |
|
|
|---|---|
|
|
| `FORGEJO_TOKEN` | The PAT from above |
|
|
| `NOMYO_API_KEY` | Your Nomyo API key |
|
|
|
|
### Workflow file
|
|
|
|
Add `.forgejo/workflows/opencode.yml` to the repo where you want opencode to respond. This example targets a Forgejo instance hosted under a subpath (`bitfreedom.net/code/`); adjust URLs and `runs-on` to match your setup.
|
|
|
|
```yaml
|
|
name: opencode
|
|
on:
|
|
issue_comment:
|
|
types: [created]
|
|
pull_request_review_comment:
|
|
types: [created]
|
|
|
|
jobs:
|
|
opencode:
|
|
if: |
|
|
contains(github.event.comment.body, '/oc') ||
|
|
contains(github.event.comment.body, '/opencode')
|
|
runs-on: docker-amd64
|
|
container:
|
|
image: node:lts-bookworm
|
|
permissions:
|
|
id-token: write
|
|
contents: write
|
|
pull-requests: write
|
|
issues: write
|
|
steps:
|
|
- name: Install git, curl and Docker
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq git curl unzip docker.io
|
|
|
|
- name: Start Docker daemon
|
|
run: |
|
|
dockerd --host=unix:///var/run/docker.sock --iptables=false --dns=8.8.8.8 --dns=8.8.4.4 > /tmp/dockerd.log 2>&1 &
|
|
for i in $(seq 1 30); do
|
|
sleep 2
|
|
docker info > /dev/null 2>&1 && echo "Docker daemon ready" && exit 0
|
|
echo "Waiting for Docker daemon... ($i/30)"
|
|
done
|
|
cat /tmp/dockerd.log
|
|
exit 1
|
|
|
|
- name: Checkout repository
|
|
run: |
|
|
git clone --depth=1 --branch "${{ github.ref_name }}" \
|
|
"https://oauth2:${{ github.token }}@bitfreedom.net/code/${{ github.repository }}.git" \
|
|
.
|
|
|
|
- name: Fetch action source
|
|
run: |
|
|
git clone --depth=1 --branch v1 \
|
|
"https://oauth2:${{ github.token }}@bitfreedom.net/code/nomyo-ai/actions.git" \
|
|
./.opencode-action
|
|
|
|
- name: Run opencode
|
|
uses: ./.opencode-action
|
|
with:
|
|
nomyo_api_key: ${{ secrets.NOMYO_API_KEY }}
|
|
model: nomyo/unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M
|
|
forgejo_api_url: https://bitfreedom.net/code/
|
|
forgejo_token: ${{ secrets.FORGEJO_TOKEN }}
|
|
```
|
|
|
|
### Why the manual clone?
|
|
|
|
`act_runner`'s `uses:` parser expects `https://<host>/<owner>/<repo>` — it does not handle a Forgejo instance hosted under a subpath (e.g. `bitfreedom.net/code/`). For subpath instances, clone the action source into `./.opencode-action` and reference it as a local action (`uses: ./.opencode-action`). If your Forgejo lives at a domain root, you can replace those two steps with `uses: <your-host>/nomyo-ai/actions@v1` directly.
|
|
|
|
## Configuration
|
|
|
|
| Input | Required | Default | Description |
|
|
|---|---|---|---|
|
|
| `model` | Yes | — | Model in the form `provider/model`. The provider name becomes a custom opencode provider; the action registers it as an `@ai-sdk/openai-compatible` provider pointing at `nomyo_api_url`. |
|
|
| `nomyo_api_key` | Yes | — | API key for the OpenAI-compatible backend. |
|
|
| `nomyo_api_url` | No | `https://chat.nomyo.ai/api` | Base URL of the OpenAI-compatible endpoint. The adapter calls `${baseURL}/chat/completions`. |
|
|
| `forgejo_api_url` | No | `https://bitfreedom.net/code/` | Forgejo instance base URL. |
|
|
| `forgejo_token` | No | — | Forgejo PAT (see scopes above). |
|
|
| `agent` | No | — | Primary agent name to use. |
|
|
| `share` | No | auto | Share the opencode session (`true`/`false`). Defaults to `true` for public repos. |
|
|
| `prompt` | No | — | Custom prompt override. |
|
|
| `mentions` | No | `/opencode,/oc` | Comma-separated trigger phrases. |
|
|
| `variant` | No | — | Provider-specific reasoning effort (`high`, `max`, `minimal`, …). |
|
|
|
|
## Supported Events
|
|
|
|
| Event | Triggered by | Notes |
|
|
|---|---|---|
|
|
| `issue_comment` | Comment on an issue or MR | Body must contain a mention phrase |
|
|
| `pull_request_review_comment` | Comment on a specific line in an MR's Files tab | Receives file path, line number, and diff hunk |
|
|
|
|
## Architecture
|
|
|
|
This action is a **composite action**. On each run it:
|
|
|
|
1. Installs Bun and the opencode CLI.
|
|
2. Runs [`bun install`](package.json) inside the action source.
|
|
3. Registers `${MODEL%/*}` as a custom opencode provider via `OPENCODE_CONFIG_CONTENT` and the API key via `OPENCODE_AUTH_CONTENT`.
|
|
4. Spawns `opencode serve` locally and talks to it over HTTP (`/session/{id}/message`).
|
|
5. Resolves the event, builds a prompt with thread/diff context, sends it to opencode, and posts the response as a comment (and, for "fix" intents, opens an MR).
|
|
|
|
There is no compiled `dist/` artifact — `index.ts` is executed directly by Bun.
|
|
|
|
## Development
|
|
|
|
Local test loop (requires Bun and `opencode` on PATH):
|
|
|
|
```bash
|
|
export NOMYO_API_KEY="..."
|
|
export NOMYO_API_URL="https://chat.nomyo.ai/api"
|
|
export FORGEJO_API_URL="https://bitfreedom.net/code/"
|
|
export FORGEJO_TOKEN="..."
|
|
export MODEL="nomyo/unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M"
|
|
export GITHUB_RUN_ID="test-run"
|
|
export GITHUB_RUN_NUMBER="1"
|
|
export GITHUB_SERVER_URL="https://bitfreedom.net/code"
|
|
export GITHUB_REPOSITORY="owner/repo"
|
|
export GITHUB_REF_NAME="main"
|
|
export GITHUB_ACTOR="testuser"
|
|
export GITHUB_EVENT_NAME="issue_comment"
|
|
export GITHUB_EVENT_PATH="/tmp/event.json"
|
|
|
|
cat > /tmp/event.json <<'EOF'
|
|
{
|
|
"repository": { "owner": { "login": "owner" }, "name": "repo", "full_name": "owner/repo" },
|
|
"issue": { "number": 1, "index": 1, "title": "Test", "body": "Test", "user": { "login": "testuser" } },
|
|
"comment": { "id": 1, "body": "/oc explain this", "user": { "login": "testuser" } }
|
|
}
|
|
EOF
|
|
|
|
bun install
|
|
bun run index.ts
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|