A shell wrapper that points Claude Code at Synthetic, so you keep the Claude Code interface — agents, tools, subagents, slash commands — while inference runs on open models like Kimi K3, GLM 5.2 and MiniMax M3.
It is one function plus three helpers. Paste the block below into your shell rc and you are done.
- Picks a model per run with a substring:
--model k3
,--model glm-5.2
- Validates the model against Synthetic's live catalogue, so a typo fails immediately with the real options instead of silently doing the wrong thing
- Points every model slot at your choice — main, opus, sonnet, haikuand subagents— so delegated work does not quietly fall back to something else - Leaks nothing into your shell: every variable is scoped to the child process
| Tool | Why |
|---|---|
claude |
|
the CLI itself — npm i -g @anthropic-ai/claude-code |
|
curl |
|
| fetches the model catalogue | |
jq |
|
parses it — brew install jq |
|
awk , sed , find |
|
| matching and caching (already on macOS/Linux) |
Shell: bash 3.2+ or zsh. It uses arrays, so it is not POSIX sh
.
A Synthetic API key: https://synthetic.new → Settings → API Keys.
Add your key and the function to ~/.zshrc
(or ~/.bashrc
):
export SYNTHETIC_API_KEY="syn_xxxxxxxxxxxxxxxx"
Then paste this block:
_synclaude_models() {
local cache="${TMPDIR:-/tmp}/synclaude-models-$(id -u).json"
if [ -n "${1:-}" ] || [ ! -s "$cache" ] || [ -n "$(find "$cache" -mmin +360 2>/dev/null)" ]; then
if curl -fsS -m 20 https://api.synthetic.new/v1/models \
-H "Authorization: Bearer ${SYNTHETIC_API_KEY:-}" -o "$cache.tmp" 2>/dev/null; then
mv -f "$cache.tmp" "$cache"
else
rm -f "$cache.tmp"
[ -s "$cache" ] || return 1
fi
fi
jq -r '.data[]?.id' "$cache" 2>/dev/null | sort
}
#
#
_synclaude_resolve_model() {
local query="$1" ids matches
if ! command -v jq >/dev/null 2>&1; then
printf >&2 "synclaude: --model needs 'jq' to read the catalogue (brew install jq)\n"
return 1
fi
if ! ids="$(_synclaude_models)"; then
printf >&2 'synclaude: could not fetch the catalogue from api.synthetic.new\n'
return 1
fi
if [ "$query" = "--list" ]; then
printf '%s\n' "$ids"
return 0
fi
matches="$(printf '%s\n' "$ids" | awk -v q="$query" '$0 == q')"
[ -z "$matches" ] && matches="$(printf '%s\n' "$ids" |
awk -v q="$query" 'BEGIN{q=tolower(q)} index(tolower($0), q)')"
if [ -z "$matches" ]; then
ids="$(_synclaude_models force)" || :
matches="$(printf '%s\n' "$ids" |
awk -v q="$query" 'BEGIN{q=tolower(q)} index(tolower($0), q)')"
fi
if [ -z "$matches" ]; then
printf >&2 "synclaude: no model matches '%s'. Available:\n" "$query"
printf '%s\n' "$ids" | sed 's/^/ /' >&2
return 1
fi
if [ "$(printf '%s\n' "$matches" | awk 'END{print NR}')" -gt 1 ]; then
printf >&2 "synclaude: '%s' is ambiguous, it matches:\n" "$query"
printf '%s\n' "$matches" | sed 's/^/ /' >&2
return 1
fi
printf '%s\n' "$matches"
}
_synclaude_help() {
cat <<HELP
synclaude - Claude Code against Synthetic
Usage:
synclaude [--model <id|substring>] [--thinking|--no-thinking] [claude args ...]
synclaude --help
Options:
--model <m> Override every model for this run - main, opus, sonnet, haiku
and subagent all become <m>. Takes a full model id or any
case-insensitive substring that matches exactly one.
--no-thinking Turn extended thinking off (the default here, see below).
--thinking Turn extended thinking back on.
-h, --help Show this message. Claude's own help is 'claude --help'.
Everything else is passed to claude untouched.
Current configuration:
credential SYNTHETIC_API_KEY
endpoint ${SYNTHETIC_BASE_URL:-https://api.synthetic.new/anthropic}
model ${SYNTHETIC_MODEL:-hf:moonshotai/Kimi-K3}
thinking ${SYNTHETIC_THINKING:-off}
HELP
printf '\nAvailable models:\n'
_synclaude_resolve_model --list 2>/dev/null | sed 's/^/ /'
}
synclaude() {
local model="${SYNTHETIC_MODEL:-hf:moonshotai/Kimi-K3}"
local base_url="${SYNTHETIC_BASE_URL:-https://api.synthetic.new/anthropic}"
local thinking="${SYNTHETIC_THINKING:-off}"
local requested_model=""
local -a args
args=()
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) _synclaude_help; return 0 ;;
--no-thinking) thinking=off; shift ;;
--thinking) thinking=on; shift ;;
--model)
if [ -z "${2:-}" ]; then
printf >&2 'synclaude: --model needs a value (try: synclaude --help)\n'
return 2
fi
requested_model="$2"; shift 2 ;;
--model=*) requested_model="${1#--model=}"; shift ;;
*) args+=("$1"); shift ;;
esac
done
if [ -z "${SYNTHETIC_API_KEY:-}" ]; then
printf >&2 'synclaude: SYNTHETIC_API_KEY is not set\n'
return 1
fi
if ! command -v claude >/dev/null 2>&1; then
printf >&2 "synclaude: 'claude' not found in PATH. Install: npm i -g @anthropic-ai/claude-code\n"
return 127
fi
if [ -n "$requested_model" ]; then
model="$(_synclaude_resolve_model "$requested_model")" || return 1
fi
local disable_thinking=1
[ "$thinking" = "on" ] && disable_thinking=
printf >&2 'synclaude: endpoint=%s | model=%s | thinking=%s\n' "$base_url" "$model" "$thinking"
env \
ANTHROPIC_BASE_URL="$base_url" \
ANTHROPIC_AUTH_TOKEN="$SYNTHETIC_API_KEY" \
ANTHROPIC_API_KEY= \
ANTHROPIC_MODEL="$model" \
ANTHROPIC_DEFAULT_OPUS_MODEL="$model" \
ANTHROPIC_DEFAULT_SONNET_MODEL="$model" \
ANTHROPIC_DEFAULT_HAIKU_MODEL="$model" \
CLAUDE_CODE_SUBAGENT_MODEL="$model" \
CLAUDE_CODE_DISABLE_THINKING="$disable_thinking" \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
CLAUDE_CODE_ATTRIBUTION_HEADER=0 \
CLAUDE_CODE_MAX_OUTPUT_TOKENS="${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-32000}" \
MAX_THINKING_TOKENS="${MAX_THINKING_TOKENS:-100000}" \
IS_DEMO="${IS_DEMO-true}" \
claude ${args[@]+"${args[@]}"}
}
Reload with exec $SHELL -l
, then run synclaude --help
.
synclaude # interactive, default model
synclaude -p "explain this repo" # one-shot
synclaude --model glm-5.2 --thinking # switch model, thinking on
synclaude --model k2.7 # substring is enough
synclaude --help # options + live model list
--model
accepts any case-insensitive substring that matches exactly one
catalogue entry. Everything the wrapper does not recognise is forwarded to
claude
untouched, so --resume
, --output-format json
, --verbose
and the rest all work as usual.
$ synclaude --model glm
synclaude: 'glm' is ambiguous, it matches:
hf:zai-org/GLM-4.7-Flash
hf:zai-org/GLM-5.2
Thinking is off by default, and that is not an arbitrary choice.
Claude Code sends a reasoning effort of medium
to third-party models. Some
models do not accept that value. Kimi K3 advertises only ['high','low','max']
, so with thinking on every request fails:
API Error: 500 {"error":"Error from inference backend: 500
Unsupported thinking_effort='medium'; supported values are ['high','low','max']."}
There is currently no way to change the value Claude Code sends. --effort
(its own CLI flag), CLAUDE_CODE_EFFORT_LEVEL
, CLAUDE_CODE_ALWAYS_ENABLE_EFFORT
and CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING
were each tested against this
endpoint and the error came back identical, still naming medium
. Synthetic's catalogue does not publish per-model effort levels either, so the wrapper cannot map them for you.
Turning thinking off avoids the rejected parameter entirely, which is why it is
the default here. The trade-off is real: you lose extended reasoning. On a
model that accepts medium
, turn it back on:
synclaude --model glm-5.2 --thinking
If you would rather default to a model that thinks, set
SYNTHETIC_MODEL=hf:zai-org/GLM-5.2
and SYNTHETIC_THINKING=on
.
All optional except the key.
| Variable | Default | Purpose |
|---|---|---|
SYNTHETIC_API_KEY |
||
| — | Required. Your Synthetic key | |
SYNTHETIC_MODEL |
||
hf:moonshotai/Kimi-K3 |
||
| Default model | ||
SYNTHETIC_THINKING |
||
off |
||
on to default extended thinking on |
||
SYNTHETIC_BASE_URL |
||
https://api.synthetic.new/anthropic |
||
| Endpoint | ||
CLAUDE_CODE_MAX_OUTPUT_TOKENS |
||
32000 |
||
| Max output tokens | ||
MAX_THINKING_TOKENS |
||
100000 |
||
| Thinking budget when thinking is on | ||
IS_DEMO |
||
true |
||
Trims the startup banner. IS_DEMO= synclaude restores it |
Two details worth knowing:
hides the "Tips for getting started" and "What's new" panels and your account/org line. Claude Code treatsIS_DEMO
any non-empty valueas on — including0
andfalse
— so the only way back to the full banner is an empty one:IS_DEMO= synclaude
.on every run so a real Anthropic key sitting in your environment cannot outrank the Synthetic token.ANTHROPIC_API_KEY
is blanked
** SYNTHETIC_API_KEY is not set** — export it, then reload your shell.
** no model matches '...'** — the error lists every available id. The catalogue is cached for six hours; to force a refresh:
rm -f "${TMPDIR:-/tmp}/synclaude-models-$(id -u).json"
** Unsupported thinking_effort='medium'** — that model rejects the effort level Claude Code sends. Drop
--thinking
, or pick another model.Model ids change. Synthetic rotates them without notice — Kimi-K2.5
was a
working default until it began returning 404. That is exactly why this wrapper
reads the catalogue from the API rather than hardcoding a list. Run
synclaude --help
to see what is live right now.
Tested on macOS with Claude Code 2.1.220 in both bash
and zsh
— including
pristine shells with no rc files — covering the default model, --model
overrides, ambiguous and unknown input, missing key and missing binary, and live end-to-end runs against the real API on Kimi K3 and GLM 5.2.
Public domain / CC0. Use it however you like.