Sync opencode config with models served by a vLLM/llama.cpp server
Project description
opencode-sync
Keep opencode's model list in sync with your local vLLM or llama.cpp server.
When you swap models on your inference server, opencode-sync queries /v1/models and updates opencode.jsonc in place. Run it once manually, or install the shell wrapper so it happens automatically every time you start opencode.
It edits only the model list of the providers it syncs. Your comments, formatting, and every other setting in the file are left byte-for-byte alone.
Requirements
- Python 3.8+
- No runtime dependencies (pure stdlib)
Install
pip install opencode-sync
Or from source:
git clone https://github.com/ozskywalker/opencode-sync
cd opencode-sync
pip install -e .
Usage
Sync against the URL already in your config (never changes the stored URL — safe to run any time):
opencode-sync
Point at a specific server and update the stored URL:
opencode-sync --host llm-server.local --port 8080
Preview changes without writing anything:
opencode-sync --dry-run
Auto-sync on every opencode launch
Install a shell wrapper once:
opencode-sync install
This writes ~/.local/bin/opencode — a small script that runs opencode-sync silently, then execs the real opencode binary. opencode always starts with a fresh model list, and still launches normally if the inference server is unreachable.
Make sure ~/.local/bin is earlier in your $PATH than the real opencode binary. The installer warns you if it isn't.
opencode-sync install [--wrapper PATH] [--opencode-bin PATH] [--force] [--dry-run]
How it works
On each run, opencode-sync:
- Works out which providers to sync (see Which providers get synced)
- Fetches each one's live model list from
GET /v1/models - Rebuilds that provider's
models— surviving IDs keep their entry exactly as you wrote it; new IDs get a generated name from the model ID - If
modelorsmall_modelpoints at this provider and its model is gone, repoints it to the first available one - Rewrites only those parts of the file
The model/small_model fields are written in opencode's expected provider/model-id format (e.g. vllm/org/model-name), so opencode resolves them correctly.
What it won't touch
Everything outside the model lists it syncs. Concretely:
- Comments and formatting. The file is edited by splicing text, not by re-serializing it, so your
//notes, indentation, and blank lines survive exactly. - Other providers. Syncing one provider never modifies another — including
model/small_modelwhen they point somewhere else. - Everything else.
$schema,agent,mcp,keybinds, and any other key stay byte-identical. - The file itself, when nothing changed. A no-op sync doesn't rewrite it at all.
Before writing, it re-parses the result and checks it means exactly what was intended. If that check fails it writes nothing and tells you — it never falls back to reformatting your file. Writes are atomic (temp file + rename), and a .bak is kept whenever content actually changes.
Which providers get synced
| Invocation | Providers synced |
|---|---|
opencode-sync |
Every provider with a baseURL, each against its own URL |
opencode-sync --provider ID |
Just ID |
opencode-sync --host X |
The one matching http://X:8080/v1 |
With no arguments it syncs everything, which is what the wrapper does. If one server is down, the others still sync; the exit code is non-zero only if every one failed. A server that's up but serving no models is skipped — your model list is never wiped because an inference server was mid-restart.
When a server renames a model
If a server starts reporting a different ID for what is really the same model, a naive sync would drop your old entry and everything you'd tuned on it. So:
opencode-sync --provider vllm --rename aeon=qwen3.5-122B-A10B
This moves the entry — display name, context limits, sampling options, comments and all — to the new ID.
A sync that removes exactly one model and adds exactly one is assumed to be a rename, but only when the old entry has settings worth keeping. It'll tell you when it does this:
~ Renamed: aeon -> qwen3.5-122B-A10B (kept limit, options, reasoning, temperature, tool_call)
That's a guess, and a genuine model swap would inherit the wrong settings — use --no-infer-renames to turn it off and drop the old entry instead.
URL behaviour
| Invocation | Server queried | baseURL in config |
|---|---|---|
opencode-sync |
Existing baseURL (fallback: localhost:8080) |
Unchanged |
LLAMA_ARG_HOST=X opencode-sync |
http://X:8080/v1 |
Updated |
LLAMA_ARG_HOST=X LLAMA_ARG_PORT=9000 opencode-sync |
http://X:9000/v1 |
Updated |
opencode-sync --host X |
http://X:8080/v1 |
Updated |
opencode-sync --host X --no-url-update |
http://X:8080/v1 |
Unchanged |
CLI --host/--port values override LLAMA_ARG_HOST/LLAMA_ARG_PORT.
Your customisations are preserved
Anything you've written on a model entry survives a sync, as long as the server still reports that model ID:
"models": {
"org/Qwen3-27B": {
"name": "My Qwen label", // kept
"limit": { "context": 229376 }, // kept
// Even this comment is kept.
"options": { "temperature": 0.6 }
}
}
All options
opencode-sync [--host HOST] [--port PORT] [--provider ID] [--config PATH]
[--rename OLD=NEW] [--no-infer-renames] [--dry-run]
[--no-url-update] [--no-model-update] [--timeout SECONDS]
| Flag | Default | Description |
|---|---|---|
--host HOST |
env, config, else localhost | vLLM server hostname |
--port PORT |
env, config, else 8080 | vLLM server port |
--provider ID |
all providers | Provider key to update |
--config PATH |
auto-detect | Path to opencode.jsonc |
--rename OLD=NEW |
— | Move a model entry to a new ID, keeping its settings (repeatable) |
--no-infer-renames |
off | Don't treat a 1-in/1-out sync as a rename; drop the old entry |
--dry-run |
off | Print a diff of the planned changes without writing |
--no-url-update |
off | Don't update baseURL even when --host/--port is given |
--no-model-update |
off | Don't update model/small_model if the active model is removed |
--timeout SECONDS |
10 | HTTP request timeout |
Config file locations
opencode uses xdg-basedir on every platform, so the search order is the same everywhere:
$XDG_CONFIG_HOME/opencode/opencode.jsonc(ifXDG_CONFIG_HOMEis set)~/.config/opencode/opencode.jsonc
Override with --config PATH.
Development
pip install -e ".[dev]"
python -m pytest # 205 tests
python -m pytest --cov=opencode_sync # with coverage
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file opencode_sync-0.4.0.tar.gz.
File metadata
- Download URL: opencode_sync-0.4.0.tar.gz
- Upload date:
- Size: 43.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d862a37767f23f7d41cca0dc48375416310c8bc0090b460146b6719bc1be894
|
|
| MD5 |
1e19638c3da80d6c50cbeecacc16d72d
|
|
| BLAKE2b-256 |
5fb39f0e22e8e038148c4681a001003490a209896329c3d2b149f5d7516fd8cc
|
Provenance
The following attestation bundles were made for opencode_sync-0.4.0.tar.gz:
Publisher:
release.yml on ozskywalker/opencode-sync
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
opencode_sync-0.4.0.tar.gz -
Subject digest:
9d862a37767f23f7d41cca0dc48375416310c8bc0090b460146b6719bc1be894 - Sigstore transparency entry: 2185996208
- Sigstore integration time:
-
Permalink:
ozskywalker/opencode-sync@e6015391d290d85f8c1e17c2becc6ed44d8e0b98 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/ozskywalker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e6015391d290d85f8c1e17c2becc6ed44d8e0b98 -
Trigger Event:
push
-
Statement type:
File details
Details for the file opencode_sync-0.4.0-py3-none-any.whl.
File metadata
- Download URL: opencode_sync-0.4.0-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0cdcda3670e216d123f1988c96e182629a01c882d5937b0a21e3bf0d106d2ca
|
|
| MD5 |
9c83cf27ae28824c10352e3e68239deb
|
|
| BLAKE2b-256 |
b31a83000433950d571c50b8176b1819dbd216db4118dd842003d3bba84737c2
|
Provenance
The following attestation bundles were made for opencode_sync-0.4.0-py3-none-any.whl:
Publisher:
release.yml on ozskywalker/opencode-sync
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
opencode_sync-0.4.0-py3-none-any.whl -
Subject digest:
a0cdcda3670e216d123f1988c96e182629a01c882d5937b0a21e3bf0d106d2ca - Sigstore transparency entry: 2185996408
- Sigstore integration time:
-
Permalink:
ozskywalker/opencode-sync@e6015391d290d85f8c1e17c2becc6ed44d8e0b98 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/ozskywalker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e6015391d290d85f8c1e17c2becc6ed44d8e0b98 -
Trigger Event:
push
-
Statement type: