npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sheruq/claude-router

v0.3.3

Published

TypeScript-native Claude API cost router — routes each call to Haiku/Sonnet/Opus/Fable using evidence-based gates, not keyword guesswork, to cut your Anthropic/Bedrock/Vertex bill

Readme


Cut your Anthropic API bill by routing every request to the cheapest Claude model that can handle it — Haiku, Sonnet, or Opus. claude-router is a self-hosted, drop-in proxy: point any Anthropic-compatible app (Claude Code, Cursor, Cline, or your own SDK code) at it with one environment variable and get automatic, complexity-aware model routing — zero code changes, no SaaS in your request path, no per-token markup. Every response reports exactly what you saved.

[claude-router] → haiku  (heuristic, 0ms) | cost: $0.0010 | saved: $0.0030 vs claude-sonnet-5
[claude-router] → opus   (hybrid, 48ms)   | cost: $0.0890 | extra: $0.0740 vs claude-sonnet-5

Is this for you?

If you pay per token (Anthropic API, Amazon Bedrock, or Google Vertex) and your traffic is a mix of easy and hard requests. The router skims the easy majority down to Haiku/Sonnet and reserves Opus for what actually needs it — 35% on agentic coding traffic, measured by replaying 200 real turns through the published proxy.

Contents

Features

