commandcode-proxy: one file, three API shapes and a faked device
Command Code API 反代代理,兼容 OpenAI 与 Anthropic 接口 | Reverse proxy exposing Command Code API as OpenAI- and Anthropic-compatible endpoints
At a glance
- What is it?
- A reverse proxy that turns the Command Code API into OpenAI Chat Completions, the Responses API and Anthropic Messages endpoints, written as a single dependency-free Node file. Its distinguishing feature is not the translation but the identity it fabricates: a per-key device fingerprint, a project directory and lifecycle pre-requests.
- Who is it for?
- commandcode-proxy fits a developer whose tooling speaks OpenAI or Anthropic and who wants to point it at Command Code without writing an adapter, since three API shapes are covered by one file with no dependencies to audit. It does not fit anyone who needs a supported contract, because the protocol was reconstructed from captured CLI traffic and the version stays at 1.0.0 with no releases.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One file, zero dependencies, and a protocol learned from captures
The whole proxy is a single file, `proxy.mjs`, at roughly 1900 lines, with no runtime dependencies at all. The manifest declares Node 18 or newer, two scripts, and two Docker helpers, one of which is a multi-architecture build for linux/amd64 and linux/arm64.
The method of construction is stated as plainly as the constraint: the request protocol was reconstructed by analysing official CLI network traffic, and the repository keeps that traffic in a `captured-requests/` directory as the reference for the analysis. That directory is the most interesting file in the tree, because it turns an assertion about protocol fidelity into something a reader can check.
The translation surface is three shapes rather than one: OpenAI Chat Completions, the OpenAI Responses API at `/v1/responses`, and the Anthropic Messages API, with streaming and non-streaming, tool calling through `tool_use`, multimodal image input and reasoning effort.
The key arrives in a header and only has to start with user_
There is nothing to log in to. The API key is passed per request in the `Authorization` header, or `x-api-key` for Anthropic SDKs, so it never has to be written into a config file. The one requirement is that the key starts with `user_`, and the matching is deliberately loose: a value like `Bearer token_user_xxx` is accepted because the prefix is matched automatically.
A curl call is the whole smoke test:
curl http://127.0.0.1:3050/v1/chat/completions \
-H "Authorization: Bearer user_xxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek/deepseek-v4-flash","messages":[{"role":"user","content":"hi"}]}'Running it locally is two commands, `npm start` for the proxy and `npm run dev` for watch mode with reload on file changes. The shipped `config.json` listens on port 3050, which is a detail worth knowing since the documented default for the port field is 3000.
Two mode enums that are not the same enum
The configuration exposes two fields both called mode, and they take different value sets, which is the kind of detail that silently breaks a request if you confuse them.
`cliMode` sets the envelope's mode and accepts the upstream enum: agent, learning, custom-agent, custom-agent-create, title-gen, tool-desc, compact and vision. `cliSessionMode` sets the mode inside the lifecycle metadata and takes a smaller enum of its own, interactive and non-interactive.
Around them sit a handful of upstream-behaviour switches. ZDR routing is off by default and, when enabled, adds an `x-cmd-zdr: 1` header to generation requests and to the fingerprint and lifecycle initialisation requests, while deliberately leaving it off the npm version check and the proxy's own model catalogue request. The model list is fetched from the provider API by default and cached for five minutes.
One more field is about prompt hygiene rather than protocol: `emptySystemPlaceholder` defaults to true and sends a space when there is no system prompt, so the upstream does not inject its own default of roughly 7.5K tokens. That is the fix behind issue 17.
The fingerprint is a configurable identity, down to a Windows path
The feature list calls it device fingerprint disguise, per key with automatic refresh, and the configuration turns it into something you can steer. A salt rotates the whole fleet's identity at once, with the caveat that one key still always reports one device. A `deviceProjectDir` field sets the project directory the device claims to be working in, and when it is empty the built-in default is `C:\Users\dev\projects\app`, a Windows path.
That default is the sort of detail that gives a proxy away, which is exactly why it is configurable: changing the directory gives every account a different device rather than a fleet of identical ones.
The lifecycle pre-requests are the other half. Alongside the fingerprint, the proxy performs the initialisation requests the real CLI makes, so a session looks structurally normal to the upstream. The README is candid that this is imitation built from captured traffic, not documented behaviour.
Every timeout and cap is an environment variable
The tuning surface is unusually complete, and it is all in the environment rather than scattered through the file. Streaming and non-streaming reads get separate idle timeouts, 30 seconds and 90 seconds by default. The request body cap is 100 MB, and an oversized request is rejected with 413 while the connection is drained rather than reset.
Tool screenshots get their own budget of 6 MB per request in total, base64 encoded, with older images replaced by a placeholder as the budget runs out, and zero disabling the feature entirely. There is an optional in-flight request cap that returns 503 when exceeded, and by default it is zero, meaning unlimited.
Two of these settings interact with something outside the process. A client drain timeout is disabled by default and drops a client that has stopped reading once downstream backpressure blocks for longer than the configured window. The backend keep-alive timeout defaults to 65 seconds with `headersTimeout` set one second above it automatically, and the README insists it must be larger than the keep-alive timeout of any reverse proxy in front, which is the kind of ordering note that only gets written after someone lost an afternoon to it.
Retries stop at the first byte the client sees
The retry rule is the one that matters most for correctness, and it is stated precisely: upstream disconnects are retried up to two times, and only before any byte has been written downstream. Once the client has seen part of a stream, the proxy cannot safely start again, so it does not.
The backoff is a base of 400 milliseconds multiplied by the attempt number, and setting the maximum to zero disables retries. Separately, two failure modes are translated into 429 responses with automatic retry: a reply that produced no output at all, and a run of consecutive timeouts.
The headers list also documents what is not retried and what is not proxied. The ZDR header is left off the version check and off the model catalogue request, and an optional `CC_UPSTREAM_PROXY` routes requests to the upstream through an HTTP proxy with CONNECT only, not a general forward proxy.
The container copies two files and ignores the shipped config
The Dockerfile is four instructions of substance: a node:22-alpine base, a working directory, a copy of `package.json` and `proxy.mjs`, an exposed port, a health check and the command. Nothing else comes along.
That has a consequence worth noticing. The `config.json` that ships with the repository is not in the image, so the port 3050 the README tells you about is a repository-level fact, not a container one. The container gets its port from the `PORT` environment variable, which is exactly what the compose file sets, along with a published port that defaults to 3050 and a health check that probes a `/health` endpoint with wget every thirty seconds.
So the same tree behaves differently depending on how you start it: from npm you read config.json, from Docker you read the environment. The compose file also carries a `version` key that current Compose releases ignore.
Editorial conclusion
commandcode-proxy fits a developer whose tooling speaks OpenAI or Anthropic and who wants to point it at Command Code without writing an adapter, since three API shapes are covered by one file with no dependencies to audit. It does not fit anyone who needs a supported contract, because the protocol was reconstructed from captured CLI traffic and the version stays at 1.0.0 with no releases. Before you rely on it, read the timeout and keep-alive settings against your own reverse proxy, note that the container ignores the shipped config file, and remember that the ZDR header asks for zero-data-retention routing while the upstream stays the authority on retention.
Frequently asked questions
How do I start commandcode-proxy?
Run `npm start`, and `npm run dev` for watch mode with reload on file changes. The repository ships a config.json listening on http://0.0.0.0:3050. The API key is passed per request in the Authorization header, or x-api-key for Anthropic SDKs, and does not need to be stored in any config file.
Which API shapes does commandcode-proxy expose?
OpenAI Chat Completions, the OpenAI Responses API at `/v1/responses` and the Anthropic Messages API, with streaming and non-streaming, tool calling through tool_use, multimodal image input and reasoning effort. The upstream model list is fetched dynamically and cached for five minutes.
What are the retry rules in commandcode-proxy?
Upstream disconnects are retried up to two times by default, and only before any byte has been written downstream, with a backoff of 400 ms times the attempt number. A reply with no output and a run of consecutive timeouts both become 429 with automatic retry, and an optional in-flight cap returns 503 when it is exceeded.
How does commandcode-proxy handle device identity?
It disguises the device per key with automatic refresh, using a configurable salt to rotate a whole fleet at once, and a configurable fake project directory that defaults to a built-in Windows path when left empty. Lifecycle pre-requests are sent alongside the fingerprint so a session looks structurally normal.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/maxeaglet-commandcode-proxy)