Make Lemonade Server (local GGUF models served via an
OpenAI-compatible API on port 13305) available to opencode.
This repository contains two independent tools that solve the same problem in different ways. They are mutually exclusive — pick one:
| 1. opencode plugin | 2. live sync tool | |
|---|---|---|
| What it does | Registers Lemonade models with opencode at startup | Watches the model cache dir and rewrites opencode.json whenever a model changes |
| Run-time | Inside opencode (loaded as a plugin) | Standalone container daemon |
| Refresh | On every opencode restart | On every cache-dir change (debounced) |
| Config written | In-memory (no files touched) | Directly into opencode.json |
The plugin is the current, preferred approach and is what the author uses. The Docker sync tool is preserved for anyone who wants the live-watcher behavior.
Both tools assume Lemonade's model cache lives at ~/.cache/lemonade and its API is at
http://<host>:13305.
On every opencode startup, the plugin:
- Fetches the model list from
GET {baseURL}/v1/models. - Registers the
lemonadeprovider (via the@ai-sdk/openai-compatibleSDK) with each returned model in opencode's in-memory config.
Models then appear in opencode as lemonade/<model-id> (e.g. lemonade/gpt-oss-20b-GGUF-Q4_K_M).
Because registration happens in-memory, no configuration files are modified.
- opencode 1.18.x (the plugin uses the classic
{ id, server() }plugin contract). - Lemonade reachable from wherever opencode runs.
Reference the repo directly from GitHub in opencode.json:
{
"plugin": ["git+https://github.com/stein3/lemonade-watcher.git"]
}or with opencode plugin add git+https://github.com/stein3/lemonade-watcher.git.
You can pin a version with a ref fragment (...#v0.1.0, #main, or a commit SHA) and
override the base URL with the options tuple:
"plugin": [["git+https://github.com/stein3/lemonade-watcher.git", { "baseURL": "http://host:13305" }]]Then quit and restart opencode. The plugin loads once at startup; running sessions keep their already-loaded config.
The base URL is resolved in this order:
baseURLfrom the plugin options tupleLEMONADE_BASE_URLenvironment variablehttp://host.docker.internal:13305(default)
If the model fetch fails (server down, wrong URL), opencode still starts; the lemonade
provider is simply registered without models and an error is logged.
Remove the entry from the plugin array in opencode.json, then restart opencode.
Keeps provider.lemonade in your opencode.json up to date:
- On startup, it performs an initial sync.
- It watches the Lemonade model cache directory (via a polling observer, which is reliable inside Docker bind mounts) for file creation/move events.
- Docker (for the containerized daemon) or Python 3.12+ for a bare-metal run.
httpxandwatchdog(pip install -r requirements.txt).- A running Lemonade instance and write access to the
opencode.jsonyou want updated.
docker compose up --buildcompose.yaml expects two host paths; both are parameterized with sensible defaults:
| Variable | Default | Purpose |
|---|---|---|
LEMONADE_CACHE_DIR |
~/.cache/lemonade |
Host dir mounted into the container and watched |
OPENCODE_WORKSPACE_DIR |
/workspace |
Host dir containing opencode.json, mounted at /workspace |
The compose file itself sets the Lemonade API host to http://host.docker.internal:13305
via the host.docker.internal gateway.
Example overriding them:
LEMONADE_CACHE_DIR=/home/you/.cache/lemonade \
OPENCODE_WORKSPACE_DIR=/home/you/code/opencode \
docker compose up --buildEnvironment variables inside the container:
| Variable | Default | Purpose |
|---|---|---|
LEMONADE_BASE_URL |
http://localhost:13305 |
Lemonade API base URL |
LEMONADE_CACHE_DIR |
~/.cache/lemonade |
Cache dir to watch |
OPENCODE_CONFIG_PATH |
/workspace/opencode.json |
opencode.json to rewrite |
LEMONADE_BASE_URL=http://localhost:13305 \
LEMONADE_CACHE_DIR=~/.cache/lemonade \
OPENCODE_CONFIG_PATH=/path/to/opencode.json \
python3 lemonade_event_sync.pyThe daemon logs what it fetches, writes, and detects; stop it with SIGTERM/Ctrl-C.
- The opencode plugin fetches models only at startup, so a model downloaded while opencode is running appears after the next restart. The Docker tool reacts to downloads immediately but requires a file rewrite.
- Both tools register the same
lemonadeprovider shape, so switching between them requires no change to yourmodelsetting.