| | | |---|---| | 🔌 Zero code changes | Point any Anthropic app at the proxy via ANTHROPIC_BASE_URL, or import it as a library. | | 🧠 Evidence-based routing | Conjunctive gates on request shape, not a keyword score. Sonnet is the default; leaving it needs positive evidence. See research/ for the measurements behind it. | | 🛟 Quality-safe | Sonnet floor for tool-using sessions, plus auto-escalation on truncation/refusal and auto-fallback on rate limits. | | 📊 Measurable | Every call reports exact cents saved vs your baseline, with lifetime stats and a live dashboard. | | 💯 Correct by construction | Per-tier parameter normalization (so routed requests don't 400) and prompt-cache-aware pricing. | | 🏠 Self-hosted & Claude-only | No third party sees your traffic; tuned specifically for the Haiku/Sonnet/Opus/Fable tiers. |


Quick start

Claude Code (recommended)

Two commands on Windows, macOS, or Linux:

npm install -g @sheruq/claude-router
claude-router install --force-route
# open a new terminal, then use `claude` normally — every call is auto-routed

Install globally (not via npx) when using install: login autostart points at the installed CLI, and the claude-router command must stay on your PATH for status / stop / doctor.

install starts the proxy in the background (verifying it's healthy before reporting success), registers it to start on login, sets ANTHROPIC_BASE_URL, and adds a Claude Code statusline — per OS:

| OS | Autostart | Env var | |----|-----------|---------| | Windows | HKCU Run key | setx (applies to new terminals) | | macOS | LaunchAgent | block in ~/.zshrc | | Linux | systemd user unit (graceful fallback) | block in ~/.bashrc / ~/.zshrc |

Manage it anytime:

claude-router status    # health, routing stats, install state
claude-router stats     # lifetime savings + per-day breakdown
claude-router logs -f   # follow the daemon log
claude-router doctor    # diagnose setup problems
claude-router stop      # stop the background proxy
claude-router uninstall # remove everything install added

Watch routing live in the logs, or open the dashboard at http://localhost:4000/dashboard.

Why --force-route? Claude Code always pins a model, so the proxy must override it to route by complexity. The router reconciles model-specific parameters with the tier it picks (see How it works), so force-routing never 400s on Claude Code's adaptive-thinking / effort settings. Drop the flag if you want explicit model requests to pass through untouched.

Any app, without installing

1. Start the proxy in one terminal:

npx @sheruq/claude-router start --port 4000 --force-route --verbose

2. Point your app at it in another:

# macOS / Linux
export ANTHROPIC_BASE_URL=http://localhost:4000
claude
# Windows (PowerShell)
$env:ANTHROPIC_BASE_URL = "http://localhost:4000"
claude

How it works

For each request the proxy classifies the task, routes to the right tier, calls the API, and returns the response plus x-router-* headers:

  • Routemodel: "auto" (or omitted) is classified and routed. An explicit model passes through unchanged unless --force-route is set.
  • Coordinator pin (optional) — with --session-model <tier>, the Claude Code main session is pinned to that tier (e.g. opus for a large context window and no early compaction) while subagents still route by evidence. It keys off the gateway protocol's x-claude-code-agent-id header, which Claude Code sets only on subagent requests — so a request without it is the coordinator. Needs --force-route (otherwise the client's pinned model already passes through). See Pin the coordinator session.
  • Sonnet by default — leaving it needs positive evidence. Gates are conjunctive, so coincidental keyword matches can't sum into an expensive verdict.
  • Loop-position aware — a turn answering a tool_result is a mid-loop step, and those are largely tier-insensitive. Agentic turns never reach Haiku unless routing.allowHaikuInAgentic: true.
  • Auto-retry — a truncated or refused response escalates one tier and retries.
  • Auto-fallback — a rate-limited (429) tier falls back to the next tier up.
  • Parameter normalization — the router owns the model, so it adapts model-coupled params to the chosen tier (otherwise a request built for one model 400s on another). Haiku: strips thinking and output_config.effort. Sonnet/Opus/Fable: strips temperature/top_p/top_k and converts a fixed thinking.budget_tokens to adaptive thinking; Fable additionally rejects disabled thinking, so that is dropped too. Your messages, system, tools, and max_tokens are never touched.

Classification modes

| Mode | Overhead | How it decides | |------|----------|----------------| | heuristic | ~0ms | Conjunctive gates on request shape: tool use, loop position, message count, length, code fences, explicit depth markers. | | ai | one Haiku call (~$0.00004) | Asks Haiku to rate task complexity 1–3. | | hybrid (default) | 0ms, or one Haiku call | Gates first; asks Haiku only when no gate fired and routing fell through to the default. |

Every decision carries a reason (e.g. agentic:mid-loop), so routing is auditable. Fable is opt-in (routing.allowFable). AI classification is resilient by design — results are cached (LRU 500), calls time out at 1.5s, and any failure falls back to the heuristic, so a Haiku outage never blocks a request.

Pin the coordinator session

Claude Code's main interactive session is your coordinator — it holds the growing plan and delegates work to subagents. If it routes to Sonnet, its 200k context window fills fast and compaction kicks in early. --session-model opus keeps that one session on Opus (large window, no early compaction) while everything the router does for subagents stays intact:

claude-router start --force-route --session-model opus
# or in ~/.claude-router/config.json:  "sessionModel": "opus"

How it tells them apart: Claude Code sets the x-claude-code-agent-id header only on requests from a subagent it spawned (gateway protocol). A request without it is the coordinator, so it gets pinned (classifier skipped, x-router-classifier: pinned); a request with it routes by evidence as usual. To route subagents down to cheaper tiers, give them lower models in their .claude/agents/*.md frontmatter — the pin never touches them. Only takes effect under --force-route; without it, the client's explicit model already passes through untouched.

The pin also requires the request to carry tools, which every real coordinator turn does. Claude Code's own meta-calls — generating a session title, summarising a conversation — arrive without the agent-id header too, but with no tools and the conversation quoted in <session>…</session>. Those keep routing by evidence, so pinning opus doesn't pay Opus rates to name a session.


Configuration

Set defaults once in ~/.claude-router/config.json instead of passing flags. CLI flags always override the file. Scaffold it with claude-router init --force-route --port 4000.

{
  "port": 4000,
  "classifier": "hybrid",
  "forceRoute": true,
  "verbose": false,
  "sessionModel": "opus",
  "tiers": {
    "haiku": "claude-haiku-4-5",
    "sonnet": "claude-sonnet-5",
    "opus": "claude-opus-5"
  },
  "pricing": {
    "claude-opus-5": { "input": 5.0, "output": 25.0 }
  },
  "routing": {
    "haikuMax": 30,
    "opusMin": 70,
    "hybridBand": [40, 60],
    "aiTimeoutMs": 1500,
    "classifyCacheSize": 500,
    "allowHaikuInAgentic": false
  }
}
  • sessionModel — pin the Claude Code coordinator session to a tier (subagents still route). See Pin the coordinator session.
  • tiers — override which model ID each tier maps to.
  • pricing — override $/1M token rates used for savings math (handy for enterprise/negotiated rates).
  • routing — tune the classifier. allowHaikuInAgentic: true lets clearly-trivial tool-using turns reach Haiku (off by default — see the agentic floor).

CLI reference

claude-router install [options]     One-time setup: daemon + login autostart + env var + statusline
claude-router uninstall             Remove everything install added
claude-router start [options]       Run the proxy in the foreground
claude-router start -d              Run it in the background (daemon)
claude-router stop                  Stop the background proxy
claude-router restart [options]     Restart the background proxy
claude-router status                Health, routing stats, install state
claude-router stats [--json]        Lifetime savings and per-day breakdown
claude-router logs [-f] [-n N]      Show (or follow) the daemon log
claude-router init [--force] [options]  Scaffold ~/.claude-router/config.json from the given options
claude-router doctor                Diagnose common setup problems
claude-router --version, -V         Print version

Options (install / start / restart / status / doctor):
  --port, -p <number>    Port (default: 4000)
  --host <address>       Bind address (default: 127.0.0.1 — local only)
  --force-route          Route every request, ignoring the client's model (needed for Claude Code)
  --session-model <tier> Pin the Claude Code coordinator session to a tier (haiku|sonnet|opus|fable); subagents still route. Needs --force-route
  --verbose, -v          Log routing decisions
  --classifier <mode>    heuristic | ai | hybrid (default: hybrid)
  --provider <mode>      anthropic | bedrock | vertex (default: anthropic)
  --region <string>      AWS/GCP region

Install-only options:
  --no-autostart         Skip login autostart registration
  --no-env               Skip setting ANTHROPIC_BASE_URL
  --no-statusline        Skip the Claude Code statusline

Something not routing? Run claude-router doctor — it checks the Node version, config validity, proxy health on your port, ANTHROPIC_BASE_URL, credentials, stale daemon state, autostart, and the statusline, with a fix hint for anything that fails.


Pricing & savings

Each response's savedCents is (baseline cost − actual cost) for the tokens used, where the baseline is your defaultModel (Sonnet by default). Prompt-cache tokens are included — reads bill at 10% of the input rate and writes at 125% — so figures stay accurate for cache-heavy clients like Claude Code. Every routed request is appended to ~/.claude-router/history.jsonl, so savings survive restarts (claude-router stats / the dashboard's Lifetime Saved card).

history.jsonl is append-only by design and never rotated — it is the lifetime-savings ledger (~250 bytes/event; a million routed requests ≈ 250 MB). Archive or delete it anytime to start fresh; stats restart from zero. The daemon log (~/.claude-router/proxy.log) does rotate: past 5 MiB it rolls over to proxy.log.1 on the next daemon start.

Pricing tracks the current Claude generation; unknown/dated/Bedrock/Vertex IDs are priced by family so the math stays correct across model launches:

| Model | ID | Input $/1M | Output $/1M | |-------|-----|-----------:|------------:| | Claude Opus 5 | claude-opus-5 | $5.00 | $25.00 | | Claude Sonnet 5 | claude-sonnet-5 | $3.00 | $15.00 | | Claude Haiku 4.5 | claude-haiku-4-5 | $1.00 | $5.00 |

Sonnet 5 has an introductory rate of $2.00 / $10.00 per 1M through 2026-08-31. Savings use the standard $3.00 / $15.00 so numbers stay stable when the intro ends — override via pricing to reflect the intro rate, or for negotiated/enterprise rates.

Every response also carries the decision as headers:

x-router-tier: haiku
x-router-model: claude-haiku-4-5
x-router-cost-cents: 0.045
x-router-saved-cents: 1.200
x-router-classifier: heuristic
x-router-classifier-ms: 0.1
x-router-confidence: 0.9

Other providers

The proxy binds to 127.0.0.1 by default — only your own machine can reach it.

⚠️ Fable on Bedrock / Vertex is not wired up. Fable 5's inference-profile ID on those platforms is unverified, so the fable tier resolves to Opus there rather than risking a 404 — a request routed to fable on Bedrock or Vertex silently runs on Opus instead. This is inert while routing.allowFable is off (the default). If you enable fable on Bedrock or Vertex, set the real ID yourself via tiers.fable in ~/.claude-router/config.json and verify it against your console first. On the first-party Anthropic API, fable routes to claude-fable-5 as expected.

AWS Bedrock

npm install @anthropic-ai/bedrock-sdk

AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_REGION=us-east-1 \
  npx @sheruq/claude-router start --provider bedrock --port 4000
# auth comes from AWS env vars — no x-api-key needed
export ANTHROPIC_BASE_URL=http://localhost:4000

Google Vertex AI

npm install @anthropic-ai/vertex-sdk
gcloud auth application-default login

ANTHROPIC_VERTEX_PROJECT_ID=my-project \
  npx @sheruq/claude-router start --provider vertex --port 4000

Use as a library

Prefer to route inside your own app instead of running a proxy? Import it directly.

npm install @sheruq/claude-router
import { createRouter } from '@sheruq/claude-router';

const router = createRouter({ apiKey: process.env.ANTHROPIC_API_KEY!, verbose: true });

const response = await router.send({
  messages: [{ role: 'user', content: 'Translate to French: Hello world' }],
  max_tokens: 100,
});

console.log(response.meta.tier);       // 'haiku'
console.log(response.meta.savedCents); // 1.2

Streamingawait router.stream(...) classifies first, then returns the real SDK MessageStream; meta resolves after the stream completes:

const { stream, meta } = await router.stream({
  messages: [{ role: 'user', content: 'Write a detailed essay on quantum computing' }],
  max_tokens: 4096,
});

for await (const event of stream) {
  // standard @anthropic-ai/sdk stream events; .on()/.finalMessage() work too
}

const routeMeta = await meta;
console.log(routeMeta.tier); // 'sonnet'

Force a tier / read session stats:

await router.send({ messages: [/* … */], max_tokens: 50, tier: 'opus' }); // skip the classifier

