You are helping a user set up UltraCode-Shim: it lets Claude Code's UltraCode
mode run on any model the user already pays for, chosen from the pre-launch
orchestrator/worker selector or from the /model menu.
Follow these phases in order. Do not skip the doctor. Everything here is pure
Python standard library — do not pip install anything. Prefer the commands
exactly as written; they are cross-platform-aware.
The end state: the user double-clicks "UltraCode (All Models)", picks an
orchestrator + worker in the selector, and Claude Code opens already pointed at
that choice. They can still type /model later and pick any backend they
configured. The mechanism (no magic): a tiny loopback proxy (proxy.py) sits at
ANTHROPIC_BASE_URL, advertises the user's models plus synthesized Worker → X
entries on GET /v1/models, adds the UltraCode envelope to every request, and
routes each pick to its real backend. See docs/HOW_IT_WORKS.md.
Run and confirm each:
python3 --version(orpython --version) → must be ≥ 3.8.claude --version→ Claude Code CLI must be installed. If missing:npm i -g @anthropic-ai/claude-code.- Confirm the user has UltraCode access in their Claude plan (they'd have used
/effort ultracodebefore). If not, this project can't grant it.
Fastest path — the one-command installer (clones if needed, runs the offline
self-test, creates config.json, and installs a ultracode launcher on PATH):
# mac/linux/WSL
curl -fsSL https://raw.githubusercontent.com/OnlyTerp/UltraCode-Shim/main/install.sh | bash
# windows (PowerShell)
irm https://raw.githubusercontent.com/OnlyTerp/UltraCode-Shim/main/install.ps1 | iex
If you already have a checkout, run ./install.sh (or .\install.ps1) from
inside it. Either way, the installer runs the offline self-test for you.
If you'd rather do it by hand (or the installer can't run):
- Clone if not already:
git clone https://github.com/OnlyTerp/UltraCode-Shim.git - From the repo root run the doctor:
python3 scripts/doctor.py
Either way, the offline self-test (no network/keys) proves the proxy, discovery, the UltraCode envelope, and tool-call translation all work. If it fails, STOP and report the output — the install is broken, not the user's config.
Ask the user which of these they have (only configure those):
- An API key for an OpenAI-compatible service (MiniMax‑M3, MiMo, DeepSeek,
OpenRouter, OpenAI, Ollama, a local llama.cpp/LM Studio server, etc.) → use
openai_compat. - A ChatGPT/Codex login for GPT‑5.5 → use
codex_oauth(runcodex login). - Just Claude → they can still use it, routed as Anthropic passthrough. No
savings, but UltraCode works. You don't need to configure real Claude at
all: the proxy always advertises the stock Claude models (Opus/Sonnet/Haiku)
in
/modeland keeps them there even with no Anthropic key. The stock list is self-updating — the proxy learns real Claude ids from any successful upstream/v1/modelsfetch and caches them, so a newly released Opus appears on its own. So the user always has real (and current) Claude available; they only add cheaper backends. (Disable withproxy.include_stock_models: false/UC_INCLUDE_STOCK_MODELS=0for only-configured models; disable just the learning withproxy.learn_stock_models: false/UC_STOCK_LEARN=0.)
Then edit one file: copy config.example.json → config.json (the installer
and launcher also do this on first run), and edit config.json:
models— one entry per model to show in/model. EveryidMUST start withclaudeoranthropicor Claude Code drops it.routes— a route for each of those ids (the key must equal theid).- Keys go inline (config.json is gitignored) or as
${VAR}(export it, or use a gitignoredultracode.envin the repo root that the launchers load).
See docs/ADD_A_MODEL.md for exact templates per backend type.
Rules you must enforce:
- The
idinmodelsand the key inroutesmust be identical. - For
openai_compat,modelis the backend's real model id (not theclaude-…alias). - For
openai_compat,upstreamis the provider's base URL (usually ends in/v1); the proxy appends/chat/completions. - Reasoning models that inline
<think>(e.g. MiniMax‑M3): add"body": { "reasoning_split": true }to the route so the chain‑of‑thought is split out of the visible answer. The shipped example already does this forclaude-minimax-m3. Thebodydict is the general way to pass any provider‑specific request param.
If the user wants UltraCode to pick the model per task automatically (cheap
model for trivial turns, strong model for hard ones), configure the router
block in config.json. It's already present and enabled in config.example.json
— you mainly prune it to the models they kept. Full reference:
docs/AUTO_ROUTER.md.
Rules you must enforce:
router.id(defaultclaude-auto) MUST also appear inmodelsand have a{"type":"auto"}route. (The example already includes all three; the proxy also auto-creates them ifrouter.enabledand they're missing.)- Every
candidates[].idandrouter.classifierMUST be a key inroutes. Candidates without a route are silently skipped, so prune the candidate list to match the routes the user kept. router.classifiershould be the cheapest, fastest model they configured (it runs on every new task). If it's unavailable the router still works — it just falls back to the cheapest candidate without scoring.candidates[].costis a relative weight (ordering only). Order them cheap→expensive.- Set
candidates[].supports_imagestruthfully; image tasks skip models that can't see. - Write an honest
candidates[].card(strengths AND weaknesses) — that text is literally what the classifier reads to route. Vague cards → vague routing. - If the user does NOT want auto routing, set
"enabled": false(or delete therouterblock). Theclaude-automodel just won't appear.
The doctor (next phase) validates all of this.
Run the doctor again:
python3 scripts/doctor.py
Now it validates the user's actual config.json: ids are discoverable+routed,
every ${VAR} referenced by a route is present (or the key is inline), and
codex_oauth/cursor_agent routes have their login/CLI. Fix every [FAIL]
(each prints its fix) until exit code 0.
If you used the installer, just run ultracode (it's on PATH). Otherwise launch
from the checkout:
- macOS/Linux/WSL:
./bin/ultracode - Windows:
./windows/Start-UltraCode.ps1(or./install.ps1 -DesktopIconsfor "UltraCode (All Models)" + "Claude Code (Normal)" Desktop shortcuts).
The launcher starts the proxy, seeds Claude Code's gateway-models cache from the
live proxy (the stock Claude models + the user's configured models + synthesized
Worker → X entries), opens the two-column selector, then passes the selected
orchestrator as claude --model .... Set UC_SELECTOR=0 to skip the selector and
choose from /model only.
- Launch UltraCode. Confirm the selector appears and can pick an orchestrator +
worker (
Same as orchestratormeans one model runs everything). - In Claude Code, type
/modeland confirm BOTH appear: the stock Claude models (Opus/Sonnet/Haiku) and the user's custom models +Worker → Xentries (the proxy serves all of them onGET /v1/models). - Send a trivial prompt ("say OK"). Confirm a reply uses the selected model.
- Pick one that needs tools and ask something requiring a tool call; confirm tools fire (the proxy translates tool calls both ways).
- If you configured the Auto Router: pick
Auto (smart routing), run the proxy withUC_ROUTER_LOG=1, send a trivial prompt then a hard one, and confirm the proxy log shows a[router] ... -> <model>line choosing a cheap model for the trivial turn and a stronger one for the hard turn.
If a model doesn't appear or errors, go to docs/TROUBLESHOOTING.md and match the
symptom. Common ones:
- Model missing from
/model→ id didn't start withclaude/anthropic, or discovery env not set (the launcher setsCLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1). - Real Claude (Opus/etc.) missing from
/model→ only if stock models were turned off (proxy.include_stock_models: false/UC_INCLUDE_STOCK_MODELS=0); they're on by default. - "responded but never called tools" → that route must be
openai_compat(not passthrough) so tools are translated. - 401/empty from a backend → wrong/empty key in
config.json, or expiredcodex login.
- Never commit
config.jsonorultracode.env(they're gitignored; they hold the user's choices/keys). - An API key may go inline in
config.json(gitignored) or as${VAR}; never commit a real key. - Don't modify the user's global
~/.claudeconfig; this tool is session-scoped. - If the offline self-test (
python3 test_proxy.py) fails, the problem is the code/clone, not the user — report it, don't paper over it.