AnyModel Development Skill
You are working on AnyModel — a universal AI coding proxy that lets you use any AI model through one interface.
This skill contains everything you need: architecture, conventions, deployment rules, and debugging guidance.
Product Identity
- Name: AnyModel (capital A, capital M — never "anymodel" or "Any Model" in user-facing text)
- Package:
anymodel on npm
- Website: https://anymodel.dev
- GitHub (proxy): https://github.com/anton-abyzov/anymodel
- Client bundle:
cli.js in this repo/package is the shipped Claude Code-compatible TUI
- Author: Anton Abyzov (@aabyzov on X, @AntonAbyzov on YouTube)
- License: MIT
Architecture
AnyModel client (cli.js) --> anymodel proxy (:9090) --> OpenRouter / Ollama / OpenAI-compatible
Three components:
- Proxy (
proxy.mjs) — HTTP server intercepting /v1/messages, sanitizes requests, translates formats, routes to providers
- CLI (
cli.mjs) — entry point for starting proxy, connecting, managing presets
- Bundled client (
cli.js) — modified Claude Code client with violet diamond character and AnyModel branding
Providers
| Provider |
File |
API Format |
Key Behavior |
| OpenRouter |
providers/openrouter.mjs |
Anthropic passthrough |
Preserves cache_control, 300+ cloud models |
| Ollama |
providers/ollama.mjs |
OpenAI /v1/chat/completions |
Injects num_ctx=8192, capability-aware tool passthrough (v1.12.0+) |
| OpenAI |
providers/openai.mjs |
OpenAI format |
Full bidirectional Anthropic ↔ OpenAI translation |
| LMStudio |
providers/lmstudio.mjs |
OpenAI (via local factory) |
Delegates to OpenAI translator, :1234/v1 default |
| llama.cpp |
providers/llamacpp.mjs |
OpenAI (via local factory) |
Delegates to OpenAI translator, :8080/v1 default |
Presets (update version in KNOWLEDGE-BASE.md when changing)
| Preset |
Model ID |
Cost |
| gpt |
openai/gpt-5.4 |
paid |
| codex |
openai/gpt-5.3-codex |
paid, coding |
| gemini |
google/gemini-3.1-flash-lite-preview |
paid |
| deepseek |
deepseek/deepseek-r1-0528 |
paid |
| mistral |
mistralai/devstral-2512 |
paid, coding |
| gemma |
google/gemma-4-31b-it |
paid, coding |
| qwen |
qwen/qwen3-coder:free |
free |
| nemotron |
nvidia/nemotron-3-super-120b-a12b:free |
free |
| llama |
meta-llama/llama-3.3-70b-instruct:free |
free |
When adding a new preset: add to MODEL_PRESETS in cli.mjs, update help text, update KNOWLEDGE-BASE.md, update site/index.html, update README.md.
Proxy Sanitization (proxy.mjs)
The sanitizeBody() function is the heart of AnyModel. It makes any model work with Claude Code's request format.
What it does (in order):
- Strips Anthropic-only fields:
betas, metadata, speed, raw output_config, context_management; preserves output_config.effort internally for compatible OpenAI forwarding
- Preserves
thinking — reasoning models (DeepSeek R1) need this for chain-of-thought
- Preserves
cache_control for OpenRouter (Anthropic models support it), strips for Ollama/OpenAI
- Clamps
max_tokens to minimum 16 — Claude Code sends max_tokens: 1 for probes, OpenAI/GPT rejects anything below 16
- Fixes tool schemas:
- Missing
input_schema entirely -> adds a minimal object schema
- Empty
properties: {} -> keeps it empty and adds additionalProperties:false
- Missing
type field -> adds "type": "object"
- Recursively fixes nested schemas (
anyOf, oneOf, allOf, items)
- Never injects or strips
_unused; real _unused params must round-trip
- Strips Anthropic-only tool fields:
cache_control, defer_loading, eager_input_streaming, strict
- Normalizes
tool_choice: converts string format to object format
- Local providers (Ollama, LMStudio, llama.cpp): capability-aware tool passthrough — since v1.12.0 the proxy no longer blanket-strips tools. Behavior is controlled by
OLLAMA_TOOLS env var (auto default, on, off). In auto mode, the proxy tries tools and caches per-model capability; on "does not support tools" errors it retries without tools and remembers that model as no-tool-support. Tools are then compressed and budget-trimmed via providers/tool-compressor.mjs to fit local context windows (core tools Bash/Read/Write/Edit always kept). Translation: Anthropic tool definitions → OpenAI function format → Ollama message.tool_calls[] → translated back to Anthropic tool_use content blocks with stop_reason: "tool_use". Streaming supported.
tool_choice always stripped for Ollama — Ollama doesn't support tool_choice; proxy removes it unconditionally.
- Auto-retry without tools — if a provider returns "No endpoints found that support tool use" / "does not support tools", proxy retries with tools removed AND caches model as no-tool-support.
Why this matters
Without sanitization, MCP tools break on non-Anthropic models. Claude Code sends 80+ tool definitions including MCP servers (Slack, Figma, Gmail, etc.) with every request. The proxy ensures these schemas are valid for the target model, so MCP works seamlessly through any provider — including local Ollama/LMStudio/llama.cpp models that support function calling (Qwen 3/Coder, Llama 3.1+, Mistral, DeepSeek R1, Gemma 4).
Regression guard: test/ollama-tool-passthrough.test.mjs asserts that in auto mode with an uncached model, tools are forwarded (not stripped). See also test/ollama-tools.test.mjs for the capability-cache logic.
Client Identity
- Character: Violet diamond-themed (
diamond-head and diamond-feet ASCII art)
- Color: Light violet
rgb(147,130,255), ANSI fallback magentaBright
- Branding: "AnyModel" everywhere (not "Claude Code"). Tips say "Ask AnyModel" not "Ask Claude"
- Version: Synced with npm package via
prepublishOnly script
ANYMODEL_MODEL: Env var displayed in client UI showing active model name
When modifying the client: never break the violet identity. The diamond character and "AnyModel" branding are what distinguish this from standard Claude Code.
Branding (reproducible)
cli.js is a 13MB minified bundle, so branding is NOT hand-edited anymore. It is applied declaratively and verifiably:
scripts/brand-patch.mjs — idempotent applier. Asserts each patch's from appears exactly expect times before touching anything (a mismatch means upstream changed → fail loudly), applies replacements, then re-parses with node --check so a broken bundle can never ship. Some patches are adaptive: they swap a static literal for a JS expression that reads process.env.ANYMODEL_MODEL at render time, so the UI reflects the loaded backend model.
scripts/brand-patches.json — the declarative manifest (data/code separation).
scripts/gen-brand-manifest.py — regenerates the manifest. Run it against a PRISTINE bundle (it computes each expect from live anchor counts and fails if any anchor is missing).
--check gate — node scripts/brand-patch.mjs --check is verify-only (CI anti-regression gate): non-zero exit on drift. Includes a version-tolerant vendor denylist sweep that catches residual user-visible vendor strings the exact from anchors miss (e.g. version-bumped model promos).
User-facing UI text reads "AnyModel" (capital), while the package/command name stays lowercase. Re-apply on every upstream cli.js bump — a bundle refresh wipes all branding.
Environment Variables
| Variable |
Purpose |
OPENROUTER_API_KEY |
OpenRouter API key (primary provider) |
OPENAI_API_KEY |
API key for OpenAI-compatible endpoints |
OPENAI_BASE_URL |
Base URL for custom OpenAI-compatible providers |
ANYMODEL_CLIENT |
Explicit path to client binary |
ANYMODEL_MODEL |
Model name displayed in client UI |
ANYMODEL_TOKEN |
Auth token for remote proxy mode |
ANYMODEL_SKILL_ROOTS |
Colon-separated extra/override skill discovery roots (absolute paths; relative resolved against cwd) for the universal skill loader |
ANYMODEL_FORWARD_EFFORT |
Force --effort forwarding as OpenAI reasoning_effort (1/0; auto by default) |
PROXY_PORT |
Default port override (default: 9090) |
OLLAMA_NUM_CTX |
Ollama context size (default: 8192) |
LOCAL_NUM_CTX |
LMStudio / llama.cpp context size (default: 32768) |
OLLAMA_TOOLS |
Tool-passthrough mode: auto (default, try + cache), on (always pass), off (always strip — legacy behavior) |
OLLAMA_MAX_TOOLS / LOCAL_MAX_TOOLS |
Hard cap on tool count for local providers (0 = no cap, rely on budget) |
OLLAMA_MAX_TOOL_DESC / LOCAL_MAX_TOOL_DESC |
Max chars per tool description (default: 100) |
OLLAMA_TOOL_BUDGET_PCT / LOCAL_TOOL_BUDGET_PCT |
Fraction of num_ctx reserved for tool schemas (default: 0.30) |
Client Discovery (findClient())
When npx anymodel connects, it finds the client in this order:
ANYMODEL_CLIENT env var (explicit path)
cli.js next to cli.mjs (bundled in npm package)
cli.js in current directory
- Global
claude binary (last resort fallback)
Universal Skill Loader (providers/skill-bridge.mjs)
SKILL.md is ONE shared open standard — Claude Code, OpenAI/Codex, Gemini/Antigravity, Cursor and Copilot all read the same <name>/SKILL.md format (YAML frontmatter + Markdown body). Only the discovery path differs. The bundled client only scans .claude/skills, so instead of patching its minified loader, AnyModel bridges at launch time: it discovers foreign-ecosystem skills, symlinks each into a per-session temp .claude/skills shadow, and passes that shadow via --add-dir. Zero format translation — the client's native SKILL.md reader handles everything.
- Roots scanned (under both project cwd AND
$HOME, in precedence order): .agents/skills, .codex/skills, .gemini/skills, .agent/skills (Antigravity, singular). The project's own .claude/skills is read for collision precedence.
ANYMODEL_SKILL_ROOTS (colon-separated) appends/overrides extra discovery roots; relative entries resolve against cwd (a bare relative root would otherwise produce a broken symlink).
- Collision rules: a project-local
.claude/skills/<name> wins (foreign same-name is shadowed); among foreign roots the first occurrence of a name wins. Names are compared case-insensitively (macOS/Windows FS). Shadowed duplicates and unlinkable skills are logged, not silently dropped.
- Safety: symlinked skill entries that resolve outside their scanned root are skipped (no exposing
~/.ssh via an untrusted repo's .codex/skills/x link).
- A project-scoped local skill index for context economy lives in
providers/skill-catalog.mjs (increment 0016).
Deployment Pipeline (MANDATORY)
Every change MUST follow this pipeline. Never skip steps, never publish without tests passing.
# 1. Run all tests
npm test
# 2. Commit and push
git add <changed-files>
git commit -m "description of change"
git push
# 3. Version bump and publish to npm
npm version patch
npm run sync-version # syncs version to cli.js
npm publish
# 4. Deploy website (only if site/ changed)
vercel --prod
The prepublishOnly script in package.json handles version syncing. After npm version patch, run npm run sync-version to update the version in the bundled client, then npm publish.
Adding a New Provider
- Create
providers/newprovider.mjs following the pattern in existing providers
- Export:
{ name, transformRequest, transformResponse, buildUrl, getHeaders }
- Add detection logic in
cli.mjs (PROVIDERS array)
- Add env var documentation
- Handle tool schema translation if the provider's format differs
- Add tests in
test/
- Update
KNOWLEDGE-BASE.md
- Run the full deployment pipeline
Adding a New Preset
- Add to
MODEL_PRESETS in cli.mjs
- Update the help text in
showUsage()
- Update
KNOWLEDGE-BASE.md preset table
- Update
site/index.html preset section
- Update
README.md
- Run the full deployment pipeline
Debugging Guide
Common Issues
| Symptom |
Cause |
Fix |
max_tokens error |
Claude Code sends max_tokens: 1 for probes |
Proxy clamps to 16 — check sanitizeBody() |
| Tool use fails on GPT |
Invalid or missing tool schema fields |
Proxy normalizes schemas without placeholder hacks — check sanitizeBody() |
| Ollama extremely slow |
Default 8K context overflowed by big system prompts |
Bump OLLAMA_NUM_CTX=32768; tool compressor trims schemas to ~30% of ctx |
| Local model prints JSON instead of calling tools |
Model doesn't support function calling, OR OLLAMA_TOOLS=off, OR model cached as no-tool-support |
Use a tool-capable model (Qwen 3 / Qwen-Coder, Llama 3.1+, Mistral, DeepSeek R1, Gemma 4); ensure OLLAMA_TOOLS=auto; restart proxy to clear per-model cache |
Slash commands (/openspec, etc.) return JSON text |
Same as above — the command emits prompts expecting tool_use blocks; only tool-capable models will emit them |
See row above |
| "No endpoints found" / "does not support tools" |
Model doesn't support tool use |
Proxy auto-retries without tools and caches model as no-tool-support |
| Streaming breaks |
Response format mismatch |
Check provider's transformResponse — Anthropic SSE vs OpenAI SSE |
cache_control errors |
Non-Anthropic model rejecting cache hints |
Check keepCache flag in sanitizeBody() |
| Client shows "Claude" |
Branding not applied |
Check ANYMODEL_MODEL env var and client identity patches |
Debugging Proxy Traffic
The proxy logs every request with color-coded output:
[OPENROUTER] / [OLLAMA] / [OPENAI] — provider prefix
tools=N — number of tools in request
stream=true — streaming mode
- Yellow
[OLLAMA] Passing N tools to <model> (mode=auto) — tools forwarded (capability-aware passthrough, v1.12.0+)
- Yellow
[OLLAMA] Stripping N tools (mode=<x>, model=<m> cached as no-tool-support) — tool removal (either OLLAMA_TOOLS=off or model is in no-tool-support cache)
- Yellow
[OLLAMA] Tool optimization: N tools (X tok) → compressed to Y tok — schema compression + budget trimming
- Green
200 — successful response
- Red status codes — errors with body excerpt
Competitive Context
| Tool |
Key Difference from AnyModel |
| Claude Code Router (CCR) |
31K+ stars, Claude Code-specific, requires manual JSON config, no bundled client |
| OpenRouter native |
Only Anthropic models work reliably, no format translation |
| OpenCode |
Full rewrite (107K stars), own ecosystem, not a proxy |
| Cline |
VS Code extension, IDE-specific, not standalone CLI |
| LiteLLM |
Enterprise Python gateway, heavy config |
Anthropic Third-Party Cutoff (April 4, 2026)
Claude subscriptions no longer cover third-party tools. AnyModel proxy mode is NOT affected (routes through OpenRouter, never touches Anthropic OAuth). This is a key selling point — AnyModel users don't need a Claude subscription.
URLs and Links
File Structure
anymodel/
cli.mjs # CLI entry point
cli.js # Bundled client (12MB modified Claude Code)
proxy.mjs # HTTP proxy server + sanitization
package.json # npm config
providers/
openrouter.mjs # OpenRouter provider (passthrough)
ollama.mjs # Ollama provider (OpenAI translation + num_ctx + tool_use roundtrip)
ollama-tools.mjs # Tool capability mode + per-model no-tool-support cache
tool-compressor.mjs # Schema compression + budget trimming for local models
openai.mjs # OpenAI provider (bidirectional translation)
openai-local.mjs # Factory for LMStudio/llama.cpp (thin wrappers over OpenAI)
lmstudio.mjs # LMStudio alias (:1234/v1)
llamacpp.mjs # llama.cpp alias (:8080/v1)
prefix-cache.mjs # Prefix-aware caching (increment 0004)
skill-bridge.mjs # Universal skill loader — foreign skills → temp .claude/skills shadow + --add-dir
skill-catalog.mjs # Project-scoped local skill index for context economy (increment 0016)
scripts/
brand-patch.mjs # Idempotent, verifiable cli.js brand applier (--check CI gate)
brand-patches.json # Declarative brand-patch manifest
gen-brand-manifest.py # Regenerates the manifest from a pristine bundle
site/
index.html # anymodel.dev homepage
styles.css / script.js / sitemap.xml / robots.txt
test/ # 93+ tests
KNOWLEDGE-BASE.md # Single source of truth (keep in sync with this skill)
Testing
Run all tests before any commit:
npm test
Tests cover: sanitization, tool schema fixing, provider translation, preset resolution, max_tokens clamping, streaming, error handling. When adding features, add corresponding tests. The test count should only go up.
1---2name: anymodel-23description: AnyModel development skill — the universal AI coding proxy. Use this skill whenever working on AnyModel source code (proxy.mjs, cli.mjs, providers/*, site/*, test/*), adding providers or presets, debugging proxy issues, deploying to npm, or modifying the anymodel.dev website. Also activate when the user mentions AnyModel architecture, proxy sanitization, tool schema fixing, Ollama integration, OpenRouter routing, client branding, or the deployment pipeline. Covers the full AnyModel lifecycle: code, test, publish, deploy.4---56# AnyModel Development Skill78You are working on **AnyModel** — a universal AI coding proxy that lets you use any AI model through one interface.9This skill contains everything you need: architecture, conventions, deployment rules, and debugging guidance.1011## Product Identity1213- **Name**: AnyModel (capital A, capital M — never "anymodel" or "Any Model" in user-facing text)14- **Package**: `anymodel` on npm15- **Website**: https://anymodel.dev16- **GitHub (proxy)**: https://github.com/anton-abyzov/anymodel17- **Client bundle**: `cli.js` in this repo/package is the shipped Claude Code-compatible TUI18- **Author**: Anton Abyzov (@aabyzov on X, @AntonAbyzov on YouTube)19- **License**: MIT2021## Architecture2223```24AnyModel client (cli.js) --> anymodel proxy (:9090) --> OpenRouter / Ollama / OpenAI-compatible25```2627Three components:281. **Proxy** (`proxy.mjs`) — HTTP server intercepting `/v1/messages`, sanitizes requests, translates formats, routes to providers292. **CLI** (`cli.mjs`) — entry point for starting proxy, connecting, managing presets303. **Bundled client** (`cli.js`) — modified Claude Code client with violet diamond character and AnyModel branding3132### Providers3334| Provider | File | API Format | Key Behavior |35|----------|------|-----------|--------------|36| **OpenRouter** | `providers/openrouter.mjs` | Anthropic passthrough | Preserves `cache_control`, 300+ cloud models |37| **Ollama** | `providers/ollama.mjs` | OpenAI `/v1/chat/completions` | Injects `num_ctx=8192`, capability-aware tool passthrough (v1.12.0+) |38| **OpenAI** | `providers/openai.mjs` | OpenAI format | Full bidirectional Anthropic ↔ OpenAI translation |39| **LMStudio** | `providers/lmstudio.mjs` | OpenAI (via local factory) | Delegates to OpenAI translator, `:1234/v1` default |40| **llama.cpp** | `providers/llamacpp.mjs` | OpenAI (via local factory) | Delegates to OpenAI translator, `:8080/v1` default |4142### Presets (update version in KNOWLEDGE-BASE.md when changing)4344| Preset | Model ID | Cost |45|--------|----------|------|46| gpt | openai/gpt-5.4 | paid |47| codex | openai/gpt-5.3-codex | paid, coding |48| gemini | google/gemini-3.1-flash-lite-preview | paid |49| deepseek | deepseek/deepseek-r1-0528 | paid |50| mistral | mistralai/devstral-2512 | paid, coding |51| gemma | google/gemma-4-31b-it | paid, coding |52| qwen | qwen/qwen3-coder:free | free |53| nemotron | nvidia/nemotron-3-super-120b-a12b:free | free |54| llama | meta-llama/llama-3.3-70b-instruct:free | free |5556When adding a new preset: add to `MODEL_PRESETS` in `cli.mjs`, update help text, update `KNOWLEDGE-BASE.md`, update `site/index.html`, update `README.md`.5758## Proxy Sanitization (proxy.mjs)5960The `sanitizeBody()` function is the heart of AnyModel. It makes any model work with Claude Code's request format.6162### What it does (in order):63641. **Strips Anthropic-only fields**: `betas`, `metadata`, `speed`, raw `output_config`, `context_management`; preserves `output_config.effort` internally for compatible OpenAI forwarding652. **Preserves `thinking`** — reasoning models (DeepSeek R1) need this for chain-of-thought663. **Preserves `cache_control`** for OpenRouter (Anthropic models support it), strips for Ollama/OpenAI674. **Clamps `max_tokens`** to minimum 16 — Claude Code sends `max_tokens: 1` for probes, OpenAI/GPT rejects anything below 16685. **Fixes tool schemas**:69 - Missing `input_schema` entirely -> adds a minimal object schema70 - Empty `properties: {}` -> keeps it empty and adds `additionalProperties:false`71 - Missing `type` field -> adds `"type": "object"`72 - Recursively fixes nested schemas (`anyOf`, `oneOf`, `allOf`, `items`)73 - Never injects or strips `_unused`; real `_unused` params must round-trip746. **Strips Anthropic-only tool fields**: `cache_control`, `defer_loading`, `eager_input_streaming`, `strict`757. **Normalizes `tool_choice`**: converts string format to object format768. **Local providers (Ollama, LMStudio, llama.cpp): capability-aware tool passthrough** — since v1.12.0 the proxy no longer blanket-strips tools. Behavior is controlled by `OLLAMA_TOOLS` env var (`auto` default, `on`, `off`). In `auto` mode, the proxy tries tools and caches per-model capability; on "does not support tools" errors it retries without tools and remembers that model as no-tool-support. Tools are then compressed and budget-trimmed via `providers/tool-compressor.mjs` to fit local context windows (core tools Bash/Read/Write/Edit always kept). Translation: Anthropic tool definitions → OpenAI function format → Ollama `message.tool_calls[]` → translated back to Anthropic `tool_use` content blocks with `stop_reason: "tool_use"`. Streaming supported.779. **`tool_choice` always stripped for Ollama** — Ollama doesn't support `tool_choice`; proxy removes it unconditionally.7810. **Auto-retry without tools** — if a provider returns "No endpoints found that support tool use" / "does not support tools", proxy retries with tools removed AND caches model as no-tool-support.7980### Why this matters8182Without sanitization, MCP tools break on non-Anthropic models. Claude Code sends 80+ tool definitions including MCP servers (Slack, Figma, Gmail, etc.) with every request. The proxy ensures these schemas are valid for the target model, so MCP works seamlessly through any provider — including local Ollama/LMStudio/llama.cpp models that support function calling (Qwen 3/Coder, Llama 3.1+, Mistral, DeepSeek R1, Gemma 4).8384**Regression guard:** `test/ollama-tool-passthrough.test.mjs` asserts that in `auto` mode with an uncached model, tools are forwarded (not stripped). See also `test/ollama-tools.test.mjs` for the capability-cache logic.8586## Client Identity8788- **Character**: Violet diamond-themed (`diamond-head` and `diamond-feet` ASCII art)89- **Color**: Light violet `rgb(147,130,255)`, ANSI fallback `magentaBright`90- **Branding**: "AnyModel" everywhere (not "Claude Code"). Tips say "Ask AnyModel" not "Ask Claude"91- **Version**: Synced with npm package via `prepublishOnly` script92- **`ANYMODEL_MODEL`**: Env var displayed in client UI showing active model name9394When modifying the client: never break the violet identity. The diamond character and "AnyModel" branding are what distinguish this from standard Claude Code.9596### Branding (reproducible)9798`cli.js` is a 13MB minified bundle, so branding is NOT hand-edited anymore. It is applied declaratively and verifiably:99100- **`scripts/brand-patch.mjs`** — idempotent applier. Asserts each patch's `from` appears exactly `expect` times before touching anything (a mismatch means upstream changed → fail loudly), applies replacements, then re-parses with `node --check` so a broken bundle can never ship. Some patches are `adaptive`: they swap a static literal for a JS expression that reads `process.env.ANYMODEL_MODEL` at render time, so the UI reflects the loaded backend model.101- **`scripts/brand-patches.json`** — the declarative manifest (data/code separation).102- **`scripts/gen-brand-manifest.py`** — regenerates the manifest. Run it against a PRISTINE bundle (it computes each `expect` from live anchor counts and fails if any anchor is missing).103- **`--check` gate** — `node scripts/brand-patch.mjs --check` is verify-only (CI anti-regression gate): non-zero exit on drift. Includes a version-tolerant vendor denylist sweep that catches residual user-visible vendor strings the exact `from` anchors miss (e.g. version-bumped model promos).104105User-facing UI text reads "AnyModel" (capital), while the package/command name stays lowercase. **Re-apply on every upstream `cli.js` bump** — a bundle refresh wipes all branding.106107## Environment Variables108109| Variable | Purpose |110|----------|---------|111| `OPENROUTER_API_KEY` | OpenRouter API key (primary provider) |112| `OPENAI_API_KEY` | API key for OpenAI-compatible endpoints |113| `OPENAI_BASE_URL` | Base URL for custom OpenAI-compatible providers |114| `ANYMODEL_CLIENT` | Explicit path to client binary |115| `ANYMODEL_MODEL` | Model name displayed in client UI |116| `ANYMODEL_TOKEN` | Auth token for remote proxy mode |117| `ANYMODEL_SKILL_ROOTS` | Colon-separated extra/override skill discovery roots (absolute paths; relative resolved against cwd) for the universal skill loader |118| `ANYMODEL_FORWARD_EFFORT` | Force `--effort` forwarding as OpenAI `reasoning_effort` (`1`/`0`; auto by default) |119| `PROXY_PORT` | Default port override (default: 9090) |120| `OLLAMA_NUM_CTX` | Ollama context size (default: 8192) |121| `LOCAL_NUM_CTX` | LMStudio / llama.cpp context size (default: 32768) |122| `OLLAMA_TOOLS` | Tool-passthrough mode: `auto` (default, try + cache), `on` (always pass), `off` (always strip — legacy behavior) |123| `OLLAMA_MAX_TOOLS` / `LOCAL_MAX_TOOLS` | Hard cap on tool count for local providers (0 = no cap, rely on budget) |124| `OLLAMA_MAX_TOOL_DESC` / `LOCAL_MAX_TOOL_DESC` | Max chars per tool description (default: 100) |125| `OLLAMA_TOOL_BUDGET_PCT` / `LOCAL_TOOL_BUDGET_PCT` | Fraction of `num_ctx` reserved for tool schemas (default: 0.30) |126127## Client Discovery (`findClient()`)128129When `npx anymodel` connects, it finds the client in this order:1301. `ANYMODEL_CLIENT` env var (explicit path)1312. `cli.js` next to `cli.mjs` (bundled in npm package)1323. `cli.js` in current directory1334. Global `claude` binary (last resort fallback)134135## Universal Skill Loader (`providers/skill-bridge.mjs`)136137`SKILL.md` is ONE shared open standard — Claude Code, OpenAI/Codex, Gemini/Antigravity, Cursor and Copilot all read the same `<name>/SKILL.md` format (YAML frontmatter + Markdown body). Only the discovery path differs. The bundled client only scans `.claude/skills`, so instead of patching its minified loader, AnyModel bridges at launch time: it discovers foreign-ecosystem skills, symlinks each into a per-session temp `.claude/skills` shadow, and passes that shadow via `--add-dir`. Zero format translation — the client's native SKILL.md reader handles everything.138139- **Roots scanned** (under both project cwd AND `$HOME`, in precedence order): `.agents/skills`, `.codex/skills`, `.gemini/skills`, `.agent/skills` (Antigravity, singular). The project's own `.claude/skills` is read for collision precedence.140- **`ANYMODEL_SKILL_ROOTS`** (colon-separated) appends/overrides extra discovery roots; relative entries resolve against cwd (a bare relative root would otherwise produce a broken symlink).141- **Collision rules**: a project-local `.claude/skills/<name>` wins (foreign same-name is shadowed); among foreign roots the first occurrence of a name wins. Names are compared case-insensitively (macOS/Windows FS). Shadowed duplicates and unlinkable skills are logged, not silently dropped.142- **Safety**: symlinked skill entries that resolve outside their scanned root are skipped (no exposing `~/.ssh` via an untrusted repo's `.codex/skills/x` link).143- A project-scoped local skill index for context economy lives in `providers/skill-catalog.mjs` (increment 0016).144145## Deployment Pipeline (MANDATORY)146147Every change MUST follow this pipeline. Never skip steps, never publish without tests passing.148149```bash150# 1. Run all tests151npm test152153# 2. Commit and push154git add <changed-files>155git commit -m "description of change"156git push157158# 3. Version bump and publish to npm159npm version patch160npm run sync-version # syncs version to cli.js161npm publish162163# 4. Deploy website (only if site/ changed)164vercel --prod165166```167168The `prepublishOnly` script in package.json handles version syncing. After `npm version patch`, run `npm run sync-version` to update the version in the bundled client, then `npm publish`.169170## Adding a New Provider1711721. Create `providers/newprovider.mjs` following the pattern in existing providers1732. Export: `{ name, transformRequest, transformResponse, buildUrl, getHeaders }`1743. Add detection logic in `cli.mjs` (`PROVIDERS` array)1754. Add env var documentation1765. Handle tool schema translation if the provider's format differs1776. Add tests in `test/`1787. Update `KNOWLEDGE-BASE.md`1798. Run the full deployment pipeline180181## Adding a New Preset1821831. Add to `MODEL_PRESETS` in `cli.mjs`1842. Update the help text in `showUsage()`1853. Update `KNOWLEDGE-BASE.md` preset table1864. Update `site/index.html` preset section1875. Update `README.md`1886. Run the full deployment pipeline189190## Debugging Guide191192### Common Issues193194| Symptom | Cause | Fix |195|---------|-------|-----|196| `max_tokens` error | Claude Code sends `max_tokens: 1` for probes | Proxy clamps to 16 — check `sanitizeBody()` |197| Tool use fails on GPT | Invalid or missing tool schema fields | Proxy normalizes schemas without placeholder hacks — check `sanitizeBody()` |198| Ollama extremely slow | Default 8K context overflowed by big system prompts | Bump `OLLAMA_NUM_CTX=32768`; tool compressor trims schemas to ~30% of ctx |199| Local model prints JSON instead of calling tools | Model doesn't support function calling, OR `OLLAMA_TOOLS=off`, OR model cached as no-tool-support | Use a tool-capable model (Qwen 3 / Qwen-Coder, Llama 3.1+, Mistral, DeepSeek R1, Gemma 4); ensure `OLLAMA_TOOLS=auto`; restart proxy to clear per-model cache |200| Slash commands (`/openspec`, etc.) return JSON text | Same as above — the command emits prompts expecting `tool_use` blocks; only tool-capable models will emit them | See row above |201| "No endpoints found" / "does not support tools" | Model doesn't support tool use | Proxy auto-retries without tools and caches model as no-tool-support |202| Streaming breaks | Response format mismatch | Check provider's `transformResponse` — Anthropic SSE vs OpenAI SSE |203| `cache_control` errors | Non-Anthropic model rejecting cache hints | Check `keepCache` flag in `sanitizeBody()` |204| Client shows "Claude" | Branding not applied | Check `ANYMODEL_MODEL` env var and client identity patches |205206### Debugging Proxy Traffic207208The proxy logs every request with color-coded output:209- `[OPENROUTER]` / `[OLLAMA]` / `[OPENAI]` — provider prefix210- `tools=N` — number of tools in request211- `stream=true` — streaming mode212- Yellow `[OLLAMA] Passing N tools to <model> (mode=auto)` — tools forwarded (capability-aware passthrough, v1.12.0+)213- Yellow `[OLLAMA] Stripping N tools (mode=<x>, model=<m> cached as no-tool-support)` — tool removal (either `OLLAMA_TOOLS=off` or model is in no-tool-support cache)214- Yellow `[OLLAMA] Tool optimization: N tools (X tok) → compressed to Y tok` — schema compression + budget trimming215- Green `200` — successful response216- Red status codes — errors with body excerpt217218## Competitive Context219220| Tool | Key Difference from AnyModel |221|------|-----|222| **Claude Code Router (CCR)** | 31K+ stars, Claude Code-specific, requires manual JSON config, no bundled client |223| **OpenRouter native** | Only Anthropic models work reliably, no format translation |224| **OpenCode** | Full rewrite (107K stars), own ecosystem, not a proxy |225| **Cline** | VS Code extension, IDE-specific, not standalone CLI |226| **LiteLLM** | Enterprise Python gateway, heavy config |227228### Anthropic Third-Party Cutoff (April 4, 2026)229230Claude subscriptions no longer cover third-party tools. AnyModel proxy mode is NOT affected (routes through OpenRouter, never touches Anthropic OAuth). This is a key selling point — AnyModel users don't need a Claude subscription.231232## URLs and Links233234| Resource | URL |235|----------|-----|236| AnyModel website | https://anymodel.dev |237| AnyModel npm | https://npmjs.com/package/anymodel |238| AnyModel GitHub | https://github.com/anton-abyzov/anymodel |239| Client bundle | `cli.js` in this repo/package |240| SpecWeave | https://spec-weave.com |241| Verified Skills | https://verified-skill.com |242| OpenRouter keys | https://openrouter.ai/keys |243| YouTube demo | https://youtu.be/k0RI_M6lIsg |244| YouTube channel | https://youtube.com/@AntonAbyzov |245| Twitter/X | https://x.com/aabyzov |246| Discord | https://discord.gg/UYg4BGJ65V |247| Telegram | https://t.me/antonaipower |248249## File Structure250251```252anymodel/253 cli.mjs # CLI entry point254 cli.js # Bundled client (12MB modified Claude Code)255 proxy.mjs # HTTP proxy server + sanitization256 package.json # npm config257 providers/258 openrouter.mjs # OpenRouter provider (passthrough)259 ollama.mjs # Ollama provider (OpenAI translation + num_ctx + tool_use roundtrip)260 ollama-tools.mjs # Tool capability mode + per-model no-tool-support cache261 tool-compressor.mjs # Schema compression + budget trimming for local models262 openai.mjs # OpenAI provider (bidirectional translation)263 openai-local.mjs # Factory for LMStudio/llama.cpp (thin wrappers over OpenAI)264 lmstudio.mjs # LMStudio alias (:1234/v1)265 llamacpp.mjs # llama.cpp alias (:8080/v1)266 prefix-cache.mjs # Prefix-aware caching (increment 0004)267 skill-bridge.mjs # Universal skill loader — foreign skills → temp .claude/skills shadow + --add-dir268 skill-catalog.mjs # Project-scoped local skill index for context economy (increment 0016)269 scripts/270 brand-patch.mjs # Idempotent, verifiable cli.js brand applier (--check CI gate)271 brand-patches.json # Declarative brand-patch manifest272 gen-brand-manifest.py # Regenerates the manifest from a pristine bundle273 site/274 index.html # anymodel.dev homepage275 styles.css / script.js / sitemap.xml / robots.txt276 test/ # 93+ tests277 KNOWLEDGE-BASE.md # Single source of truth (keep in sync with this skill)278```279280## Testing281282Run all tests before any commit:283284```bash285npm test286```287288Tests cover: sanitization, tool schema fixing, provider translation, preset resolution, max_tokens clamping, streaming, error handling. When adding features, add corresponding tests. The test count should only go up.