OpenAI-compatible proxy server for the CommandCode API.
It exposes /v1/chat/completions and /v1/models endpoints
so OpenAI-compatible clients can call CommandCode models
through a local HTTP server.
Repository: https://github.com/Xell79/command-code-proxy-server
Version: v1.3.0
Fork of https://github.com/dev2k6/command-code-proxy-server with VPS hardening, protocol fixes, and optional thinking passthrough. Tuned for a thin VPS host: one stripped Go binary, no extra runtime dependencies.
- OpenAI-compatible chat completions and Responses endpoints
- Streaming and non-streaming responses with usage and prompt cache details
- Multimodal Vision support (
image_urldata URLs and images translated to CommandCode format) - Client disconnect detection with immediate upstream request cancellation (
r.Context()) - Extracts system prompts from both
systemanddevelopermessage roles - Prompt cache token details passthrough (
prompt_tokens_details.cached_tokens) - Preserves
reasoning_contentin assistant messages across conversation turns - Inline
<think>...</think>tag extraction intoreasoning_content - OpenAI-compatible model list endpoint
- Short model name mapping,
command-code/prefix stripping, and pass-through for unlisted models thinking: {type: "disabled"}andreasoning_effort: "none"forwarded asenable_thinking: false/v1/responsessession chaining viaprevious_response_id(in-memory, 1h TTL)- Optional default upstream API key from CLI or
COMMAND_CODE_API_KEY/COMMANDCODE_API_KEY - Per-request API key via
Authorization: Bearer <key>orx-api-keyheader - Optional client key whitelist (
-auth-keys/CCP_AUTH_KEYS) with constant-time comparison - Configurable host, port, and upstream
-base-url - Request body cap (32 MB) and
ReadHeaderTimeout/IdleTimeout - Empty or error upstream streams close cleanly (
[DONE], HTTP 502) - Empty/null message content is normalized (never
"content": null) - Optional thinking/reasoning passthrough (
reasoning_content) - Pinned
x-command-code-version(no blocking npm fetch on the request path) - Checks GitHub tags for a newer proxy version and displays it next to the current version
- Go 1.26.2 or newer
go run main.goDefault server address:
http://127.0.0.1:55990
go run main.go [options]| Option | Default | Description |
|---|---|---|
-host |
127.0.0.1 |
Host to bind the server to |
-port |
55990 |
Port to run the server on |
-api-key |
empty | CommandCode API key used for upstream calls (kept server-side). Also: COMMANDCODE_API_KEY |
-auth-keys |
empty | Comma-separated client keys allowed to use this proxy; empty = open proxy. Env: CCP_AUTH_KEYS |
-base-url |
https://api.commandcode.ai |
Override upstream base URL |
-version |
false |
Print version and exit |
Examples:
# Run on default host and port
go run main.go
# Run on a custom port
go run main.go -port 8080
# Expose on all interfaces (pair with -auth-keys)
go run main.go -host 0.0.0.0 -auth-keys client-key-1 -api-key your-commandcode-api-key
# Same keys via environment
COMMANDCODE_API_KEY=your-commandcode-api-key \
CCP_AUTH_KEYS=client-key-1 go run main.go -host 0.0.0.0
# Print version
go run main.go -versionBuild a stripped binary for a thin VPS:
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bin/command-code-proxyCross-compile:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o bin/command-code-proxy
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
go build -trimpath -ldflags="-s -w" -o bin/command-code-proxy-arm64
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o bin/command-code-proxy.exeDo not commit release binaries. Deploy the stripped file only.
- Client key —
Authorization: Bearer ...orx-api-key: ...from whoever calls this proxy. When-auth-keys(orCCP_AUTH_KEYS) is set, the key must be in the whitelist or the request is401. EmptyBearerfalls back to the server default instead of forwarding an empty key upstream. - Upstream key — used against
api.commandcode.ai, in this order:-api-key/COMMANDCODE_API_KEY(recommended: keep it server-side)- The client's own key (open-proxy mode)
If neither a whitelist nor any key is available, the request returns 401 Unauthorized.
Binding to 0.0.0.0 without -auth-keys turns this proxy
into an open relay: anyone who finds the address can burn
your CommandCode quota. Always pair public deployments with
a whitelist, or keep -host 127.0.0.1 behind your own
auth layer.
Request bodies are capped at 32 MB. Larger uploads are
rejected with 413 instead of hanging.
See systemd/ccproxy.service. Copy keys into
/opt/ccproxy/.env from .env.example — do not put the
upstream key on the command line (ps will show it).
sudo useradd --system --home /opt/ccproxy --shell /usr/sbin/nologin ccproxy
sudo install -o ccproxy -g ccproxy -m 0755 bin/command-code-proxy /opt/ccproxy/command-code-proxy
sudo install -o ccproxy -g ccproxy -m 0600 .env /opt/ccproxy/.env
sudo cp systemd/ccproxy.service /etc/systemd/system/ccproxy.service
sudo systemctl daemon-reload
sudo systemctl enable --now ccproxySuggested limits on a thin VPS: MemoryMax=128M,
GOMAXPROCS=1 if you share the box.
Put Caddy/nginx in front only if you need TLS. This process speaks HTTP.
GET /healthResponse:
{"status":"ok"}GET /v1/modelsReturns an OpenAI-compatible model list. When a client whitelist is configured, this endpoint also requires a valid client key.
POST /v1/chat/completionsExample non-streaming request:
curl http://127.0.0.1:55990/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-commandcode-api-key" \
-d '{
"model": "deepseek-v4-pro",
"messages": [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hello"}
],
"stream": false
}'Example streaming request:
curl -N http://127.0.0.1:55990/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-commandcode-api-key" \
-d '{
"model": "deepseek-v4-pro",
"messages": [
{"role": "user", "content": "Write a short poem."}
],
"stream": true
}'Thinking/reasoning (optional): send reasoning_effort
and/or "thinking": {"type": "enabled"}. Deltas arrive
as delta.reasoning_content.
The proxy accepts full model IDs and short aliases:
| Alias | Maps to |
|---|---|
deepseek-v4-pro, deepseek-v4, deepseek-pro |
deepseek/deepseek-v4-pro |
deepseek-v4-flash, deepseek-flash |
deepseek/deepseek-v4-flash |
deepseek-v4.1-flash, deepseek-v4.1 |
deepseek/deepseek-v4.1-flash |
deepseek-v4-flash-fast |
deepseek/deepseek-v4-flash-fast |
kimi-k3, kimi3 |
moonshotai/kimi-k3 |
kimi-k2.7-code, kimi2.7-code |
moonshotai/kimi-k2.7-code |
kimi-k2.7-code-highspeed |
moonshotai/kimi-k2.7-code-highspeed |
kimi-k2.6, kimi2.6 |
moonshotai/kimi-k2.6 |
kimi-k2.5, kimi2.5 |
moonshotai/kimi-k2.5 |
glm-5.3-flash |
z-ai/glm-5.3-flash |
glm-5.3 |
zai-org/glm-5.3 |
glm-5.2 |
zai-org/glm-5.2 |
glm-5.2-fast |
zai-org/glm-5.2-fast |
glm-5.1 |
zai-org/glm-5.1 |
glm-5 |
zai-org/glm-5 |
minimax-m3, minimax3 |
minimaxai/minimax-m3 |
minimax-m2.7, minimax2.7 |
minimaxai/minimax-m2.7 |
minimax-m2.5, minimax2.5, minimax |
minimaxai/minimax-m2.5 |
mimo-v2.5-pro, mimo-pro |
xiaomi/mimo-v2.5-pro |
mimo-v2.5, mimo |
xiaomi/mimo-v2.5 |
qwen-3.8-max-0902 |
qwen/qwen3.8-max-0902 |
qwen-3.8-max, qwen3.8-max |
qwen/qwen3.8-max |
qwen-3.8-27b, qwen3.8-27b |
qwen/qwen3.8-27b |
qwen-3.8-flash, qwen3.8-flash |
qwen/qwen3.8-flash |
qwen-3.7-max, qwen3.7-max |
qwen/qwen3.7-max |
qwen-3.7-plus, qwen3.7-plus |
qwen/qwen3.7-plus |
qwen-3.7-flash, qwen3.7-flash |
qwen/qwen3.7-flash |
qwen-3.6-max-preview, qwen3.6-max |
qwen/qwen3.6-max-preview |
qwen-3.6-plus, qwen3.6-plus, qwen3.6 |
qwen/qwen3.6-plus |
longcat-2.0:free, longcat:free, longcat |
meituan/longcat-2.0:free |
laguna-s-2.1-free, laguna:free, laguna |
poolside/laguna-s-2.1-free |
ling-3.0-flash-sante:free, ling:free, ling |
inclusionai/ling-3.0-flash-sante:free |
step-3.7-flash, step3.7 |
stepfun/step-3.7-flash |
step-3.5-flash, step3.5 |
stepfun/step-3.5-flash |
gemini-3.8-flash, gemini3.8-flash |
google/gemini-3.8-flash |
gemini-3.7-flash, gemini3.7-flash |
google/gemini-3.7-flash |
gemini-3.6-flash, gemini3.6-flash |
google/gemini-3.6-flash |
gemini-3.5-flash, gemini-flash |
google/gemini-3.5-flash |
gemini-3.5-flash-lite, gemini-flash-lite |
google/gemini-3.5-flash-lite |
gemini-3.1-flash-lite |
google/gemini-3.1-flash-lite |
nemotron-3-ultra, nemotron |
nvidia/nemotron-3-ultra-550b-a55b |
Unknown model names are passed through unchanged.
.
├── README.md
├── AGENTS.md
├── .env.example
├── go.mod
├── go.sum
├── main.go
├── systemd
│ └── ccproxy.service
└── internal
├── api
├── proxy
├── server
├── update
└── version
- Client sends an OpenAI-compatible request to the local proxy.
- The proxy extracts system messages, maps the model name, and converts messages to CommandCode format.
- The proxy sends the request to
https://api.commandcode.ai/alpha/generate. - CommandCode streaming NDJSON events are converted back to OpenAI-compatible SSE chunks or collected into a single JSON response.
On startup and when running -version, the proxy calls:
https://api.github.com/repos/dev2k6/command-code-proxy-server/tags
If the latest GitHub tag is newer than the current app version, the version line is displayed as:
v1.3.0 (latest: v1.x.x)
The upstream request includes:
x-command-code-version: 1.50.1The value is pinned (internal/version.Baseline) so the
first chat request never blocks on the npm registry. Bump
the constant when CommandCode requires a newer advertised
CLI version.