Skip to content
AI & Agents
Skill

/run-shunt

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.

BOOST
From plugin
shunt
3023 skills16 agents1 command
Install
$ npx -y skills add pleaseai/shunt --skill run-shunt --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/run-shunt

Context 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.

SKILL.md

run-shunt.SKILL.md
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.

Run shunt

`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).

Prerequisites

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.

Build

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.

Run (agent path) — the smoke driver

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.

Drive it by hand

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 first

Then, 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 error

CLI shape: `shunt run|check [--config <path>]` (also `shunt --check`). Default config path is `./shunt.toml`; `SHUNT_`-prefixed env vars override (with `__` for nesting).

Direct invocation (internal logic — most PRs touch this)

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
Read more
Ships withshunt

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.

Get the whole plugin

Other skills on shunt.

verify
Skill

verify

Use `.claude/skills/run-shunt/SKILL.md` for the standard gateway launch pattern. For protocol adapters, run a local mock upstream…

@pleaseai@pleaseaiInstall Skill