console.log(router.stats());
// { totalCostCents: 5.3, totalSavedCents: 47.3, callCount: 120,
//   tierBreakdown: { haiku: 72, sonnet: 41, opus: 7 } }
const router = createRouter({
  apiKey: 'sk-...',
  classifier: 'hybrid',            // 'heuristic' | 'ai' | 'hybrid' (default: 'hybrid')
  defaultModel: 'claude-sonnet-5', // baseline for savings (default: sonnet tier)
  tiers: {                         // override model IDs per tier
    haiku: 'claude-haiku-4-5',
    sonnet: 'claude-sonnet-5',
    opus: 'claude-opus-5',
  },
  pricing: {                       // override $/1M token pricing
    'claude-sonnet-5': { input: 3.0, output: 15.0 },
  },
  fallback: true,                  // auto-fallback to next tier on rate limit (default: true)
  verbose: true,                   // log routing decisions (default: false)
  routing: {                       // classifier tuning (defaults shown)
    haikuMax: 30,                  // score below this → haiku
    opusMin: 70,                   // score above this → opus
    hybridBand: [40, 60],          // hybrid confirms with AI inside this band
    aiTimeoutMs: 1500,             // AI classifier timeout → heuristic fallback
    classifyCacheSize: 500,        // LRU size for AI results (0 disables)
    allowHaikuInAgentic: false,    // let trivial tool-using turns reach haiku (default: floor at sonnet)
  },
});

