pleaseai/shunt/tree/main/plugins/shunt
shunt
공유 shunt gateway 계정 풀 사용량에 /shunt:usage로 답하는 Claude Mod입니다. 동작은 function hooks 모듈인 Claude Code plugin으로 구현됩니다.
이 mod 소개
shunt gateway용 Claude Mod입니다. /shunt:usage는 shunt의 GET /usage에서 공유 계정 풀의 남은 여유와 재설정 시각을 읽으므로 관리 대시보드를 열지 않고도 풀이 throttling에 얼마나 가까운지 확인할 수 있습니다.
설치
먼저 작성자의 README에서 marketplace와 플러그인 이름을 확인하세요. 저장소 구조에 따라 명령어가 달라질 수 있습니다.
claude plugin marketplace add pleaseai/shunt claude plugin install shunt
원문 / README
shunt
A Claude Mod — a Claude Code plugin whose behaviour is a
function hooks module —
that answers /shunt:usage with the shunt
gateway's shared account pool usage.
> /shunt:usage
shunt: pool — degraded http://127.0.0.1:3001
5h ▓▓▓▓▓▓░░░░ 62% left resets 04:11
7d ▓▓▓▓▓▓▓▓░░ 81% left resets Sun 01:11
fable ▓▓░░░░░░░░ 19% left resets Sun 01:11
claude ok 5h 71% 7d 84% fable 19%
codex exhausted 5h 0% 7d 40% fable —
headroom left, averaged over the pool's accounts; a shared figure, not a promise about your next request
The mod reads the gateway's GET /usage
endpoint and prints it. It answers the command itself — the hook returns without
calling next, so nothing is sent to the model and the answer costs no tokens.
Reading the numbers
remaining is the fraction of the pool's combined capacity still usable, so
62% means 62% of the headroom is left, not that 62% is spent. It is
mean(clamp(cap - utilization, 0, 1)) over the non-disabled accounts that report
the window, where cap is the account's max_utilization hard cap for that
window (100% when none is set): nine exhausted accounts plus one fresh uncapped
one read 10%, not 100%, and an account at 44% under a 50% cap counts only 6%.
An account a cap already excludes from the window's requests counts zero; a 5h or
7d cap excludes it from every request, Fable ones included.
It is a pool-wide aggregate, not a prediction — routing also weighs
availability, model, session affinity and priority, so a healthy figure is not a
promise that your next request is admitted. For the routing-aware worst case, use
GET /api/oauth/usage instead.
| Window | What it covers |
| ------- | -------------- |
| 5h | The rolling 5-hour session window |
| 7d | The shared weekly window |
| fable | The Fable-scoped weekly window (7d_oi) |
A window reads — when no non-disabled account reports it. ChatGPT/Codex
accounts populate 5h and 7d from x-codex-* response headers and have no
Fable-scoped signal of their own.
pool is the aggregate across every pooled provider; the rows beneath it are the
same aggregate per pooled provider, so a session routed to one provider can read
that provider's headroom instead of the blended figure. The endpoint never
carries account names, counts, priorities or per-account numbers — that detail
stays behind the admin-only GET /admin/api/pool.
Prerequisites
-
Point Claude Code at your gateway, as you already do to route through it:
export ANTHROPIC_BASE_URL=http://127.0.0.1:3001 export ANTHROPIC_AUTH_TOKEN=<your client token> -
Enable the endpoint on the gateway.
GET /usageis opt-in and requires[server.auth], so both tables must be present inshunt.toml:[server.auth] # Presence alone opts in; the table takes no keys. [server.usage][server.auth]takes its tokens from the environment rather than the TOML — by defaultSHUNT_CLIENT_TOKENS, asname:tokenpairs — and the gateway fails to start when it is unset. Export it where the gateway runs, using the same token you set asANTHROPIC_AUTH_TOKENabove:export SHUNT_CLIENT_TOKENS="claude-code:<your client token>" -
Run Claude Code with function hooks enabled — the feature is early access:
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude
Without step 3 the command still exists, but falls back to commands/usage.md,
which asks the model to read the endpoint with a tool call instead.
Install
/plugin marketplace add pleaseai/shunt
/plugin install shunt@shunt
Configuration
The mod reads five environment variables and writes none. By default it sends the
session's own credential to the gateway the session is already sending every
message to, so it reaches no host the session was not already using;
SHUNT_BASE_URL is the one deliberate exception, and points it at a gateway you
name instead. With no base URL set at all it asks nothing rather than sending the
credential to Anthropic's API.
| Variable | Purpose |
| -------- | ------- |
| SHUNT_BASE_URL | The gateway base URL; overrides ANTHROPIC_BASE_URL |
| ANTHROPIC_BASE_URL | The gateway this session already routes through |
| SHUNT_TOKEN | The client token; overrides both below, sent as Authorization: Bearer |
| ANTHROPIC_AUTH_TOKEN | Sent as Authorization: Bearer, as Claude Code sends it |
| ANTHROPIC_API_KEY | Sent as x-api-key, as Claude Code sends it |
SHUNT_BASE_URL is what lets you read one gateway's pool while routing traffic
through another.
apiKeyHelper-only sessions
The mod reads the credential from the environment and nowhere else, so a session
whose credential arrives through apiKeyHelper has none it can see: Claude Code
consumes the helper's output internally and never re-exports it. That covers both
shunt gateway claude, whose launcher scrubs ANTHROPIC_AUTH_TOKEN and
ANTHROPIC_API_KEY from the environment it hands Claude Code and wires
apiKeyHelper instead, and a hand-configured apiKeyHelper. In those sessions
/shunt:usage reports that it has no client token even though the session itself
is authenticating fine.
Export a client token for the mod to use alongside it — one of the name:token
pairs the gateway was given in SHUNT_CLIENT_TOKENS:
export SHUNT_TOKEN=<your client token>
It has to be a [server.auth] client token, and not the output of shunt gateway token: that command prints the [server.gateway] session's access token, which
is a separate credential that rotates on refresh. GET /usage authenticates
only against the configured client tokens, so a session token sent as a Bearer
is refused with a 401.
Why /shunt:usage and not /usage
/usage is Claude Code's own built-in, and the engine refuses to let a plugin
take a built-in's name:
$.command.register: "/usage" refused: it is the built-in /usage
$.command.register takes a bare, global name. A plugin's markdown command
is namespaced by the plugin instead, so commands/usage.md in a plugin named
shunt lists as shunt:usage and collides with nothing. The hooks module then
intercepts command.run for that name and answers it directly.
Layout
| Path | What it is |
| ---- | ---------- |
| hooks/register.ts | The one hook: command.run on shunt:usage |
| hooks/endpoint.ts | Resolves the gateway URL and credential header |
| hooks/report.ts | Reads a GET /usage body, defensively |
| hooks/views.ts | Renders the bars, percentages and reset times |
| hooks/names.ts | The command name and the fixed texts |
| commands/usage.md | The command, and the no-function-hooks fallback |
| tests/ | Vitest suite over the pure modules |
Development
# The static side-effect analysis: which events it hooks, which $ calls and
# environment variables it makes and reads.
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude plugin validate plugins/shunt
# The pure modules' suite and typecheck (both CI gates).
cd plugins/shunt && npm ci && npm run typecheck && npm test
# Run it from source against a live gateway.
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir plugins/shunt
hooks/register.ts is the only file that imports claude-code. Typechecking it
needs the engine's own claude-code.d.ts, which Claude Code writes into a plugin
author's project via /plugin-types; the other modules import nothing from the
engine, which is why the suite covers them without it. For the same reason
tsconfig.json excludes hooks/register.ts from npm run typecheck — that
exclusion is deliberate, not an oversight, and typechecking that one file means
running /plugin-types first.
Function hooks are early access. Hooks modules load only where they are enabled, and the API this mod is written against may change between Claude Code releases without notice.
License
MIT OR Apache-2.0, matching the shunt project.
동명의 다른 작품
- shuntmiguelrisero · ★ 0
