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

@srscalzo/clauderouter

v1.0.0

Published

Automatic cost optimization and routing layer for the Anthropic Claude API

Readme

Claude Router

Automatic model routing and token tracking for the Anthropic Claude API.

Routes every prompt to the cheapest Claude model likely to succeed, escalates if the response is insufficient, and tracks usage over time.

Target savings: 20–40% on Claude API spend with no code changes beyond the import.


How It Works

Your prompt
    │
    ▼
┌─────────────┐     score < 2    ┌─────────────────────────┐
│  Classifier │ ───────────────► │  Haiku  (Tier 1 — fast) │
│  (heuristic)│     score 2–3    ├─────────────────────────┤
│             │ ───────────────► │  Sonnet (Tier 2 — smart)│
│             │     score ≥ 4    ├─────────────────────────┤
└─────────────┘ ───────────────► │  Opus   (Tier 3 — best) │
                                 └─────────────────────────┘
                                           │
                          Response too short / uncertain?
                                           │ yes
                                           ▼
                                    Escalate to next tier
                                           │
                                           ▼
                                    Log result to JSONL
  1. Classify — Scores the prompt using heuristics: token count, code blocks, complexity keywords (architect, refactor, optimize, …), conversation length, and system prompt size.
  2. Route — Picks the cheapest model for the score: Haiku → Sonnet → Opus.
  3. Escalate — If the response looks insufficient (too short, contains uncertainty phrases like "I'm not sure"), retries with the next model up.
  4. Log — Appends a JSONL record to ~/.clauderouter/logs.jsonl with model, tokens, latency, and escalation count.

Requirements


Installation

npm install clauderouter

Setup

Set your API key as an environment variable:

# .env or shell profile
export ANTHROPIC_API_KEY="sk-ant-..."

Or pass it directly to the constructor (see usage below).


Usage

Basic — automatic routing

import { ClaudeRouter } from 'clauderouter';

const router = new ClaudeRouter({
  apiKey: process.env.ANTHROPIC_API_KEY!,
});

const response = await router.run({
  messages: [{ role: 'user', content: 'What is 2 + 2?' }],
  max_tokens: 100,
});

console.log(response.content[0].text);
// → "4"
// Used Haiku automatically — costs ~18× less than Opus

router.run() is a drop-in replacement for client.messages.create(). It returns the same Anthropic.Message type, so no downstream changes are needed.

Migrating from the Anthropic SDK

// Before
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const response = await client.messages.create({
  model: 'claude-sonnet-4-6',
  messages: [...],
  max_tokens: 1024,
});

// After — delete the model field, swap the import
import { ClaudeRouter } from 'clauderouter';
const router = new ClaudeRouter({ apiKey: process.env.ANTHROPIC_API_KEY! });
const response = await router.run({
  //  model: 'claude-sonnet-4-6',  ← remove this line
  messages: [...],
  max_tokens: 1024,
});

Force a specific model

When you know which model you need, pass model explicitly — the classifier is skipped:

const response = await router.run({
  messages: [{ role: 'user', content: 'Redesign our entire auth system.' }],
  max_tokens: 4096,
  model: 'claude-opus-4-7',
});

With tools

All Anthropic SDK parameters are supported:

const response = await router.run({
  messages: [{ role: 'user', content: 'What files are in the current directory?' }],
  max_tokens: 1024,
  tools: [
    {
      name: 'list_files',
      description: 'List files in a directory',
      input_schema: {
        type: 'object',
        properties: { path: { type: 'string' } },
        required: ['path'],
      },
    },
  ],
});

Multi-turn conversation

const messages: Anthropic.MessageParam[] = [];

messages.push({ role: 'user', content: 'Explain closures in JavaScript.' });
const first = await router.run({ messages, max_tokens: 512 });

messages.push({ role: 'assistant', content: first.content });
messages.push({ role: 'user', content: 'Now show me a practical example.' });
const second = await router.run({ messages, max_tokens: 512 });

Configuration

const router = new ClaudeRouter({
  apiKey: string;             // required — your Anthropic API key

  maxRetries?: number;        // max escalation/retry attempts (default: 2)
  disableEscalation?: boolean;// skip escalation checks entirely (default: false)
  logPath?: string;           // JSONL log file path (default: ~/.clauderouter/logs.jsonl)
});

CLI

Install globally or use npx:

npm install -g clauderouter
# or
npx clauderouter <command>

ask — one-shot query

claude-router ask "What is the time complexity of quicksort?"

# Force a specific model
claude-router ask "Refactor this service layer" --model claude-opus-4-7

# Custom log path
claude-router ask "Hello" --log-path ./my-logs.jsonl

Output goes to stdout; metadata (model, tokens, latency) goes to stderr so you can pipe responses cleanly:

claude-router ask "Write a haiku about Go channels" > haiku.txt

chat — interactive session

Starts a persistent session with your project's file tree injected as a cached system prompt. Context is sent once and cached by Anthropic for 5 minutes — subsequent turns cost ~10% of the first turn's price.

claude-router chat

# Skip context injection
claude-router chat --no-context

# Force a model for all turns
claude-router chat --model claude-sonnet-4-6

Type exit or quit to end the session. A summary of models used and total tokens is printed on exit.

stats — usage analytics

claude-router stats

# Custom log path
claude-router stats --log-path ./my-logs.jsonl

Example output:

 Claude Router — Usage Analytics
────────────────────────────────────────────────────────
  Total requests:              42
  Total input tokens:          18,432
  Total output tokens:          3,891
────────────────────────────────────────────────────────
  Model distribution:
    claude-haiku-4-5-20251001:   35 (83.3%)
    claude-sonnet-4-6:            6 (14.3%)
    claude-opus-4-7:              1 (2.4%)
────────────────────────────────────────────────────────
  Total escalations:           3
  Avg latency:                 412 ms
────────────────────────────────────────────────────────

Log Format

Each request appends one JSON line to ~/.clauderouter/logs.jsonl:

{
  "timestamp": "2026-05-13T10:22:01.443Z",
  "model_used": "claude-haiku-4-5-20251001",
  "model_intended": "claude-haiku-4-5-20251001",
  "input_tokens": 312,
  "output_tokens": 87,
  "latency_ms": 408,
  "retries": 0,
  "escalations": 0,
  "prompt_tier": 1
}

You can pipe it to jq for custom queries:

# All escalated requests
jq 'select(.escalations > 0)' ~/.clauderouter/logs.jsonl

# Average latency
jq -s '[.[].latency_ms] | add / length' ~/.clauderouter/logs.jsonl

# Model distribution
jq -r '.model_used' ~/.clauderouter/logs.jsonl | sort | uniq -c | sort -rn

Development

git clone https://github.com/srscalzo/claude-router.git
cd claude-router
npm install

npm test          # run all tests
npm run lint      # type-check without building
npm run build     # compile to dist/

Project structure

src/
├── index.ts        # public API barrel
├── types.ts        # shared interfaces
├── classifier.ts   # heuristic complexity scorer
├── escalation.ts   # response quality checker
├── pricing.ts      # model routing + escalation chain
├── logger.ts       # JSONL file writer
├── router.ts       # ClaudeRouter class
└── cli/
    ├── index.ts    # CLI entrypoint
    ├── ask.ts      # one-shot query command
    ├── chat.ts     # interactive session command
    ├── stats.ts    # analytics aggregator + formatter
    ├── context.ts  # project file tree builder
    └── spinner.ts  # terminal animation
tests/
├── classifier.test.ts
├── escalation.test.ts
├── pricing.test.ts
├── logger.test.ts
└── router.test.ts

License

MIT