Every send / stream response includes a meta object:

interface RouteMeta {
  tier: 'haiku' | 'sonnet' | 'opus';
  model: string;
  inputTokens: number;
  outputTokens: number;
  costCents: number;          // actual cost in cents
  savedCents: number;         // vs baseline (negative when routed to opus)
  classifierMethod: 'heuristic' | 'ai';
  classifierMs: number;
  confidence: number;         // 0–1
  fallbackUsed: boolean;      // rate-limited and escalated
  retried: boolean;           // auto-retried on bad output
  retryReason: string | null; // 'truncation' | 'refusal' | null
}

Authentication & security

The proxy forwards whatever credentials your app already sends — no extra config:

| Method | Header | Use case | |--------|--------|----------| | API key | x-api-key: sk-ant-... | Anthropic API (pay-per-token) | | Bearer token | Authorization: Bearer <token> | Claude Code / subscription auth | | Env vars | — | AWS Bedrock, Google Vertex AI |

Security. The proxy binds to 127.0.0.1 by default — only your own machine can reach it. If you pass --host 0.0.0.0 to share it on a network, note that with the bedrock/vertex providers it calls out with your cloud credentials and does not authenticate incoming requests.


Contributing

Contributions welcome! See CONTRIBUTING.md to go from clone to merged PR — the short version is npm install && npm test, branch, add a test, open a PR. master is protected: changes merge by squash after review and green CI. For security reports, see SECURITY.md.

License

MIT