run-claude-gateway-ref
Launch and drive a local reference Claude apps gateway (claude gateway + Dex + Postgres) to probe, capture, or re-verify the…
Build, launch, and drive shunt — the Claude Code LLM gateway (a Rust/axum Anthropic-Messages proxy). Use to run, start, smoke-test, or curl-drive the gateway, exercise /v1/models discovery and /v1/messages proxying, or connect Claude Code to a local shunt instance.
$ npx -y skills add pleaseai/shunt --skill run-shunt --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/run-shuntContext preview
The summary Claude sees to decide when to auto-load this skill.
Build, launch, and drive shunt — the Claude Code LLM gateway (a Rust/axum Anthropic-Messages proxy). Use to run, start, smoke-test, or curl-drive the gateway, exercise /v1/models discovery and /v1/messages proxying, or connect Claude Code to a local shunt instance.
name: run-shunt description: Build, launch, and drive shunt — the Claude Code LLM gateway (a Rust/axum Anthropic-Messages proxy). Use to run, start, smoke-test, or curl-drive the gateway, exercise /v1/models discovery and /v1/messages proxying, or connect Claude Code to a local shunt instance.
`shunt` is a Rust ([axum](https://github.com/tokio-rs/axum)) HTTP server — a Claude Code LLM gateway. It has **no GUI**: you drive it with `curl`. It listens on `127.0.0.1:3001` by default and serves an Anthropic-Messages surface (`GET /v1/models`, `POST /v1/messages`, `POST /v1/messages/count_tokens`, `HEAD /`). For each mapped `model` id it diverts inference to another provider (OpenAI / Codex / ChatGPT via the OpenAI Responses API); everything else passes through to Anthropic.
**Primary agent path:** run the committed driver [`.claude/skills/run-shunt/smoke.sh`](smoke.sh). It builds the binary, spins up a local mock upstream (so no real API key is needed), launches the gateway, and drives every route end to end with assertions. That is the way to confirm a change works.
All paths below are relative to the repo root (the `shunt/` directory).
Everything the driver needs is already standard on macOS/Linux dev boxes:
cargo --version # Rust stable (built with 1.94); toolchain via rust-toolchain.toml if present python3 --version # smoke.sh uses http.server as a stand-in upstream curl --version jq --version # smoke.sh asserts JSON responses with jq lsof -v
On a bare Ubuntu container: `apt-get install -y curl jq lsof python3` and install Rust via `rustup` if `cargo` is missing.
BIN="$(cargo build --locked --message-format=json-render-diagnostics | jq -sr '[.[] | select(.reason == "compiler-artifact" and .target.name == "shunt" and (.target.kind | index("bin"))) | .executable | select(. != null)] | last // empty')"
test -x "$BIN"`BIN` resolves Cargo's actual executable path, including `CARGO_TARGET_DIR` and a configured target triple.
This is what you run to see shunt working. It is hermetic (no network, no credentials) and exits non-zero on the first failed assertion.
.claude/skills/run-shunt/smoke.sh
Expected tail:
PASS shunt check -> config ok PASS HEAD / -> 200 (server live) PASS GET /v1/models returns configured model PASS POST /v1/messages proxied to upstream and returned its body PASS malformed request -> 400 invalid_request_error PASS GET /health reports ok PASS GET /protocol describes the anthropic-messages contract PASS GET /routes resolves the configured route PASS POST /v1/messages/count_tokens proxied to upstream on its own path All smoke checks passed.
What it covers: every route the server registers unconditionally. Config validation (`shunt check`), liveness (`HEAD /`), health (`GET /health`), the gateway contract (`GET /protocol`), model discovery (`GET /v1/models`), the resolved route table (`GET /routes`), both proxy forward paths (`POST /v1/messages` and `POST /v1/messages/count_tokens`, routed to a local mock that stands in for `api.anthropic.com` and asserted by the path it forwarded to), and the routing error path (a body with no `model` field → `400 invalid_request_error`). Routes that only an optional config section registers (the Codex endpoint, the usage surfaces) are out of scope, since the smoke config does not enable them.
`SHUNT_PORT` and `MOCK_PORT` default to `31711` and `31712`. Set either to `0` to bind an ephemeral port instead, which is the conflict-free way to run the driver beside a live gateway.
The driver has its own regression, [`test_smoke.sh`](test_smoke.sh). Run it after editing `smoke.sh`. It proves the driver refuses a port outside `0..65535` by name; refuses a port another listener already holds, where the real process dies at bind and the driver says which one died; and refuses a shunt listening on a port it never requested, which is the case the pid-derived readiness check catches. It then runs the normal path. The stand-in listener answers every assertion the driver makes, so a driver that trusted `$SHUNT_PORT` over the process it started would go green against it and get caught.
Write a config, launch, and curl it yourself:
BIN="$(cargo build --locked --message-format=json-render-diagnostics | jq -sr '[.[] | select(.reason == "compiler-artifact" and .target.name == "shunt" and (.target.kind | index("bin"))) | .executable | select(. != null)] | last // empty')"
test -x "$BIN"
"$BIN" run --config ./shunt.toml # or copy shunt.toml.example firstThen, against the running server:
curl -s "http://127.0.0.1:3001/v1/models?limit=1000" | jq .
# => {"data":[{"id":"claude-opus-via-codex","display_name":"Opus (via Codex)"}]}Validate a config without starting the server:
BIN="$(cargo build --locked --message-format=json-render-diagnostics | jq -sr '[.[] | select(.reason == "compiler-artifact" and .target.name == "shunt" and (.target.kind | index("bin"))) | .executable | select(. != null)] | last // empty')"
test -x "$BIN"
"$BIN" check --config ./shunt.toml # prints "config ok" or a precise errorCLI shape: `shunt run|check [--config <path>]` (also `shunt --check`). Default config path is `./shunt.toml`; `SHUNT_`-prefixed env vars override (with `__` for nesting).
The interesting code is the **Anthropic Messages ⇄ OpenAI Responses translation** (`src/adapters/`, `src/model/`) and routing (`src/routing.rs`). These are covered by unit + integration tests — the fastest inner loop for a PR touching them:
cargo test --workspace # all tests cargo test --test responses_translate # the translation integration suite
Full pre-PR gate (matches CI):
cargo fmt --all --check cargo clippy --all-targets --all-f
Offline credential import: shunt import opencodex --dry-run previews compatible API keys and Cursor/Command Code access tokens. Exports create new private snapshots without changing existing settings or copying refresh tokens. See credential import.
Launch and drive a local reference Claude apps gateway (claude gateway + Dex + Postgres) to probe, capture, or re-verify the…
Use `.claude/skills/run-shunt/SKILL.md` for the standard gateway launch pattern. For protocol adapters, run a local mock upstream…