No description
  • JavaScript 78.8%
  • Shell 13.3%
  • Dockerfile 7.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-28 13:07:10 +00:00
defaults initial build 0.0.5 2026-09-28 16:30:41 +01:00
.gitignore initial build 2026-09-28 15:24:08 +01:00
build-stats.md 0.0.12 : upstream 1.19.5 2026-10-28 13:07:10 +00:00
CHANGELOG.md 0.0.12 : upstream 1.19.5 2026-10-28 13:07:10 +00:00
CloudronManifest.json 0.0.12 : upstream 1.19.5 2026-10-28 13:07:10 +00:00
CloudronVersions.json 0.0.12 : upstream 1.19.5 2026-10-28 13:07:10 +00:00
configure.js initial build 0.0.5 2026-09-28 16:30:41 +01:00
DESCRIPTION.md initial build 2026-09-28 15:33:56 +01:00
Dockerfile 0.0.12 : upstream 1.19.5 2026-10-28 13:07:10 +00:00
LICENSE initial build 2026-09-28 15:24:08 +01:00
logo.png initial build 2026-09-28 15:24:08 +01:00
POSTINSTALL.md initial build 0.0.3 2026-09-28 15:43:59 +01:00
README.md initial build 0.0.5 2026-09-28 16:30:41 +01:00
server.js initial build 0.0.5 2026-09-28 16:30:41 +01:00
start.sh initial build 0.0.5 2026-09-28 16:30:41 +01:00
version.md 0.0.12 : upstream 1.19.5 2026-10-28 13:07:10 +00:00

opencode-worker for Cloudron

A lean, headless execution engine for the opencode CLI. Peer apps dispatch AI coding tasks over an authenticated HTTPS API; the worker runs each task as an opencode child process and streams output to a per-task log file.

Inspiration from Marcus Quinn https://aidevops.sh gratefully acknowledged.

API

All endpoints except /health require Authorization: Bearer <AUTH_TOKEN>.

Method Endpoint Body Description
GET /health — App name, status message, version and uptime
GET /guide — Consumer how-to guide, read live from /app/data/config/consumer-guide.md
POST /dispatch {"prompt":"...", "repo":"...", "branch":"...", "model":"..."} Start a task, returns {"taskId":"...","status":"dispatched"}
GET /status — Active task count, concurrency limit and memory usage
GET /logs/:id — Current text content of the task log
DELETE /logs/:id — Delete the task log

repo may be an owner/name GitHub-style reference (cloned into /app/data/workspace/<name>), a full git URL, or an absolute path that already exists in the container. Set GIT_HOST if the host is not github.com. branch is optional and checks that branch out (fresh and reused clones alike) before the task runs; omitted, the repo's default branch is used. model is optional and uses the configured default when omitted.

Example: dispatch and read the log

curl -X POST https://<app-domain>/dispatch \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repo":"myuser/myrepo","prompt":"Fix the login bug"}'

curl -H "Authorization: Bearer $AUTH_TOKEN" https://<app-domain>/logs/<taskId>

Example: non-destructive task

--auto is passed to opencode, so a read-only task relies on the prompt itself to forbid changes:

curl -X POST https://<app-domain>/dispatch \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repo":"myuser/myrepo","prompt":"Inspect this repository in read-only mode. Do not create, edit, delete, move, commit, push, or otherwise modify any file, and do not run any command that changes repository or system state. Locate the project'"'"'s primary version file (e.g. package.json, VERSION, pyproject.toml, composer.json, Cargo.toml, go.mod) and report: 1. the project name, 2. the declared version, 3. the path to the version file, 4. the language/runtime and any declared dependencies. If no version file exists, say so explicitly. Output a short summary only."}'

Retrieving results

There is no separate result endpoint: the result is the task log. On success it ends with [worker] Task Completed Successfully; on failure with [worker] Task Failed: <reason>. opencode's own output is streamed into the same file live.

# Dispatch and capture the task id
TASK_ID=$(curl -s -X POST https://<app-domain>/dispatch \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repo":"myuser/myrepo","prompt":"Fix the login bug"}' | jq -r .taskId)

# Read the log at any time (including while the task is still running)
curl -s -H "Authorization: Bearer $AUTH_TOKEN" "https://<app-domain>/logs/$TASK_ID"

# Block until the task finishes (success or failure), then print the log
while true; do
  LOG=$(curl -s -H "Authorization: Bearer $AUTH_TOKEN" "https://<app-domain>/logs/$TASK_ID")
  echo "$LOG" | grep -qE "Task (Completed Successfully|Failed)" && { echo "$LOG"; break; }
  sleep 5
done

# Check what is running right now
curl -s -H "Authorization: Bearer $AUTH_TOKEN" https://<app-domain>/status | jq .

# Tidy up once done
curl -s -X DELETE -H "Authorization: Bearer $AUTH_TOKEN" "https://<app-domain>/logs/$TASK_ID"

Configuration

All settings live in /app/data/config/worker.env. Edit the file and restart the app.

Key Purpose
AUTH_TOKEN Required bearer token for API access (auto-generated on first run if blank)
MAX_CONCURRENT Maximum simultaneous opencode processes (default 2)
COMPAT_BASE_URL Base URL of an OpenAI v1-compatible endpoint, including /v1
COMPAT_API_KEY API key for that endpoint
COMPAT_MODEL Default model name exposed by that endpoint
DEFAULT_MODEL Default provider/model for native providers (e.g. anthropic/claude-sonnet-4-5)
ANTHROPIC_API_KEY Native provider credentials (passed through to opencode)
GIT_TOKEN Token used when cloning private repositories (read-only scope is enough)
GIT_HOST Host used to resolve owner/name references (default github.com)

Generating an AUTH_TOKEN

Leave AUTH_TOKEN blank and the worker creates one on first start, writes it back to worker.env, and logs that it did so. To generate one yourself:

head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'

OpenAI v1-compatible endpoints

Setting COMPAT_BASE_URL, COMPAT_API_KEY and COMPAT_MODEL registers a compat provider (via @ai-sdk/openai-compatible, since these endpoints are not necessarily OpenAI) and makes compat/<COMPAT_MODEL> the default model. This works with OpenAI, OpenRouter, LiteLLM, vLLM, Ollama, LM Studio and other v1-compatible servers.

A dispatch request can override the model per task with any provider/model string, for example:

{ "repo": "myuser/myrepo", "prompt": "Add tests", "model": "compat/qwen2.5-coder" }

Storage

  • /app/data/workspace/ — cloned repositories
  • /app/data/logs/ — task logs
  • /app/data/config/ — the app's own config: worker.env and consumer-guide.md
  • /app/data/.opencode/ — everything opencode owns, kept hidden and separate: its generated config/ (including opencode.json and the single node_modules for provider/plugin packages), data/ (sessions DB, logs), cache/, state/ and home/

Consumer guide

A how-to document for peer apps that consume this service is served at GET /guide (requires AUTH_TOKEN). It is read from disk on every request, so /app/data/config/consumer-guide.md can be edited on the server without rebuilding or restarting the app. The shipped default lives at defaults/consumer-guide.md.

Documentation


Packaged by @timconsidine https://communityapps.appx.uk (full portfolio: https://myca.appx.uk)