opencode-ccs-sync is an OpenCode plugin that reads your Claude Code Switch (CCS) configuration and automatically keeps your OpenCode config in sync.
It is designed to be safe and repeatable:
- it only manages
ccs-*providers and their provider-local model lists - it preserves unrelated OpenCode config and JSONC comments
- it chooses one deterministic global default model for OpenCode
- it automatically syncs once on every OpenCode startup
- it automatically re-syncs when the CCS config changes while OpenCode is running
- it exposes structured JSON through its internal tool surface for automation and debugging
You MUST have all of the following working first:
- OpenCode installed and running
- CCS installed and configured
- CLIProxy reachable from your machine
- A CCS config file at
~/.ccs/config.yaml, see CCS Install
If CCS itself is not healthy, this plugin cannot fix that for you. It only syncs CCS state into OpenCode.
Before you add this plugin, you SHOULD verify:
ccs doctorsucceeds or at least clearly reports the current CCS state- your live
~/.ccs/*.settings.jsonfiles exist for the providers you actually use - those live settings files contain the Anthropic-compatible values you expect
- OpenCode is already using a config file you can edit
If you are unsure whether CCS is configured correctly, start here:
ccs doctorThat is the fastest way to catch broken CCS setup before debugging this plugin.
Once OpenCode loads the plugin, it starts a background sync/watch process automatically.
That startup process does this:
- resolves your OpenCode config path
- resolves your CCS config path
- parses and normalizes CCS config values
- inspects live
~/.ccs/*.settings.jsonfiles to determine which providers are actually in use - extracts the explicit selected/default models for each live provider from Anthropic-compatible env values
- validates those selected models against CLIProxy discovery
- rewrites only the managed
ccs-*sections in your OpenCode config - chooses one deterministic global default model for OpenCode
- keeps watching for CCS/OpenCode config changes and repeats the sync when needed
The normal user flow is therefore:
- install or link the plugin
- restart OpenCode
- let the plugin perform its automatic startup sync
- edit CCS config as needed and let the plugin re-sync automatically
This plugin only owns entries prefixed with ccs-.
It may create, update, or remove:
provider.ccs-*provider.ccs-*.models- the global
modelfield when a CCS default model is selected
It does not touch unrelated entries like openai, anthropic, or any non-ccs-*
providers.
Add this package to the plugin array in your OpenCode config.
Example:
OpenCode resolves and installs package-based plugins automatically with Bun at startup.
If you are developing or testing this repo locally, OpenCode can also load plugins from:
.opencode/plugins/in your project~/.config/opencode/plugins/globally
For a local test, you can place a built plugin file in one of those directories and let OpenCode load it directly.
OpenCode’s docs describe config files such as:
- global:
~/.config/opencode/opencode.json - project:
./opencode.json
This plugin follows OpenCode's normal file conventions automatically.
It resolves your OpenCode config in this order:
./opencode.json./opencode.jsonc~/.config/opencode/opencode.json~/.config/opencode/opencode.jsonc
By default, the plugin reads:
~/.ccs/config.yaml
CCS reference:
- CCS repository/README: https://github.com/kaitranntt/ccs
- CCS docs: https://docs.ccs.kaitran.ca
The plugin reads two kinds of CCS inputs:
~/.ccs/config.yamlfor broad CCS/CLIProxy context such as:
cliproxy.providerscliproxy_server.local.port
- Live
~/.ccs/*.settings.jsonfiles for the provider-specific values that actually matter for OpenCode registration:
ANTHROPIC_BASE_URLANTHROPIC_MODELANTHROPIC_DEFAULT_OPUS_MODELANTHROPIC_DEFAULT_SONNET_MODELANTHROPIC_DEFAULT_HAIKU_MODEL
The live *.settings.json files are the more specific source of truth. This plugin uses them to
decide:
- which providers are actually active
- which models should be offered for each provider
- which runtime base URL a given provider really uses
- which model should be preferred as the default for that provider
If a provider-specific base URL cannot be derived from live settings, the plugin falls back to the
local cliproxy port from config.yaml. If neither value exists, it defaults to:
http://127.0.0.1:3456
The plugin always uses this bearer token when talking to CLIProxy:
ccs-internal-managed
The plugin does not register every provider listed in the broad CCS cliproxy pool.
Instead, it prefers live provider settings files in ~/.ccs/, such as:
codex.settings.jsonclaude.settings.jsonghcp.settings.json
A provider is treated as actually configured only when its live settings file contains an Anthropic-compatible env marker such as:
ANTHROPIC_BASE_URLANTHROPIC_MODEL
Files that do not expose those env values are ignored for OpenCode registration. That means a
file like agy.settings.json with hooks only will not create ccs-agy in OpenCode.
If no live settings files can be used, the plugin falls back to the broader provider list from
config.yaml.
For each kept provider, the plugin does not expose the full discovery universe from CLIProxy.
Instead, it builds a per-provider allowlist from the live provider settings file using these env keys:
ANTHROPIC_MODELANTHROPIC_DEFAULT_OPUS_MODELANTHROPIC_DEFAULT_SONNET_MODELANTHROPIC_DEFAULT_HAIKU_MODEL
That explicit model set is then validated against the provider’s discovered CLIProxy models.
So the final provider-local model list is:
- models explicitly selected in the provider’s live CCS settings file
- filtered to those that actually exist in discovery
This is why the plugin no longer writes giant mixed model lists under every ccs-* provider.
If codex.settings.json contains:
{
"env": {
"ANTHROPIC_MODEL": "gpt-5.3-codex",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gpt-5.3-codex",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-5.3-codex",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-5-codex-mini"
}
}then ccs-codex.models SHOULD end up containing only:
gpt-5.3-codexgpt-5-codex-mini
not unrelated models such as gpt-4o, Gemini models, or Claude models.
After OpenCode loads the plugin, the plugin immediately starts its background watch flow.
That means:
- on every OpenCode start, it performs one initial sync
- while OpenCode keeps running, it watches the resolved CCS config path
- it also watches the resolved OpenCode config path
- if either file changes, it re-runs sync automatically
- it suppresses self-triggered write loops using content hashing and a short suppression window
This automatic lifecycle is the primary user-facing behavior. You SHOULD think of this as a background syncing plugin, not a manual command you have to keep invoking.
OpenCode has one global model field. This plugin always writes at most one default model.
When multiple CCS providers are available, it chooses deterministically:
- sort provider IDs lexicographically
- pick the first provider
- inside that provider, prefer
ANTHROPIC_MODELif it exists in the final validated provider model list - otherwise pick the first lexicographically sorted model ID
The final written format is always:
ccs-<provider>/<model-id>
Managed providers are written as OpenCode custom providers using
@ai-sdk/openai-compatible.
Example generated shape:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-ccs-sync"],
"provider": {
"ccs-claude": {
"npm": "@ai-sdk/openai-compatible",
"name": "CCS Claude",
"options": {
"baseURL": "http://127.0.0.1:3456/api/provider/claude/v1",
"apiKey": "ccs-internal-managed",
},
"models": {
"claude-sonnet-4-6": {
"name": "Claude Sonnet 4.6",
},
"claude-opus-4-6": {
"name": "Claude Opus 4.6",
},
"claude-haiku-4-5-20251001": {
"name": "Claude Haiku 4.5 20251001",
},
},
},
},
"model": "ccs-claude/claude-sonnet-4-6",
}Important: there is no root-level models block. OpenCode expects provider-local models.
If you need to debug the plugin, use OpenCode's normal logs rather than manually driving sync as a user workflow.
Useful checks:
- restart OpenCode and inspect the config after startup
- inspect OpenCode logs for
opencode-ccs-sync - confirm the generated
provider.ccs-*entries match the live~/.ccs/*.settings.jsonfiles - edit the CCS config and confirm OpenCode updates automatically while still running
Model discovery retries indefinitely only for transient availability failures such as:
- connection failures
- CLIProxy being unavailable
- retryable 5xx responses
It does not retry indefinitely for:
- malformed local config
- invalid YAML
- non-retryable 4xx HTTP responses from model discovery
Backoff starts at 1 second and is capped at 30 seconds.
If you want the quickest end-to-end validation, follow this order:
- make sure CCS works
- run
ccs doctor - confirm
~/.ccs/config.yamland the live~/.ccs/*.settings.jsonfiles contain the providers/models you actually expect - add the plugin to OpenCode
- restart OpenCode
- inspect your OpenCode config after startup sync
- confirm each generated
provider.ccs-*contains only the models explicitly selected in the matching live~/.ccs/*.settings.jsonfile - edit the CCS config and confirm the plugin re-syncs automatically while OpenCode is still running
That is the real user path now: install, restart, verify auto-sync, then verify automatic re-sync on CCS changes.
Use these commands from the repository root:
bun install
bun test
./node_modules/.bin/eslint .
bunx tsc --noEmit
bun build ./src/index.ts --outdir dist --target bunYou have two practical options.
This is the normal user path once the package is published and available to Bun:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-ccs-sync"],
}OpenCode can load local JavaScript/TypeScript plugin files from:
.opencode/plugins/~/.config/opencode/plugins/
For local development, you SHOULD build this repo first:
bun build ./src/index.ts --outdir dist --target bunThen copy or symlink the built plugin file into one of OpenCode’s plugin directories.
Example project-local setup:
mkdir -p .opencode/plugins
ln -sf "$(pwd)/dist/index.js" .opencode/plugins/opencode-ccs-sync.jsExample global setup:
mkdir -p ~/.config/opencode/plugins
ln -sf "$(pwd)/dist/index.js" ~/.config/opencode/plugins/opencode-ccs-sync.jsAfter restarting OpenCode, the plugin SHOULD perform its initial sync automatically.
For local validation, check these instead of manually invoking the internal tool:
- OpenCode starts without plugin load errors
- your OpenCode config is updated on startup
- only
ccs-*sections are changed - provider-local model lists match the live
~/.ccs/*.settings.jsonselections - editing the CCS config triggers another automatic sync while OpenCode is still open
If sync does not behave as expected, check these in order:
ccs doctor- verify
~/.ccs/config.yamlexists and CLIProxy is pointed at the expected local server - verify the live
~/.ccs/*.settings.jsonfiles contain the providers and Anthropic model envs you actually expect OpenCode to expose - verify CLIProxy is reachable at the provider-specific
ANTHROPIC_BASE_URLvalues from the live settings files, or at the derived local cliproxy port if those are absent - restart OpenCode and inspect the OpenCode log output / config result after startup
Common causes of confusion:
- CCS is installed but not healthy yet
- providers appear in
cliproxy.providersbut do not have live Anthropic-compatible*.settings.jsonfiles - CLIProxy is reachable but a discovered model is not explicitly selected in the live provider settings, so it is intentionally omitted
- expecting non-
ccs-*providers/models to be modified
See LICENSE.
{ "$schema": "https://opencode.ai/config.json", "plugin": ["opencode-ccs-sync"], }