sunny-dispatch
by BrettKnox
README.md
# sunny-dispatch
Zero-dependency semantic task router. Send grunt work to free/local models,
keep premium tokens for judgement.
Most LLM routers (LiteLLM, OpenRouter's own routing, gateway proxies) pick a
model by name, price, or latency. None of them look at **what the task is**.
sunny-dispatch classifies the task itself with deterministic, user-editable
rules (zero tokens spent deciding) and routes it to the cheapest tier that can
do it: summarizing a log goes to your local Ollama model for free; a security
review goes to a strong model; everything routes BYO-key, so this project
never touches your spend.
```
sunny "summarize this build log: <paste>"
[routed: local | qwen2.5-coder:7b | FREE/local | 2841ms]
```
## Install
```
npm i -g sunny-dispatch # or: npx sunny-dispatch --help
```
Requirements: Node 18+. For the free tier: [Ollama](https://ollama.com) with a
model pulled (default `qwen2.5-coder:7b`). For cloud tiers: `OPENROUTER_API_KEY`
(or point any tier at any OpenAI-compatible endpoint).
## Use as a CLI
```
sunny "<task>" # dispatch, auto-routed
sunny --tier strong "<task>" # force a tier
sunny route "<task>" # preview where it would go, spend nothing
sunny doctor # check ollama, keys, config
```
Result goes to stdout, routing info to stderr, so it pipes cleanly.
## Use from Claude Code (MCP)
```
claude mcp add sunny -- npx -y sunny-dispatch mcp
```
Claude gets two tools: `dispatch` (offload a self-contained sub-task, get the
result back as text) and `route_preview` (see the routing decision for free).
## Tiers and cost
| tier | default model | $/1M in | $/1M out |
|---|---|---|---|
| local | qwen2.5-coder:7b (Ollama) | 0 | 0 |
| cheap | deepseek/deepseek-v4-flash | 0.14 | 0.28 |
| strong | deepseek/deepseek-v4-pro | 0.435 | 0.87 |
| quality | anthropic/claude-haiku-4.5 | 1.00 | 5.00 |
Every model, endpoint, and price is a default, not a decision: override any of
it in config. Prices drift; edit yours to match reality.
## Configure
`sunny.config.json` in the working directory (or point `SUNNY_CONFIG` at a
path). Everything is optional; user rules replace the default rules.
```json
{
"rules": [
{ "tier": "quality", "pattern": "\\b(flashcard|anki)", "reason": "study content, never downgrade" },
{ "tier": "strong", "pattern": "\\b(architect|security|debug)", "reason": "needs reasoning" },
{ "tier": "local", "pattern": "\\b(summar|translat|format|extract)", "reason": "mechanical" }
],
"routes": {
"local": { "model": "llama3.2:3b" },
"strong": { "provider": "openai", "baseUrl": "https://api.deepseek.com/chat/completions", "keyEnv": "DEEPSEEK_API_KEY", "model": "deepseek-chat", "inUsd": 0.27, "outUsd": 1.1 }
},
"shortTaskChars": 240,
"shortTaskTier": "local",
"defaultTier": "cheap"
}
```
Rules run in order, first match wins; unmatched short tasks go local,
everything else to `defaultTier`. `sunny route "<task>"` is the fast way to
check what your rules do.
## Why zero dependencies
The whole thing is three small files on Node builtins: the router core, a CLI,
and a hand-rolled MCP stdio server. Nothing phones home, nothing to audit
beyond what fits in one sitting, `npm i` installs nothing else.
## License
MIT © Wukoric LLC
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues