musistudio/claude-code-router/main 546k tokens More Tools
```
├── .github/
   ├── workflows/
      ├── docker-publish.yml (400 tokens)
      ├── docs.yml (200 tokens)
├── .gitignore
├── .npmignore
├── CLAUDE.md (1700 tokens)
├── LICENSE (omitted)
├── README.md (5.1k tokens)
├── README_zh.md (3.5k tokens)
├── blog/
   ├── en/
      ├── glm-4.6-supports-reasoning.md (1100 tokens)
      ├── maybe-we-can-do-more-with-the-route.md (1400 tokens)
      ├── progressive-disclosure-of-agent-tools-from-the-perspective-of-cli-tool-style.md (2.1k tokens)
      ├── project-motivation-and-how-it-works.md (1400 tokens)
   ├── images/
      ├── alipay.jpg
      ├── chrome-devtools.png
      ├── chrome-inspect.png
      ├── claude-code-router-img.png
      ├── claude-code.png
      ├── models.gif
      ├── roadmap.svg (600 tokens)
      ├── search.png
      ├── sponsors/
         ├── glm-en.jpg
         ├── glm-zh.jpg
      ├── statusline-config.png
      ├── statusline.png
      ├── ui.png
      ├── webstorm-formate-file.png
      ├── wechat.jpg
      ├── wechat_group.jpg
   ├── zh/
      ├── GLM-4.6支持思考及思维链回传.md (700 tokens)
      ├── 从CLI工具风格看工具渐进式披露.md (3.1k tokens)
      ├── 或许我们能在Router中做更多事情.md (1000 tokens)
      ├── 项目初衷及原理.md (900 tokens)
├── custom-router.example.js
├── docs/
   ├── .gitignore
   ├── README.md (400 tokens)
   ├── blog/
      ├── 2025-02-25-project-motivation.md (1400 tokens)
      ├── 2025-11-18-glm-reasoning.md (1100 tokens)
      ├── 2025-11-18-router-exploration.md (1400 tokens)
   ├── docs/
      ├── cli/
         ├── commands/
            ├── model.md (300 tokens)
            ├── other.md (200 tokens)
            ├── preset.md (1100 tokens)
            ├── start.md (300 tokens)
            ├── status.md (200 tokens)
            ├── statusline.md (1800 tokens)
         ├── config/
            ├── basic.md (800 tokens)
            ├── project-level.md (800 tokens)
         ├── installation.md (200 tokens)
         ├── intro.md (400 tokens)
         ├── quick-start.md (300 tokens)
      ├── presets/
         ├── intro.md (3.2k tokens)
      ├── server/
         ├── advanced/
            ├── custom-router.md (500 tokens)
         ├── api/
            ├── config-api.md (800 tokens)
            ├── logs-api.md (700 tokens)
            ├── messages-api.md (800 tokens)
            ├── overview.md (500 tokens)
         ├── config/
            ├── basic.md (500 tokens)
            ├── providers.md (300 tokens)
            ├── routing.md (1100 tokens)
            ├── transformers.md (3.6k tokens)
         ├── deployment.md (700 tokens)
         ├── intro.md (1200 tokens)
   ├── docusaurus.config.ts (500 tokens)
   ├── i18n/
      ├── en/
         ├── code.json (2.7k tokens)
         ├── docusaurus-plugin-content-blog/
            ├── options.json (100 tokens)
         ├── docusaurus-plugin-content-docs/
            ├── current.json (900 tokens)
         ├── docusaurus-theme-classic/
            ├── footer.json (200 tokens)
            ├── navbar.json (100 tokens)
      ├── zh-CN/
         ├── code.json (2.5k tokens)
         ├── docusaurus-plugin-content-blog/
            ├── 2025-02-25-project-motivation.md (900 tokens)
            ├── 2025-11-18-glm-reasoning.md (800 tokens)
            ├── 2025-11-18-router-exploration.md (1000 tokens)
            ├── options.json (100 tokens)
         ├── docusaurus-plugin-content-docs.backup.20260101_205603/
            ├── advanced/
               ├── custom-router.md (500 tokens)
               ├── preset-format.md (4.3k tokens)
               ├── presets.md (2.5k tokens)
            ├── cli/
               ├── commands/
                  ├── preset.md (700 tokens)
                  ├── statusline.md (1500 tokens)
               ├── config/
                  ├── basic.md (600 tokens)
                  ├── project-level.md (600 tokens)
               ├── intro.md (200 tokens)
               ├── model.md (300 tokens)
               ├── other-commands.md (200 tokens)
               ├── start.md (200 tokens)
               ├── status.md (100 tokens)
            ├── config/
               ├── basic.md (500 tokens)
               ├── providers.md (700 tokens)
               ├── routing.md (400 tokens)
               ├── transformers.md (600 tokens)
            ├── current.json (700 tokens)
            ├── installation.md (100 tokens)
            ├── intro.md (300 tokens)
            ├── quick-start.md (200 tokens)
            ├── server/
               ├── api/
                  ├── config-api.md (700 tokens)
                  ├── logs-api.md (600 tokens)
                  ├── messages-api.md (700 tokens)
                  ├── overview.md (300 tokens)
               ├── deployment.md (600 tokens)
               ├── intro.md (300 tokens)
         ├── docusaurus-plugin-content-docs/
            ├── current.json (900 tokens)
            ├── current/
               ├── cli/
                  ├── commands/
                     ├── model.md (300 tokens)
                     ├── other.md (200 tokens)
                     ├── preset.md (700 tokens)
                     ├── start.md (200 tokens)
                     ├── status.md (100 tokens)
                     ├── statusline.md (1500 tokens)
                  ├── config/
                     ├── basic.md (600 tokens)
                     ├── project-level.md (600 tokens)
                  ├── installation.md (100 tokens)
                  ├── intro.md (300 tokens)
                  ├── quick-start.md (200 tokens)
               ├── presets/
                  ├── intro.md (2.4k tokens)
               ├── server/
                  ├── advanced/
                     ├── custom-router.md (500 tokens)
                     ├── preset-format.md (4.2k tokens)
                  ├── api/
                     ├── config-api.md (700 tokens)
                     ├── logs-api.md (600 tokens)
                     ├── messages-api.md (700 tokens)
                     ├── overview.md (300 tokens)
                  ├── config/
                     ├── basic.md (500 tokens)
                     ├── providers.md (700 tokens)
                     ├── routing.md (800 tokens)
                     ├── transformers.md (3k tokens)
                  ├── deployment.md (600 tokens)
                  ├── intro.md (800 tokens)
         ├── docusaurus-theme-classic/
            ├── footer.json (400 tokens)
            ├── navbar.json (100 tokens)
   ├── package.json (200 tokens)
   ├── postcss.config.js
   ├── sidebars.ts (700 tokens)
   ├── src/
      ├── components/
         ├── HomepageFeatures.module.css (200 tokens)
         ├── HomepageFeatures.tsx (400 tokens)
      ├── css-modules.d.ts (omitted)
      ├── css/
         ├── custom.css (1700 tokens)
      ├── docusaurus.d.ts (omitted)
      ├── pages/
         ├── index.tsx (5.8k tokens)
   ├── static/
      ├── blog-images/
         ├── alipay.jpg
         ├── chrome-devtools.png
         ├── chrome-inspect.png
         ├── claude-code-router-img.png
         ├── claude-code.png
         ├── models.gif
         ├── roadmap.svg (600 tokens)
         ├── search.png
         ├── sponsors/
            ├── glm-en.jpg
            ├── glm-zh.jpg
         ├── statusline-config.png
         ├── statusline.png
         ├── ui.png
         ├── webstorm-formate-file.png
         ├── wechat.jpg
         ├── wechat_group.jpg
      ├── img/
         ├── ccr.svg (148.7k tokens)
         ├── docusaurus-social-card.jpg
         ├── favicon.ico
         ├── logo.svg (100 tokens)
         ├── undraw_docusaurus_mountain.svg (100 tokens)
         ├── undraw_docusaurus_react.svg (100 tokens)
         ├── undraw_docusaurus_tree.svg (100 tokens)
   ├── tailwind.config.js (400 tokens)
   ├── tsconfig.json (100 tokens)
├── examples/
   ├── README.md (600 tokens)
   ├── dynamic-preset-example.json (1100 tokens)
   ├── preset-manifest-example.json (1200 tokens)
   ├── simple-preset-example.json (400 tokens)
├── package.json (300 tokens)
├── packages/
   ├── cli/
      ├── package.json (200 tokens)
      ├── src/
         ├── cli.ts (2.7k tokens)
         ├── types.d.ts (omitted)
         ├── types/
            ├── inquirer.d.ts (omitted)
         ├── utils/
            ├── activateCommand.ts (100 tokens)
            ├── codeCommand.ts (800 tokens)
            ├── createEnvVariables.ts (200 tokens)
            ├── index.ts (1800 tokens)
            ├── installCommand.ts (300 tokens)
            ├── modelSelector.ts (2.7k tokens)
            ├── preset/
               ├── commands.ts (1600 tokens)
               ├── export.ts (700 tokens)
               ├── index.ts (100 tokens)
               ├── install-github.ts (1000 tokens)
               ├── install.ts (1600 tokens)
            ├── processCheck.ts (800 tokens)
            ├── prompt/
               ├── schema-input.ts (1200 tokens)
            ├── status.ts (200 tokens)
            ├── statusline.ts (6.8k tokens)
            ├── update.ts (500 tokens)
      ├── tsconfig.json
   ├── core/
      ├── .npmignore
      ├── package.json (300 tokens)
      ├── scripts/
         ├── build.ts (800 tokens)
         ├── esbuild-plugin-path-alias.ts (500 tokens)
      ├── src/
         ├── api/
            ├── middleware.ts (200 tokens)
            ├── routes.ts (3.6k tokens)
         ├── plugins/
            ├── index.ts
            ├── output/
               ├── console-handler.ts (700 tokens)
               ├── index.ts (500 tokens)
               ├── output-manager.ts (1300 tokens)
               ├── temp-file-handler.ts (700 tokens)
               ├── types.ts (600 tokens)
               ├── webhook-handler.ts (1000 tokens)
            ├── plugin-manager.ts (600 tokens)
            ├── token-speed.ts (3.1k tokens)
            ├── types.ts (100 tokens)
         ├── server.ts (1900 tokens)
         ├── services/
            ├── config.ts (900 tokens)
            ├── provider.ts (1700 tokens)
            ├── tokenizer.ts (1100 tokens)
            ├── transformer.ts (900 tokens)
         ├── tokenizer/
            ├── api-tokenizer.ts (1400 tokens)
            ├── huggingface-tokenizer.ts (1500 tokens)
            ├── tiktoken-tokenizer.ts (700 tokens)
         ├── transformer/
            ├── anthropic.transformer.ts (7.5k tokens)
            ├── cerebras.transformer.ts (200 tokens)
            ├── cleancache.transformer.ts (200 tokens)
            ├── customparams.transformer.ts (600 tokens)
            ├── deepseek.transformer.ts (1600 tokens)
            ├── enhancetool.transformer.ts (2.4k tokens)
            ├── forcereasoning.transformer.ts (2.4k tokens)
            ├── gemini.transformer.ts (200 tokens)
            ├── groq.transformer.ts (1600 tokens)
            ├── index.ts (400 tokens)
            ├── maxcompletiontokens.transformer.ts (100 tokens)
            ├── maxtoken.transformer.ts (100 tokens)
            ├── openai.responses.transformer.ts (5.3k tokens)
            ├── openai.transformer.ts
            ├── openrouter.transformer.ts (2.5k tokens)
            ├── reasoning.transformer.ts (1800 tokens)
            ├── sampling.transformer.ts (300 tokens)
            ├── streamoptions.transformer.ts (100 tokens)
            ├── tooluse.transformer.ts (1700 tokens)
            ├── vercel.transformer.ts (2.6k tokens)
            ├── vertex-claude.transformer.ts (600 tokens)
            ├── vertex-gemini.transformer.ts (600 tokens)
         ├── types/
            ├── llm.ts (1000 tokens)
            ├── tokenizer.d.ts (omitted)
            ├── transformer.ts (200 tokens)
         ├── utils/
            ├── cache.ts (200 tokens)
            ├── converter.ts (2.7k tokens)
            ├── gemini.util.ts (7.1k tokens)
            ├── image.ts (100 tokens)
            ├── request.ts (300 tokens)
            ├── router.ts (2.3k tokens)
            ├── sse/
               ├── SSEParser.transform.ts (500 tokens)
               ├── SSESerializer.transform.ts (200 tokens)
               ├── index.ts
               ├── rewriteStream.ts (200 tokens)
            ├── thinking.ts (100 tokens)
            ├── toolArgumentsParser.ts (400 tokens)
            ├── vertex-claude.util.ts (3.3k tokens)
      ├── tsconfig.json (100 tokens)
   ├── server/
      ├── .dockerignore
      ├── Dockerfile (400 tokens)
      ├── ecosystem.config.cjs (100 tokens)
      ├── package.json (200 tokens)
      ├── src/
         ├── agents/
            ├── image.agent.ts (1900 tokens)
            ├── index.ts (200 tokens)
            ├── type.ts (100 tokens)
         ├── index.ts (3.1k tokens)
         ├── middleware/
            ├── auth.ts (400 tokens)
         ├── server.ts (3.3k tokens)
         ├── types.d.ts (omitted)
         ├── types/
            ├── llms-plugin.d.ts (omitted)
         ├── utils/
            ├── SSEParser.transform.ts (500 tokens)
            ├── SSESerializer.transform.ts (200 tokens)
            ├── index.ts (1100 tokens)
            ├── rewriteStream.ts (200 tokens)
      ├── tsconfig.json
   ├── shared/
      ├── package.json (100 tokens)
      ├── src/
         ├── constants.ts (200 tokens)
         ├── index.ts (100 tokens)
         ├── preset/
            ├── export.ts (600 tokens)
            ├── install.ts (2.4k tokens)
            ├── marketplace.ts (300 tokens)
            ├── merge.ts (1200 tokens)
            ├── readPreset.ts (200 tokens)
            ├── schema.ts (3.8k tokens)
            ├── sensitiveFields.ts (1300 tokens)
            ├── types.ts (1800 tokens)
      ├── tsconfig.json
   ├── ui/
      ├── PROJECT.md (100 tokens)
      ├── README.md (500 tokens)
      ├── components.json (100 tokens)
      ├── config.example.json (800 tokens)
      ├── eslint.config.js (100 tokens)
      ├── index.html (100 tokens)
      ├── package-lock.json (38.6k tokens)
      ├── package.json (400 tokens)
      ├── pnpm-lock.yaml (27.5k tokens)
      ├── public/
         ├── vite.svg (300 tokens)
      ├── src/
         ├── App.tsx (3.4k tokens)
         ├── assets/
            ├── react.svg (800 tokens)
         ├── components/
            ├── ConfigProvider.tsx (1200 tokens)
            ├── DebugPage.tsx (3.8k tokens)
            ├── JsonEditor.tsx (1300 tokens)
            ├── LogViewer.tsx (5.8k tokens)
            ├── Login.tsx (900 tokens)
            ├── Presets.tsx (5.8k tokens)
            ├── ProtectedRoute.tsx
            ├── ProviderList.tsx (800 tokens)
            ├── Providers.tsx (10k tokens)
            ├── PublicRoute.tsx
            ├── RequestHistoryDrawer.tsx (1200 tokens)
            ├── Router.tsx (1400 tokens)
            ├── SettingsDialog.tsx (1700 tokens)
            ├── StatusLineConfigDialog.tsx (8.7k tokens)
            ├── StatusLineImportExport.tsx (2.4k tokens)
            ├── TransformerList.tsx (800 tokens)
            ├── Transformers.tsx (2.1k tokens)
            ├── preset/
               ├── DynamicConfigForm.tsx (2.7k tokens)
            ├── ui/
               ├── badge.tsx (200 tokens)
               ├── button.tsx (400 tokens)
               ├── card.tsx (400 tokens)
               ├── checkbox.tsx (200 tokens)
               ├── color-picker.tsx (1100 tokens)
               ├── combo-input.tsx (800 tokens)
               ├── combobox.tsx (500 tokens)
               ├── command.tsx (900 tokens)
               ├── dialog.tsx (800 tokens)
               ├── input.tsx (500 tokens)
               ├── label.tsx (100 tokens)
               ├── multi-combobox.tsx (700 tokens)
               ├── popover.tsx (300 tokens)
               ├── select.tsx (1100 tokens)
               ├── switch.tsx (200 tokens)
               ├── tabs.tsx (400 tokens)
               ├── textarea.tsx (200 tokens)
               ├── toast.tsx (300 tokens)
               ├── tooltip.tsx (200 tokens)
         ├── i18n.ts (100 tokens)
         ├── index.css (1200 tokens)
         ├── lib/
            ├── api.ts (2000 tokens)
            ├── db.ts (700 tokens)
            ├── utils.ts
         ├── locales/
            ├── en.json (2.4k tokens)
            ├── zh.json (1800 tokens)
         ├── main.tsx (100 tokens)
         ├── routes.tsx (200 tokens)
         ├── styles/
            ├── animations.css (200 tokens)
         ├── types.ts (300 tokens)
         ├── utils/
            ├── statusline.ts (800 tokens)
         ├── vite-env.d.ts (omitted)
      ├── tsconfig.app.json (100 tokens)
      ├── tsconfig.json (100 tokens)
      ├── vite.config.ts (100 tokens)
├── pnpm-lock.yaml (omitted)
├── pnpm-workspace.yaml
├── scripts/
   ├── build-cli.js (700 tokens)
   ├── build-core.js (100 tokens)
   ├── build-server.js (300 tokens)
   ├── build-shared.js (200 tokens)
   ├── build.js (200 tokens)
   ├── release.sh (900 tokens)
├── tsconfig.base.json (100 tokens)
├── tsconfig.json (100 tokens)
```


## /.github/workflows/docker-publish.yml

```yml path="/.github/workflows/docker-publish.yml" 
name: Build and Publish Docker Image

on:
  push:
    tags:
      - 'v*.*.*'
  workflow_dispatch:

env:
  DOCKER_IMAGE: musistudio/claude-code-router

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest

      - name: Get version
        id: version
        run: |
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            VERSION=${GITHUB_REF#refs/tags/}
          else
            VERSION=$(node -p "require('./package.json').version")
          fi
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Docker image version: $VERSION"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build packages
        run: |
          pnpm build

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.DOCKER_IMAGE }}
          tags: |
            type=semver,pattern={{version}},value=${{ steps.version.outputs.version }}
            type=semver,pattern={{major}}.{{minor}},value=${{ steps.version.outputs.version }}
            type=raw,value=latest
            type=sha

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./packages/server/Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Image digest
        run: echo "Image pushed with digest ${{ steps.meta.outputs.digest }}"

```

## /.github/workflows/docs.yml

```yml path="/.github/workflows/docs.yml" 
name: Deploy Docs to GitHub Pages

on:
  push:
    branches:
      - main
    paths:
      - 'docs/**'
      - '.github/workflows/docs.yml'
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        working-directory: ./docs
        run: npm install

      - name: Build Docusaurus
        working-directory: ./docs
        run: npm run build

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./docs/build

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

```

## /.gitignore

```gitignore path="/.gitignore" 
node_modules
.env
log.txt
.idea
dist
.DS_Store
.vscode
tsconfig.tsbuildinfo

# Documentation build output
docs/build
docs/.docusaurus

```

## /.npmignore

```npmignore path="/.npmignore" 
src
node_modules
.claude
CLAUDE.md
screenshoots
.DS_Store
.vscode
.idea
.env
.blog
docs
.log
blog
config.json
ui
scripts
packages
custom-router.example.js
examples

```

## /CLAUDE.md

# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Claude Code Router is a tool that routes Claude Code requests to different LLM providers. It uses a Monorepo architecture with four main packages:

- **cli** (`@musistudio/claude-code-router`): Command-line tool providing the `ccr` command
- **server** (`@CCR/server`): Core server handling API routing and transformations
- **shared** (`@CCR/shared`): Shared constants, utilities, and preset management
- **ui** (`@CCR/ui`): Web management interface (React + Vite)

## Build Commands

### Build all packages
```bash
pnpm build
```

### Build individual packages
```bash
pnpm build:cli      # Build CLI
pnpm build:server   # Build Server
pnpm build:ui       # Build UI
```

### Development mode
```bash
pnpm dev:cli        # Develop CLI (ts-node)
pnpm dev:server     # Develop Server (ts-node)
pnpm dev:ui         # Develop UI (Vite)
```

### Publish
```bash
pnpm release        # Build and publish all packages
```

## Core Architecture

### 1. Routing System (packages/server/src/utils/router.ts)

The routing logic determines which model a request should be sent to:

- **Default routing**: Uses `Router.default` configuration
- **Project-level routing**: Checks `~/.claude/projects/<project-id>/claude-code-router.json`
- **Custom routing**: Loads custom JavaScript router function via `CUSTOM_ROUTER_PATH`
- **Built-in scenario routing**:
  - `background`: Background tasks (typically lightweight models)
  - `think`: Thinking-intensive tasks (Plan Mode)
  - `longContext`: Long context (exceeds `longContextThreshold` tokens)
  - `webSearch`: Web search tasks
  - `image`: Image-related tasks

Token calculation uses `tiktoken` (cl100k_base) to estimate request size.

### 2. Transformer System

The project uses the `@musistudio/llms` package (external dependency) to handle request/response transformations. Transformers adapt to different provider API differences:

- Built-in transformers: `anthropic`, `deepseek`, `gemini`, `openrouter`, `groq`, `maxtoken`, `tooluse`, `reasoning`, `enhancetool`, etc.
- Custom transformers: Load external plugins via `transformers` array in `config.json`

Transformer configuration supports:
- Global application (provider level)
- Model-specific application
- Option passing (e.g., `max_tokens` parameter for `maxtoken`)

### 3. Agent System (packages/server/src/agents/)

Agents are pluggable feature modules that can:
- Detect whether to handle a request (`shouldHandle`)
- Modify requests (`reqHandler`)
- Provide custom tools (`tools`)

Built-in agents:
- **imageAgent**: Handles image-related tasks

Agent tool call flow:
1. Detect and mark agents in `preHandler` hook
2. Add agent tools to the request
3. Intercept tool call events in `onSend` hook
4. Execute agent tool and initiate new LLM request
5. Stream results back

### 4. SSE Stream Processing

The server uses custom Transform streams to handle Server-Sent Events:
- `SSEParserTransform`: Parses SSE text stream into event objects
- `SSESerializerTransform`: Serializes event objects into SSE text stream
- `rewriteStream`: Intercepts and modifies stream data (for agent tool calls)

### 5. Configuration Management

Configuration file location: `~/.claude-code-router/config.json`

Key features:
- Supports environment variable interpolation (`$VAR_NAME` or `${VAR_NAME}`)
- JSON5 format (supports comments)
- Automatic backups (keeps last 3 backups)
- Hot reload requires service restart (`ccr restart`)

Configuration validation:
- If `Providers` are configured, both `HOST` and `APIKEY` must be set
- Otherwise listens on `0.0.0.0` without authentication

### 6. Logging System

Two separate logging systems:

**Server-level logs** (pino):
- Location: `~/.claude-code-router/logs/ccr-*.log`
- Content: HTTP requests, API calls, server events
- Configuration: `LOG_LEVEL` (fatal/error/warn/info/debug/trace)

**Application-level logs**:
- Location: `~/.claude-code-router/claude-code-router.log`
- Content: Routing decisions, business logic events

## CLI Commands

```bash
ccr start      # Start server
ccr stop       # Stop server
ccr restart    # Restart server
ccr status     # Show status
ccr code       # Execute claude command
ccr model      # Interactive model selection and configuration
ccr preset     # Manage presets (export, install, list, info, delete)
ccr activate   # Output shell environment variables (for integration)
ccr ui         # Open Web UI
ccr statusline # Integrated statusline (reads JSON from stdin)
```

### Preset Commands

```bash
ccr preset export <name>      # Export current configuration as a preset
ccr preset install <source>   # Install a preset from file, URL, or name
ccr preset list               # List all installed presets
ccr preset info <name>        # Show preset information
ccr preset delete <name>      # Delete a preset
```

## Subagent Routing

Use special tags in subagent prompts to specify models:
```
<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>
Please help me analyze this code...
```

## Preset System

The preset system allows users to save, share, and reuse configurations easily.

### Preset Structure

Presets are stored in `~/.claude-code-router/presets/<preset-name>/manifest.json`

Each preset contains:
- **Metadata**: name, version, description, author, keywords, etc.
- **Configuration**: Providers, Router, transformers, and other settings
- **Dynamic Schema** (optional): Input fields for collecting required information during installation
- **Required Inputs** (optional): Fields that need to be filled during installation (e.g., API keys)

### Core Functions

Located in `packages/shared/src/preset/`:

- **export.ts**: Export current configuration as a preset directory
  - `exportPreset(presetName, config, options)`: Creates preset directory with manifest.json
  - Automatically sanitizes sensitive data (api_key fields become `{{field}}` placeholders)

- **install.ts**: Install and manage presets
  - `installPreset(preset, config, options)`: Install preset to config
  - `loadPreset(source)`: Load preset from directory
  - `listPresets()`: List all installed presets
  - `isPresetInstalled(presetName)`: Check if preset is installed
  - `validatePreset(preset)`: Validate preset structure

- **merge.ts**: Merge preset configuration with existing config
  - Handles conflicts using different strategies (ask, overwrite, merge, skip)

- **sensitiveFields.ts**: Identify and sanitize sensitive fields
  - Detects api_key, password, secret fields automatically
  - Replaces sensitive values with environment variable placeholders

### Preset File Format

**manifest.json** (in preset directory):
```json
{
  "name": "my-preset",
  "version": "1.0.0",
  "description": "My configuration",
  "author": "Author Name",
  "keywords": ["openai", "production"],
  "Providers": [...],
  "Router": {...},
  "schema": [
    {
      "id": "apiKey",
      "type": "password",
      "label": "OpenAI API Key",
      "prompt": "Enter your OpenAI API key"
    }
  ]
}
```

### CLI Integration

The CLI layer (`packages/cli/src/utils/preset/`) handles:
- User interaction and prompts
- File operations
- Display formatting

Key files:
- `commands.ts`: Command handlers for `ccr preset` subcommands
- `export.ts`: CLI wrapper for export functionality
- `install.ts`: CLI wrapper for install functionality

## Dependencies

```
cli → server → shared
server → @musistudio/llms (core routing and transformation logic)
ui (standalone frontend application)
```

## Development Notes

1. **Node.js version**: Requires >= 18.0.0
2. **Package manager**: Uses pnpm (monorepo depends on workspace protocol)
3. **TypeScript**: All packages use TypeScript, but UI package is ESM module
4. **Build tools**:
   - cli/server/shared: esbuild
   - ui: Vite + TypeScript
5. **@musistudio/llms**: This is an external dependency package providing the core server framework and transformer functionality, type definitions in `packages/server/src/types.d.ts`
6. **Code comments**: All comments in code MUST be written in English
7. **Documentation**: When implementing new features, add documentation to the docs project instead of creating standalone md files

## Configuration Example Locations

- Main configuration example: Complete example in README.md
- Custom router example: `custom-router.example.js`


## /README.md

![](blog/images/claude-code-router-img.png)

[![](https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%B3-%E4%B8%AD%E6%96%87%E7%89%88-ff0000?style=flat)](README_zh.md)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white)](https://discord.gg/rdftVMaUcS)
[![](https://img.shields.io/github/license/musistudio/claude-code-router)](https://github.com/musistudio/claude-code-router/blob/main/LICENSE)

<hr>

![](blog/images/sponsors/glm-en.jpg)
> This project is sponsored by Z.ai, supporting us with their GLM CODING PLAN.

> GLM CODING PLAN is a subscription service designed for AI coding, starting at just $10/month. It provides access to their flagship GLM-4.7 & (GLM-5 Only Available  for Pro Users)model across 10+ popular AI coding tools (Claude Code, Cline, Roo Code, etc.), offering developers top-tier, fast, and stable coding experiences.

> Get 10% OFF GLM CODING PLAN:https://z.ai/subscribe?ic=8JVLJQFSKB  

> [Progressive Disclosure of Agent Tools from the Perspective of CLI Tool Style](/blog/en/progressive-disclosure-of-agent-tools-from-the-perspective-of-cli-tool-style.md)

> A powerful tool to route Claude Code requests to different models and customize any request.

![](blog/images/claude-code.png)

## ✨ Features

- **Model Routing**: Route requests to different models based on your needs (e.g., background tasks, thinking, long context).
- **Multi-Provider Support**: Supports various model providers like OpenRouter, DeepSeek, Ollama, Gemini, Volcengine, and SiliconFlow.
- **Request/Response Transformation**: Customize requests and responses for different providers using transformers.
- **Dynamic Model Switching**: Switch models on-the-fly within Claude Code using the `/model` command.
- **CLI Model Management**: Manage models and providers directly from the terminal with `ccr model`.
- **GitHub Actions Integration**: Trigger Claude Code tasks in your GitHub workflows.
- **Plugin System**: Extend functionality with custom transformers.

## 🚀 Getting Started

### 1. Installation

First, ensure you have [Claude Code](https://docs.anthropic.com/en/docs/claude-code/quickstart) installed:

```shell
npm install -g @anthropic-ai/claude-code
```

Then, install Claude Code Router:

```shell
npm install -g @musistudio/claude-code-router
```

### 2. Configuration

Create and configure your `~/.claude-code-router/config.json` file. For more details, you can refer to `config.example.json`.

The `config.json` file has several key sections:

- **`PROXY_URL`** (optional): You can set a proxy for API requests, for example: `"PROXY_URL": "http://127.0.0.1:7890"`.
- **`LOG`** (optional): You can enable logging by setting it to `true`. When set to `false`, no log files will be created. Default is `true`.
- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"debug"`.
- **Logging Systems**: The Claude Code Router uses two separate logging systems:
  - **Server-level logs**: HTTP requests, API calls, and server events are logged using pino in the `~/.claude-code-router/logs/` directory with filenames like `ccr-*.log`
  - **Application-level logs**: Routing decisions and business logic events are logged in `~/.claude-code-router/claude-code-router.log`
- **`APIKEY`** (optional): You can set a secret key to authenticate requests. When set, clients must provide this key in the `Authorization` header (e.g., `Bearer your-secret-key`) or the `x-api-key` header. Example: `"APIKEY": "your-secret-key"`.
- **`HOST`** (optional): You can set the host address for the server. If `APIKEY` is not set, the host will be forced to `127.0.0.1` for security reasons to prevent unauthorized access. Example: `"HOST": "0.0.0.0"`.
- **`NON_INTERACTIVE_MODE`** (optional): When set to `true`, enables compatibility with non-interactive environments like GitHub Actions, Docker containers, or other CI/CD systems. This sets appropriate environment variables (`CI=true`, `FORCE_COLOR=0`, etc.) and configures stdin handling to prevent the process from hanging in automated environments. Example: `"NON_INTERACTIVE_MODE": true`.

- **`Providers`**: Used to configure different model providers.
- **`Router`**: Used to set up routing rules. `default` specifies the default model, which will be used for all requests if no other route is configured.
- **`API_TIMEOUT_MS`**: Specifies the timeout for API calls in milliseconds.

#### Environment Variable Interpolation

Claude Code Router supports environment variable interpolation for secure API key management. You can reference environment variables in your `config.json` using either `$VAR_NAME` or `${VAR_NAME}` syntax:

```json
{
  "OPENAI_API_KEY": "$OPENAI_API_KEY",
  "GEMINI_API_KEY": "${GEMINI_API_KEY}",
  "Providers": [
    {
      "name": "openai",
      "api_base_url": "https://api.openai.com/v1/chat/completions",
      "api_key": "$OPENAI_API_KEY",
      "models": ["gpt-5", "gpt-5-mini"]
    }
  ]
}
```

This allows you to keep sensitive API keys in environment variables instead of hardcoding them in configuration files. The interpolation works recursively through nested objects and arrays.

Here is a comprehensive example:

```json
{
  "APIKEY": "your-secret-key",
  "PROXY_URL": "http://127.0.0.1:7890",
  "LOG": true,
  "API_TIMEOUT_MS": 600000,
  "NON_INTERACTIVE_MODE": false,
  "Providers": [
    {
      "name": "openrouter",
      "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
      "api_key": "sk-xxx",
      "models": [
        "google/gemini-2.5-pro-preview",
        "anthropic/claude-sonnet-4",
        "anthropic/claude-3.5-sonnet",
        "anthropic/claude-3.7-sonnet:thinking"
      ],
      "transformer": {
        "use": ["openrouter"]
      }
    },
    {
      "name": "deepseek",
      "api_base_url": "https://api.deepseek.com/chat/completions",
      "api_key": "sk-xxx",
      "models": ["deepseek-chat", "deepseek-reasoner"],
      "transformer": {
        "use": ["deepseek"],
        "deepseek-chat": {
          "use": ["tooluse"]
        }
      }
    },
    {
      "name": "ollama",
      "api_base_url": "http://localhost:11434/v1/chat/completions",
      "api_key": "ollama",
      "models": ["qwen2.5-coder:latest"]
    },
    {
      "name": "gemini",
      "api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
      "api_key": "sk-xxx",
      "models": ["gemini-2.5-flash", "gemini-2.5-pro"],
      "transformer": {
        "use": ["gemini"]
      }
    },
    {
      "name": "volcengine",
      "api_base_url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
      "api_key": "sk-xxx",
      "models": ["deepseek-v3-250324", "deepseek-r1-250528"],
      "transformer": {
        "use": ["deepseek"]
      }
    },
    {
      "name": "modelscope",
      "api_base_url": "https://api-inference.modelscope.cn/v1/chat/completions",
      "api_key": "",
      "models": ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507"],
      "transformer": {
        "use": [
          [
            "maxtoken",
            {
              "max_tokens": 65536
            }
          ],
          "enhancetool"
        ],
        "Qwen/Qwen3-235B-A22B-Thinking-2507": {
          "use": ["reasoning"]
        }
      }
    },
    {
      "name": "dashscope",
      "api_base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
      "api_key": "",
      "models": ["qwen3-coder-plus"],
      "transformer": {
        "use": [
          [
            "maxtoken",
            {
              "max_tokens": 65536
            }
          ],
          "enhancetool"
        ]
      }
    },
    {
      "name": "aihubmix",
      "api_base_url": "https://aihubmix.com/v1/chat/completions",
      "api_key": "sk-",
      "models": [
        "Z/glm-4.5",
        "claude-opus-4-20250514",
        "gemini-2.5-pro"
      ]
    }
  ],
  "Router": {
    "default": "deepseek,deepseek-chat",
    "background": "ollama,qwen2.5-coder:latest",
    "think": "deepseek,deepseek-reasoner",
    "longContext": "openrouter,google/gemini-2.5-pro-preview",
    "longContextThreshold": 60000,
    "webSearch": "gemini,gemini-2.5-flash"
  }
}
```

### 3. Running Claude Code with the Router

Start Claude Code using the router:

```shell
ccr code
```

> **Note**: After modifying the configuration file, you need to restart the service for the changes to take effect:
>
> ```shell
> ccr restart
> ```

### 4. UI Mode

For a more intuitive experience, you can use the UI mode to manage your configuration:

```shell
ccr ui
```

This will open a web-based interface where you can easily view and edit your `config.json` file.

![UI](/blog/images/ui.png)

### 5. CLI Model Management

For users who prefer terminal-based workflows, you can use the interactive CLI model selector:

```shell
ccr model
```
![](blog/images/models.gif)

This command provides an interactive interface to:

- View current configuration:
- See all configured models (default, background, think, longContext, webSearch, image)
- Switch models: Quickly change which model is used for each router type
- Add new models: Add models to existing providers
- Create new providers: Set up complete provider configurations including:
   - Provider name and API endpoint
   - API key
   - Available models
   - Transformer configuration with support for:
     - Multiple transformers (openrouter, deepseek, gemini, etc.)
     - Transformer options (e.g., maxtoken with custom limits)
     - Provider-specific routing (e.g., OpenRouter provider preferences)

The CLI tool validates all inputs and provides helpful prompts to guide you through the configuration process, making it easy to manage complex setups without editing JSON files manually.

### 6. Presets Management

Presets allow you to save, share, and reuse configurations easily. You can export your current configuration as a preset and install presets from files or URLs.

```shell
# Export current configuration as a preset
ccr preset export my-preset

# Export with metadata
ccr preset export my-preset --description "My OpenAI config" --author "Your Name" --tags "openai,production"

# Install a preset from local directory
ccr preset install /path/to/preset

# List all installed presets
ccr preset list

# Show preset information
ccr preset info my-preset

# Delete a preset
ccr preset delete my-preset
```

**Preset Features:**
- **Export**: Save your current configuration as a preset directory (with manifest.json)
- **Install**: Install presets from local directories
- **Sensitive Data Handling**: API keys and other sensitive data are automatically sanitized during export (marked as `{{field}}` placeholders)
- **Dynamic Configuration**: Presets can include input schemas for collecting required information during installation
- **Version Control**: Each preset includes version metadata for tracking updates

**Preset File Structure:**
```
~/.claude-code-router/presets/
├── my-preset/
│   └── manifest.json    # Contains configuration and metadata
```

### 7. Activate Command (Environment Variables Setup)

The `activate` command allows you to set up environment variables globally in your shell, enabling you to use the `claude` command directly or integrate Claude Code Router with applications built using the Agent SDK.

To activate the environment variables, run:

```shell
eval "$(ccr activate)"
```

This command outputs the necessary environment variables in shell-friendly format, which are then set in your current shell session. After activation, you can:

- **Use `claude` command directly**: Run `claude` commands without needing to use `ccr code`. The `claude` command will automatically route requests through Claude Code Router.
- **Integrate with Agent SDK applications**: Applications built with the Anthropic Agent SDK will automatically use the configured router and models.

The `activate` command sets the following environment variables:

- `ANTHROPIC_AUTH_TOKEN`: API key from your configuration
- `ANTHROPIC_BASE_URL`: The local router endpoint (default: `http://127.0.0.1:3456`)
- `NO_PROXY`: Set to `127.0.0.1` to prevent proxy interference
- `DISABLE_TELEMETRY`: Disables telemetry
- `DISABLE_COST_WARNINGS`: Disables cost warnings
- `API_TIMEOUT_MS`: API timeout from your configuration

> **Note**: Make sure the Claude Code Router service is running (`ccr start`) before using the activated environment variables. The environment variables are only valid for the current shell session. To make them persistent, you can add `eval "$(ccr activate)"` to your shell configuration file (e.g., `~/.zshrc` or `~/.bashrc`).

#### Providers

The `Providers` array is where you define the different model providers you want to use. Each provider object requires:

- `name`: A unique name for the provider.
- `api_base_url`: The full API endpoint for chat completions.
- `api_key`: Your API key for the provider.
- `models`: A list of model names available from this provider.
- `transformer` (optional): Specifies transformers to process requests and responses.

#### Transformers

Transformers allow you to modify the request and response payloads to ensure compatibility with different provider APIs.

- **Global Transformer**: Apply a transformer to all models from a provider. In this example, the `openrouter` transformer is applied to all models under the `openrouter` provider.
  ```json
  {
    "name": "openrouter",
    "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
    "api_key": "sk-xxx",
    "models": [
      "google/gemini-2.5-pro-preview",
      "anthropic/claude-sonnet-4",
      "anthropic/claude-3.5-sonnet"
    ],
    "transformer": { "use": ["openrouter"] }
  }
  ```
- **Model-Specific Transformer**: Apply a transformer to a specific model. In this example, the `deepseek` transformer is applied to all models, and an additional `tooluse` transformer is applied only to the `deepseek-chat` model.

  ```json
  {
    "name": "deepseek",
    "api_base_url": "https://api.deepseek.com/chat/completions",
    "api_key": "sk-xxx",
    "models": ["deepseek-chat", "deepseek-reasoner"],
    "transformer": {
      "use": ["deepseek"],
      "deepseek-chat": { "use": ["tooluse"] }
    }
  }
  ```

- **Passing Options to a Transformer**: Some transformers, like `maxtoken`, accept options. To pass options, use a nested array where the first element is the transformer name and the second is an options object.
  ```json
  {
    "name": "siliconflow",
    "api_base_url": "https://api.siliconflow.cn/v1/chat/completions",
    "api_key": "sk-xxx",
    "models": ["moonshotai/Kimi-K2-Instruct"],
    "transformer": {
      "use": [
        [
          "maxtoken",
          {
            "max_tokens": 16384
          }
        ]
      ]
    }
  }
  ```

**Available Built-in Transformers:**

- `Anthropic`:If you use only the `Anthropic` transformer, it will preserve the original request and response parameters(you can use it to connect directly to an Anthropic endpoint).
- `deepseek`: Adapts requests/responses for DeepSeek API.
- `gemini`: Adapts requests/responses for Gemini API.
- `openrouter`: Adapts requests/responses for OpenRouter API. It can also accept a `provider` routing parameter to specify which underlying providers OpenRouter should use. For more details, refer to the [OpenRouter documentation](https://openrouter.ai/docs/features/provider-routing). See an example below:
  ```json
    "transformer": {
      "use": ["openrouter"],
      "moonshotai/kimi-k2": {
        "use": [
          [
            "openrouter",
            {
              "provider": {
                "only": ["moonshotai/fp8"]
              }
            }
          ]
        ]
      }
    }
  ```
- `groq`: Adapts requests/responses for groq API.
- `maxtoken`: Sets a specific `max_tokens` value.
- `tooluse`: Optimizes tool usage for certain models via `tool_choice`.
- `gemini-cli` (experimental): Unofficial support for Gemini via Gemini CLI [gemini-cli.js](https://gist.github.com/musistudio/1c13a65f35916a7ab690649d3df8d1cd).
- `reasoning`: Used to process the `reasoning_content` field.
- `sampling`: Used to process sampling information fields such as `temperature`, `top_p`, `top_k`, and `repetition_penalty`.
- `enhancetool`: Adds a layer of error tolerance to the tool call parameters returned by the LLM (this will cause the tool call information to no longer be streamed).
- `cleancache`: Clears the `cache_control` field from requests.
- `vertex-gemini`: Handles the Gemini API using Vertex authentication.
- `chutes-glm` Unofficial support for GLM 4.5 model via Chutes [chutes-glm-transformer.js](https://gist.github.com/vitobotta/2be3f33722e05e8d4f9d2b0138b8c863).
- `qwen-cli` (experimental): Unofficial support for qwen3-coder-plus model via Qwen CLI [qwen-cli.js](https://gist.github.com/musistudio/f5a67841ced39912fd99e42200d5ca8b).
- `rovo-cli` (experimental): Unofficial support for gpt-5 via Atlassian Rovo Dev CLI [rovo-cli.js](https://gist.github.com/SaseQ/c2a20a38b11276537ec5332d1f7a5e53).

**Custom Transformers:**

You can also create your own transformers and load them via the `transformers` field in `config.json`.

```json
{
  "transformers": [
    {
      "path": "/User/xxx/.claude-code-router/plugins/gemini-cli.js",
      "options": {
        "project": "xxx"
      }
    }
  ]
}
```

#### Router

The `Router` object defines which model to use for different scenarios:

- `default`: The default model for general tasks.
- `background`: A model for background tasks. This can be a smaller, local model to save costs.
- `think`: A model for reasoning-heavy tasks, like Plan Mode.
- `longContext`: A model for handling long contexts (e.g., > 60K tokens).
- `longContextThreshold` (optional): The token count threshold for triggering the long context model. Defaults to 60000 if not specified.
- `webSearch`: Used for handling web search tasks and this requires the model itself to support the feature. If you're using openrouter, you need to add the `:online` suffix after the model name.
- `image` (beta): Used for handling image-related tasks (supported by CCR’s built-in agent). If the model does not support tool calling, you need to set the `config.forceUseImageAgent` property to `true`.

- You can also switch models dynamically in Claude Code with the `/model` command:
`/model provider_name,model_name`
Example: `/model openrouter,anthropic/claude-3.5-sonnet`

#### Custom Router

For more advanced routing logic, you can specify a custom router script via the `CUSTOM_ROUTER_PATH` in your `config.json`. This allows you to implement complex routing rules beyond the default scenarios.

In your `config.json`:

```json
{
  "CUSTOM_ROUTER_PATH": "/User/xxx/.claude-code-router/custom-router.js"
}
```

The custom router file must be a JavaScript module that exports an `async` function. This function receives the request object and the config object as arguments and should return the provider and model name as a string (e.g., `"provider_name,model_name"`), or `null` to fall back to the default router.

Here is an example of a `custom-router.js` based on `custom-router.example.js`:

```javascript
// /User/xxx/.claude-code-router/custom-router.js

/**
 * A custom router function to determine which model to use based on the request.
 *
 * @param {object} req - The request object from Claude Code, containing the request body.
 * @param {object} config - The application's config object.
 * @returns {Promise<string|null>} - A promise that resolves to the "provider,model_name" string, or null to use the default router.
 */
module.exports = async function router(req, config) {
  const userMessage = req.body.messages.find((m) => m.role === "user")?.content;

  if (userMessage && userMessage.includes("explain this code")) {
    // Use a powerful model for code explanation
    return "openrouter,anthropic/claude-3.5-sonnet";
  }

  // Fallback to the default router configuration
  return null;
};
```

##### Subagent Routing

For routing within subagents, you must specify a particular provider and model by including `<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>` at the **beginning** of the subagent's prompt. This allows you to direct specific subagent tasks to designated models.

**Example:**

```
<CCR-SUBAGENT-MODEL>openrouter,anthropic/claude-3.5-sonnet</CCR-SUBAGENT-MODEL>
Please help me analyze this code snippet for potential optimizations...
```

## Status Line (Beta)
To better monitor the status of claude-code-router at runtime, version v1.0.40 includes a built-in statusline tool, which you can enable in the UI.
![statusline-config.png](/blog/images/statusline-config.png)

The effect is as follows:
![statusline](/blog/images/statusline.png)

## 🤖 GitHub Actions

Integrate Claude Code Router into your CI/CD pipeline. After setting up [Claude Code Actions](https://docs.anthropic.com/en/docs/claude-code/github-actions), modify your `.github/workflows/claude.yaml` to use the router:

```yaml
name: Claude Code

on:
  issue_comment:
    types: [created]
  # ... other triggers

jobs:
  claude:
    if: |
      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
      # ... other conditions
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
      issues: read
      id-token: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Prepare Environment
        run: |
          curl -fsSL https://bun.sh/install | bash
          mkdir -p $HOME/.claude-code-router
          cat << 'EOF' > $HOME/.claude-code-router/config.json
          {
            "log": true,
            "NON_INTERACTIVE_MODE": true,
            "OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
            "OPENAI_BASE_URL": "https://api.deepseek.com",
            "OPENAI_MODEL": "deepseek-chat"
          }
          EOF
        shell: bash

      - name: Start Claude Code Router
        run: |
          nohup ~/.bun/bin/bunx @musistudio/claude-code-router@1.0.8 start &
        shell: bash

      - name: Run Claude Code
        id: claude
        uses: anthropics/claude-code-action@beta
        env:
          ANTHROPIC_BASE_URL: http://localhost:3456
        with:
          anthropic_api_key: "any-string-is-ok"
```

> **Note**: When running in GitHub Actions or other automation environments, make sure to set `"NON_INTERACTIVE_MODE": true` in your configuration to prevent the process from hanging due to stdin handling issues.

This setup allows for interesting automations, like running tasks during off-peak hours to reduce API costs.

## 📝 Further Reading

- [Project Motivation and How It Works](blog/en/project-motivation-and-how-it-works.md)
- [Maybe We Can Do More with the Router](blog/en/maybe-we-can-do-more-with-the-route.md)
- [GLM-4.6 Supports Reasoning and Interleaved Thinking](blog/en/glm-4.6-supports-reasoning.md)

## ❤️ Support & Sponsoring

If you find this project helpful, please consider sponsoring its development. Your support is greatly appreciated!

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F31GN2GM)

[Paypal](https://paypal.me/musistudio1999)

<table>
  <tr>
    <td><img src="/blog/images/alipay.jpg" width="200" alt="Alipay" /></td>
    <td><img src="/blog/images/wechat.jpg" width="200" alt="WeChat Pay" /></td>
  </tr>
</table>

### Our Sponsors

A huge thank you to all our sponsors for their generous support!


- [AIHubmix](https://aihubmix.com/)
- [BurnCloud](https://ai.burncloud.com)
- [302.AI](https://share.302.ai/ZGVF9w)
- [Z智谱](https://www.bigmodel.cn/claude-code?ic=FPF9IVAGFJ)
- @Simon Leischnig
- [@duanshuaimin](https://github.com/duanshuaimin)
- [@vrgitadmin](https://github.com/vrgitadmin)
- @\*o
- [@ceilwoo](https://github.com/ceilwoo)
- @\*说
- @\*更
- @K\*g
- @R\*R
- [@bobleer](https://github.com/bobleer)
- @\*苗
- @\*划
- [@Clarence-pan](https://github.com/Clarence-pan)
- [@carter003](https://github.com/carter003)
- @S\*r
- @\*晖
- @\*敏
- @Z\*z
- @\*然
- [@cluic](https://github.com/cluic)
- @\*苗
- [@PromptExpert](https://github.com/PromptExpert)
- @\*应
- [@yusnake](https://github.com/yusnake)
- @\*飞
- @董\*
- @\*汀
- @\*涯
- @\*:-)
- @\*\*磊
- @\*琢
- @\*成
- @Z\*o
- @\*琨
- [@congzhangzh](https://github.com/congzhangzh)
- @\*\_
- @Z\*m
- @*鑫
- @c\*y
- @\*昕
- [@witsice](https://github.com/witsice)
- @b\*g
- @\*亿
- @\*辉
- @JACK
- @\*光
- @W\*l
- [@kesku](https://github.com/kesku)
- [@biguncle](https://github.com/biguncle)
- @二吉吉
- @a\*g
- @\*林
- @\*咸
- @\*明
- @S\*y
- @f\*o
- @\*智
- @F\*t
- @r\*c
- [@qierkang](http://github.com/qierkang)
- @\*军
- [@snrise-z](http://github.com/snrise-z)
- @\*王
- [@greatheart1000](http://github.com/greatheart1000)
- @\*王
- @zcutlip
- [@Peng-YM](http://github.com/Peng-YM)
- @\*更
- @\*.
- @F\*t
- @\*政
- @\*铭
- @\*叶
- @七\*o
- @\*青
- @\*\*晨
- @\*远
- @\*霄
- @\*\*吉
- @\*\*飞
- @\*\*驰
- @x\*g
- @\*\*东
- @\*落
- @哆\*k
- @\*涛
- [@苗大](https://github.com/WitMiao)
- @\*呢
- @\d*u
- @crizcraig
- s\*s
- \*火
- \*勤
- \*\*锟
- \*涛
- \*\*明
- \*知
- \*语
- \*瓜


(If your name is masked, please contact me via my homepage email to update it with your GitHub username.)


## /README_zh.md

![](blog/images/claude-code-router-img.png)

[![](https://img.shields.io/badge/%F0%9F%87%AC%F0%9F%87%A7-English-000aff?style=flat)](README.md)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white)](https://discord.gg/rdftVMaUcS)
[![](https://img.shields.io/github/license/musistudio/claude-code-router)](https://github.com/musistudio/claude-code-router/blob/main/LICENSE)

<hr>

![](blog/images/sponsors/glm-zh.jpg)
> 本项目由 Z智谱 提供赞助, 他们通过 GLM CODING PLAN 对本项目提供技术支持。
> GLM CODING PLAN 是专为AI编码打造的订阅套餐,每月最低仅需20元,即可在十余款主流AI编码工具如 Claude Code、Cline、Roo Code 中畅享智谱旗舰模型GLM-4.7(受限于算力,目前仅限Pro用户开放),为开发者提供顶尖的编码体验。
> 智谱AI为本产品提供了特别优惠,使用以下链接购买可以享受九折优惠:https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII

> [从CLI工具风格看工具渐进式披露](/blog/zh/从CLI工具风格看工具渐进式披露.md)

> 一款强大的工具,可将 Claude Code 请求路由到不同的模型,并自定义任何请求。

![](blog/images/claude-code.png)


## ✨ 功能

-   **模型路由**: 根据您的需求将请求路由到不同的模型(例如,后台任务、思考、长上下文)。
-   **多提供商支持**: 支持 OpenRouter、DeepSeek、Ollama、Gemini、Volcengine 和 SiliconFlow 等各种模型提供商。
-   **请求/响应转换**: 使用转换器为不同的提供商自定义请求和响应。
-   **动态模型切换**: 在 Claude Code 中使用 `/model` 命令动态切换模型。
-   **GitHub Actions 集成**: 在您的 GitHub 工作流程中触发 Claude Code 任务。
-   **插件系统**: 使用自定义转换器扩展功能。

## 🚀 快速入门

### 1. 安装

首先,请确保您已安装 [Claude Code](https://docs.anthropic.com/en/docs/claude-code/quickstart):

```shell
npm install -g @anthropic-ai/claude-code
```

然后,安装 Claude Code Router:

```shell
npm install -g @musistudio/claude-code-router
```

### 2. 配置

创建并配置您的 `~/.claude-code-router/config.json` 文件。有关更多详细信息,您可以参考 `config.example.json`。

`config.json` 文件有几个关键部分:
- **`PROXY_URL`** (可选): 您可以为 API 请求设置代理,例如:`"PROXY_URL": "http://127.0.0.1:7890"`。
- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。当设置为 `false` 时,将不会创建日志文件。默认值为 `true`。
- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"debug"`。
- **日志系统**: Claude Code Router 使用两个独立的日志系统:
  - **服务器级别日志**: HTTP 请求、API 调用和服务器事件使用 pino 记录在 `~/.claude-code-router/logs/` 目录中,文件名类似于 `ccr-*.log`
  - **应用程序级别日志**: 路由决策和业务逻辑事件记录在 `~/.claude-code-router/claude-code-router.log` 文件中
- **`APIKEY`** (可选): 您可以设置一个密钥来进行身份验证。设置后,客户端请求必须在 `Authorization` 请求头 (例如, `Bearer your-secret-key`) 或 `x-api-key` 请求头中提供此密钥。例如:`"APIKEY": "your-secret-key"`。
- **`HOST`** (可选): 您可以设置服务的主机地址。如果未设置 `APIKEY`,出于安全考虑,主机地址将强制设置为 `127.0.0.1`,以防止未经授权的访问。例如:`"HOST": "0.0.0.0"`。
- **`NON_INTERACTIVE_MODE`** (可选): 当设置为 `true` 时,启用与非交互式环境(如 GitHub Actions、Docker 容器或其他 CI/CD 系统)的兼容性。这会设置适当的环境变量(`CI=true`、`FORCE_COLOR=0` 等)并配置 stdin 处理,以防止进程在自动化环境中挂起。例如:`"NON_INTERACTIVE_MODE": true`。
- **`Providers`**: 用于配置不同的模型提供商。
- **`Router`**: 用于设置路由规则。`default` 指定默认模型,如果未配置其他路由,则该模型将用于所有请求。
- **`API_TIMEOUT_MS`**: API 请求超时时间,单位为毫秒。

这是一个综合示例:

```json
{
  "APIKEY": "your-secret-key",
  "PROXY_URL": "http://127.0.0.1:7890",
  "LOG": true,
  "API_TIMEOUT_MS": 600000,
  "NON_INTERACTIVE_MODE": false,
  "Providers": [
    {
      "name": "openrouter",
      "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
      "api_key": "sk-xxx",
      "models": [
        "google/gemini-2.5-pro-preview",
        "anthropic/claude-sonnet-4",
        "anthropic/claude-3.5-sonnet",
        "anthropic/claude-3.7-sonnet:thinking"
      ],
      "transformer": {
        "use": ["openrouter"]
      }
    },
    {
      "name": "deepseek",
      "api_base_url": "https://api.deepseek.com/chat/completions",
      "api_key": "sk-xxx",
      "models": ["deepseek-chat", "deepseek-reasoner"],
      "transformer": {
        "use": ["deepseek"],
        "deepseek-chat": {
          "use": ["tooluse"]
        }
      }
    },
    {
      "name": "ollama",
      "api_base_url": "http://localhost:11434/v1/chat/completions",
      "api_key": "ollama",
      "models": ["qwen2.5-coder:latest"]
    },
    {
      "name": "gemini",
      "api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
      "api_key": "sk-xxx",
      "models": ["gemini-2.5-flash", "gemini-2.5-pro"],
      "transformer": {
        "use": ["gemini"]
      }
    },
    {
      "name": "volcengine",
      "api_base_url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
      "api_key": "sk-xxx",
      "models": ["deepseek-v3-250324", "deepseek-r1-250528"],
      "transformer": {
        "use": ["deepseek"]
      }
    },
    {
      "name": "modelscope",
      "api_base_url": "https://api-inference.modelscope.cn/v1/chat/completions",
      "api_key": "",
      "models": ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507"],
      "transformer": {
        "use": [
          [
            "maxtoken",
            {
              "max_tokens": 65536
            }
          ],
          "enhancetool"
        ],
        "Qwen/Qwen3-235B-A22B-Thinking-2507": {
          "use": ["reasoning"]
        }
      }
    },
    {
      "name": "dashscope",
      "api_base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
      "api_key": "",
      "models": ["qwen3-coder-plus"],
      "transformer": {
        "use": [
          [
            "maxtoken",
            {
              "max_tokens": 65536
            }
          ],
          "enhancetool"
        ]
      }
    },
    {
      "name": "aihubmix",
      "api_base_url": "https://aihubmix.com/v1/chat/completions",
      "api_key": "sk-",
      "models": [
        "Z/glm-4.5",
        "claude-opus-4-20250514",
        "gemini-2.5-pro"
      ]
    }
  ],
  "Router": {
    "default": "deepseek,deepseek-chat",
    "background": "ollama,qwen2.5-coder:latest",
    "think": "deepseek,deepseek-reasoner",
    "longContext": "openrouter,google/gemini-2.5-pro-preview",
    "longContextThreshold": 60000,
    "webSearch": "gemini,gemini-2.5-flash"
  }
}
```


### 3. 使用 Router 运行 Claude Code

使用 router 启动 Claude Code:

```shell
ccr code
```

> **注意**: 修改配置文件后,需要重启服务使配置生效:
> ```shell
> ccr restart
> ```

### 4. UI 模式

为了获得更直观的体验,您可以使用 UI 模式来管理您的配置:

```shell
ccr ui
```

这将打开一个基于 Web 的界面,您可以在其中轻松查看和编辑您的 `config.json` 文件。

![UI](/blog/images/ui.png)

### 5. CLI 模型管理

对于偏好终端工作流的用户,可以使用交互式 CLI 模型选择器:

```shell
ccr model
```

该命令提供交互式界面来:

- 查看当前配置
- 查看所有配置的模型(default、background、think、longContext、webSearch、image)
- 切换模型:快速更改每个路由器类型使用的模型
- 添加新模型:向现有提供商添加模型
- 创建新提供商:设置完整的提供商配置,包括:
   - 提供商名称和 API 端点
   - API 密钥
   - 可用模型
   - Transformer 配置,支持:
     - 多个转换器(openrouter、deepseek、gemini 等)
     - Transformer 选项(例如,带自定义限制的 maxtoken)
     - 特定于提供商的路由(例如,OpenRouter 提供商偏好)

CLI 工具验证所有输入并提供有用的提示来引导您完成配置过程,使管理复杂的设置变得容易,无需手动编辑 JSON 文件。

### 6. 预设管理

预设允许您轻松保存、共享和重用配置。您可以将当前配置导出为预设,并从文件或 URL 安装预设。

```shell
# 将当前配置导出为预设
ccr preset export my-preset

# 使用元数据导出
ccr preset export my-preset --description "我的 OpenAI 配置" --author "您的名字" --tags "openai,生产环境"

# 从本地目录安装预设
ccr preset install /path/to/preset

# 列出所有已安装的预设
ccr preset list

# 显示预设信息
ccr preset info my-preset

# 删除预设
ccr preset delete my-preset
```

**预设功能:**
- **导出**:将当前配置保存为预设目录(包含 manifest.json)
- **安装**:从本地目录安装预设
- **敏感数据处理**:导出期间自动清理 API 密钥和其他敏感数据(标记为 `{{field}}` 占位符)
- **动态配置**:预设可以包含输入架构,用于在安装期间收集所需信息
- **版本控制**:每个预设包含版本元数据,用于跟踪更新

**预设文件结构:**
```
~/.claude-code-router/presets/
├── my-preset/
│   └── manifest.json    # 包含配置和元数据
```

### 7. Activate 命令(环境变量设置)

`activate` 命令允许您在 shell 中全局设置环境变量,使您能够直接使用 `claude` 命令或将 Claude Code Router 与使用 Agent SDK 构建的应用程序集成。

要激活环境变量,请运行:

```shell
eval "$(ccr activate)"
```

此命令会以 shell 友好的格式输出必要的环境变量,这些变量将在当前的 shell 会话中设置。激活后,您可以:

- **直接使用 `claude` 命令**:无需使用 `ccr code` 即可运行 `claude` 命令。`claude` 命令将自动通过 Claude Code Router 路由请求。
- **与 Agent SDK 应用程序集成**:使用 Anthropic Agent SDK 构建的应用程序将自动使用配置的路由器和模型。

`activate` 命令设置以下环境变量:

- `ANTHROPIC_AUTH_TOKEN`: 来自配置的 API 密钥
- `ANTHROPIC_BASE_URL`: 本地路由器端点(默认:`http://127.0.0.1:3456`)
- `NO_PROXY`: 设置为 `127.0.0.1` 以防止代理干扰
- `DISABLE_TELEMETRY`: 禁用遥测
- `DISABLE_COST_WARNINGS`: 禁用成本警告
- `API_TIMEOUT_MS`: 来自配置的 API 超时时间

> **注意**:在使用激活的环境变量之前,请确保 Claude Code Router 服务正在运行(`ccr start`)。环境变量仅在当前 shell 会话中有效。要使其持久化,您可以将 `eval "$(ccr activate)"` 添加到您的 shell 配置文件(例如 `~/.zshrc` 或 `~/.bashrc`)中。

#### Providers

`Providers` 数组是您定义要使用的不同模型提供商的地方。每个提供商对象都需要:

-   `name`: 提供商的唯一名称。
-   `api_base_url`: 聊天补全的完整 API 端点。
-   `api_key`: 您提供商的 API 密钥。
-   `models`: 此提供商可用的模型名称列表。
-   `transformer` (可选): 指定用于处理请求和响应的转换器。

#### Transformers

Transformers 允许您修改请求和响应负载,以确保与不同提供商 API 的兼容性。

-   **全局 Transformer**: 将转换器应用于提供商的所有模型。在此示例中,`openrouter` 转换器将应用于 `openrouter` 提供商下的所有模型。
    ```json
     {
       "name": "openrouter",
       "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
       "api_key": "sk-xxx",
       "models": [
         "google/gemini-2.5-pro-preview",
         "anthropic/claude-sonnet-4",
         "anthropic/claude-3.5-sonnet"
       ],
       "transformer": { "use": ["openrouter"] }
     }
    ```
-   **特定于模型的 Transformer**: 将转换器应用于特定模型。在此示例中,`deepseek` 转换器应用于所有模型,而额外的 `tooluse` 转换器仅应用于 `deepseek-chat` 模型。
    ```json
     {
       "name": "deepseek",
       "api_base_url": "https://api.deepseek.com/chat/completions",
       "api_key": "sk-xxx",
       "models": ["deepseek-chat", "deepseek-reasoner"],
       "transformer": {
         "use": ["deepseek"],
         "deepseek-chat": { "use": ["tooluse"] }
       }
     }
    ```

-   **向 Transformer 传递选项**: 某些转换器(如 `maxtoken`)接受选项。要传递选项,请使用嵌套数组,其中第一个元素是转换器名称,第二个元素是选项对象。
    ```json
    {
      "name": "siliconflow",
      "api_base_url": "https://api.siliconflow.cn/v1/chat/completions",
      "api_key": "sk-xxx",
      "models": ["moonshotai/Kimi-K2-Instruct"],
      "transformer": {
        "use": [
          [
            "maxtoken",
            {
              "max_tokens": 16384
            }
          ]
        ]
      }
    }
    ```

**可用的内置 Transformer:**

-   `Anthropic`: 如果你只使用这一个转换器,则会直接透传请求和响应(你可以用它来接入其他支持Anthropic端点的服务商)。
-   `deepseek`: 适配 DeepSeek API 的请求/响应。
-   `gemini`: 适配 Gemini API 的请求/响应。
-   `openrouter`: 适配 OpenRouter API 的请求/响应。它还可以接受一个 `provider` 路由参数,以指定 OpenRouter 应使用哪些底层提供商。有关更多详细信息,请参阅 [OpenRouter 文档](https://openrouter.ai/docs/features/provider-routing)。请参阅下面的示例:
    ```json
      "transformer": {
        "use": ["openrouter"],
        "moonshotai/kimi-k2": {
          "use": [
            [
              "openrouter",
              {
                "provider": {
                  "only": ["moonshotai/fp8"]
                }
              }
            ]
          ]
        }
      }
    ```
-   `groq`: 适配 groq API 的请求/响应
-   `maxtoken`: 设置特定的 `max_tokens` 值。
-   `tooluse`: 优化某些模型的工具使用(通过`tool_choice`参数)。
-   `gemini-cli` (实验性): 通过 Gemini CLI [gemini-cli.js](https://gist.github.com/musistudio/1c13a65f35916a7ab690649d3df8d1cd) 对 Gemini 的非官方支持。
-   `reasoning`: 用于处理 `reasoning_content` 字段。
-   `sampling`: 用于处理采样信息字段,如 `temperature`、`top_p`、`top_k` 和 `repetition_penalty`。
-   `enhancetool`: 对 LLM 返回的工具调用参数增加一层容错处理(这会导致不再流式返回工具调用信息)。
-   `cleancache`: 清除请求中的 `cache_control` 字段。
-   `vertex-gemini`: 处理使用 vertex 鉴权的 gemini api。
-   `qwen-cli` (实验性): 通过 Qwen CLI [qwen-cli.js](https://gist.github.com/musistudio/f5a67841ced39912fd99e42200d5ca8b) 对 qwen3-coder-plus 的非官方支持。
-   `rovo-cli` (experimental): 通过 Atlassian Rovo Dev CLI [rovo-cli.js](https://gist.github.com/SaseQ/c2a20a38b11276537ec5332d1f7a5e53) 对 GPT-5 的非官方支持。

**自定义 Transformer:**

您还可以创建自己的转换器,并通过 `config.json` 中的 `transformers` 字段加载它们。

```json
{
  "transformers": [
      {
        "path": "/User/xxx/.claude-code-router/plugins/gemini-cli.js",
        "options": {
          "project": "xxx"
        }
      }
  ]
}
```

#### Router

`Router` 对象定义了在不同场景下使用哪个模型:

-   `default`: 用于常规任务的默认模型。
-   `background`: 用于后台任务的模型。这可以是一个较小的本地模型以节省成本。
-   `think`: 用于推理密集型任务(如计划模式)的模型。
-   `longContext`: 用于处理长上下文(例如,> 60K 令牌)的模型。
-   `longContextThreshold` (可选): 触发长上下文模型的令牌数阈值。如果未指定,默认为 60000。
-   `webSearch`: 用于处理网络搜索任务,需要模型本身支持。如果使用`openrouter`需要在模型后面加上`:online`后缀。
-   `image`(测试版): 用于处理图片类任务(采用CCR内置的agent支持),如果该模型不支持工具调用,需要将`config.forceUseImageAgent`属性设置为`true`。

您还可以使用 `/model` 命令在 Claude Code 中动态切换模型:
`/model provider_name,model_name`
示例: `/model openrouter,anthropic/claude-3.5-sonnet`

#### 自定义路由器

对于更高级的路由逻辑,您可以在 `config.json` 中通过 `CUSTOM_ROUTER_PATH` 字段指定一个自定义路由器脚本。这允许您实现超出默认场景的复杂路由规则。

在您的 `config.json` 中配置:

```json
{
  "CUSTOM_ROUTER_PATH": "/User/xxx/.claude-code-router/custom-router.js"
}
```

自定义路由器文件必须是一个导出 `async` 函数的 JavaScript 模块。该函数接收请求对象和配置对象作为参数,并应返回提供商和模型名称的字符串(例如 `"provider_name,model_name"`),如果返回 `null` 则回退到默认路由。

这是一个基于 `custom-router.example.js` 的 `custom-router.js` 示例:

```javascript
// /User/xxx/.claude-code-router/custom-router.js

/**
 * 一个自定义路由函数,用于根据请求确定使用哪个模型。
 *
 * @param {object} req - 来自 Claude Code 的请求对象,包含请求体。
 * @param {object} config - 应用程序的配置对象。
 * @returns {Promise<string|null>} - 一个解析为 "provider,model_name" 字符串的 Promise,如果返回 null,则使用默认路由。
 */
module.exports = async function router(req, config) {
  const userMessage = req.body.messages.find(m => m.role === 'user')?.content;

  if (userMessage && userMessage.includes('解释这段代码')) {
    // 为代码解释任务使用更强大的模型
    return 'openrouter,anthropic/claude-3.5-sonnet';
  }

  // 回退到默认的路由配置
  return null;
};
```

##### 子代理路由

对于子代理内的路由,您必须在子代理提示词的**开头**包含 `<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>` 来指定特定的提供商和模型。这样可以将特定的子代理任务定向到指定的模型。

**示例:**

```
<CCR-SUBAGENT-MODEL>openrouter,anthropic/claude-3.5-sonnet</CCR-SUBAGENT-MODEL>
请帮我分析这段代码是否存在潜在的优化空间...
```

## Status Line (Beta)
为了在运行时更好的查看claude-code-router的状态,claude-code-router在v1.0.40内置了一个statusline工具,你可以在UI中启用它,
![statusline-config.png](/blog/images/statusline-config.png)

效果如下:
![statusline](/blog/images/statusline.png)

## 🤖 GitHub Actions

将 Claude Code Router 集成到您的 CI/CD 管道中。在设置 [Claude Code Actions](https://docs.anthropic.com/en/docs/claude-code/github-actions) 后,修改您的 `.github/workflows/claude.yaml` 以使用路由器:

```yaml
name: Claude Code

on:
  issue_comment:
    types: [created]
  # ... other triggers

jobs:
  claude:
    if: |
      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
      # ... other conditions
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
      issues: read
      id-token: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Prepare Environment
        run: |
          curl -fsSL https://bun.sh/install | bash
          mkdir -p $HOME/.claude-code-router
          cat << 'EOF' > $HOME/.claude-code-router/config.json
          {
            "log": true,
            "NON_INTERACTIVE_MODE": true,
            "OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
            "OPENAI_BASE_URL": "https://api.deepseek.com",
            "OPENAI_MODEL": "deepseek-chat"
          }
          EOF
        shell: bash

      - name: Start Claude Code Router
        run: |
          nohup ~/.bun/bin/bunx @musistudio/claude-code-router@1.0.8 start &
        shell: bash

      - name: Run Claude Code
        id: claude
        uses: anthropics/claude-code-action@beta
        env:
          ANTHROPIC_BASE_URL: http://localhost:3456
        with:
          anthropic_api_key: "any-string-is-ok"
```

这种设置可以实现有趣的自动化,例如在非高峰时段运行任务以降低 API 成本。

## 📝 深入阅读

-   [项目动机和工作原理](blog/zh/项目初衷及原理.md)
-   [也许我们可以用路由器做更多事情](blog/zh/或许我们能在Router中做更多事情.md)

## ❤️ 支持与赞助

如果您觉得这个项目有帮助,请考虑赞助它的开发。非常感谢您的支持!

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F31GN2GM)

[Paypal](https://paypal.me/musistudio1999)

<table>
  <tr>
    <td><img src="/blog/images/alipay.jpg" width="200" alt="Alipay" /></td>
    <td><img src="/blog/images/wechat.jpg" width="200" alt="WeChat Pay" /></td>
  </tr>
</table>

### 我们的赞助商

非常感谢所有赞助商的慷慨支持!

- [AIHubmix](https://aihubmix.com/)
- [BurnCloud](https://ai.burncloud.com)
- [302.AI](https://share.302.ai/ZGVF9w)
- [Z智谱](https://www.bigmodel.cn/claude-code?ic=FPF9IVAGFJ)
- @Simon Leischnig
- [@duanshuaimin](https://github.com/duanshuaimin)
- [@vrgitadmin](https://github.com/vrgitadmin)
- @*o
- [@ceilwoo](https://github.com/ceilwoo)
- @*说
- @*更
- @K*g
- @R*R
- [@bobleer](https://github.com/bobleer)
- @*苗
- @*划
- [@Clarence-pan](https://github.com/Clarence-pan)
- [@carter003](https://github.com/carter003)
- @S*r
- @*晖
- @*敏
- @Z*z
- @*然
- [@cluic](https://github.com/cluic)
- @*苗
- [@PromptExpert](https://github.com/PromptExpert)
- @*应
- [@yusnake](https://github.com/yusnake)
- @*飞
- @董*
- @*汀
- @*涯
- @*:-)
- @**磊
- @*琢
- @*成
- @Z*o
- @\*琨
- [@congzhangzh](https://github.com/congzhangzh)
- @*_
- @Z\*m
- @*鑫
- @c\*y
- @\*昕
- [@witsice](https://github.com/witsice)
- @b\*g
- @\*亿
- @\*辉
- @JACK 
- @\*光
- @W\*l
- [@kesku](https://github.com/kesku)
- [@biguncle](https://github.com/biguncle)
- @二吉吉
- @a\*g
- @\*林
- @\*咸
- @\*明
- @S\*y
- @f\*o
- @\*智
- @F\*t
- @r\*c
- [@qierkang](http://github.com/qierkang)
- @\*军
- [@snrise-z](http://github.com/snrise-z)
- @\*王
- [@greatheart1000](http://github.com/greatheart1000)
- @\*王
- @zcutlip
- [@Peng-YM](http://github.com/Peng-YM)
- @\*更
- @\*.
- @F\*t
- @\*政
- @\*铭
- @\*叶
- @七\*o
- @\*青
- @\*\*晨
- @\*远
- @\*霄
- @\*\*吉
- @\*\*飞
- @\*\*驰
- @x\*g
- @\*\*东
- @\*落
- @哆\*k
- @\*涛
- [@苗大](https://github.com/WitMiao)
- @\*呢
- @\d*u
- @crizcraig
- s\*s
- \*火
- \*勤
- \*\*锟
- \*涛
- \*\*明
- \*知
- \*语
- \*瓜

(如果您的名字被屏蔽,请通过我的主页电子邮件与我联系,以便使用您的 GitHub 用户名进行更新。)


## 交流群
<img src="/blog/images/wechat_group.jpg" width="200" alt="wechat_group" />


## /blog/en/glm-4.6-supports-reasoning.md

# GLM-4.6 Supports Reasoning and Interleaved Thinking

## Enabling Reasoning in Claude Code with GLM-4.6

Starting from version 4.5, GLM has supported Claude Code. I’ve been following its progress closely, and many users have reported that reasoning could not be enabled within Claude Code. Recently, thanks to sponsorship from Zhipu, I decided to investigate this issue in depth. According to the [official documentation](https://docs.z.ai/api-reference/llm/chat-completion), the`/chat/completions` endpoint has reasoning enabled by default, but the model itself decides whether to think:

```
thinking.type enum<string> default:enabled

Whether to enable the chain of thought(When enabled, GLM-4.6, GLM-4.5 and others will automatically determine whether to think, while GLM-4.5V will think compulsorily), default: enabled

Available options: enabled, disabled 
```

However, within Claude Code, its heavy system prompt interference disrupts GLM’s internal reasoning judgment, causing the model to rarely think.
Therefore, we need to explicitly guide the model to believe reasoning is required. Since claude-code-router functions as a proxy, the only feasible approach is modifying prompts or parameters.

Initially, I tried completely removing Claude Code’s system prompt — and indeed, the model started reasoning — but that broke Claude Code’s workflow.
So instead, I used prompt injection to clearly instruct the model to think step by step.


```javascript
// transformer.ts
import { UnifiedChatRequest } from "../types/llm";
import { Transformer } from "../types/transformer";

export class ForceReasoningTransformer implements Transformer {
  name = "forcereasoning";

  async transformRequestIn(
    request: UnifiedChatRequest
  ): Promise<UnifiedChatRequest> {
    const systemMessage = request.messages.find(
      (item) => item.role === "system"
    );
    if (Array.isArray(systemMessage?.content)) {
      systemMessage.content.push({
        type: "text",
        text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
      });
    }
    const lastMessage = request.messages[request.messages.length - 1];
    if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
      lastMessage.content.push({
        type: "text",
        text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
      });
    }
    if (lastMessage.role === "tool") {
      request.messages.push({
        role: "user",
        content: [
          {
            type: "text",
            text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
          },
        ],
      });
    }
    return request;
  }
}
```

Why use <reasoning_content> instead of the <think> tag? Two reasons:

1. Using the <think> tag doesn’t effectively trigger reasoning — likely because the model was trained on data where <think> had special behavior.

2. If we use <think>, the reasoning output is split into a separate field, which directly relates to the chain-of-thought feedback problem discussed below.

## Chain-of-Thought Feedback
Recently, Minimax released `Minimax-m2`, along with [an article](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2) explaining interleaved thinking.
While the idea isn’t entirely new, it’s a good opportunity to analyze it.

Why do we need to interleaved thinking?
Minimax’s article mentions that the Chat Completion API does not support passing reasoning content between requests.
We know ChatGPT was the first to support reasoning, but OpenAI initially didn’t expose the chain of thought to users.
Therefore, the Chat Completion API didn’t need to support it. Even the CoT field was first introduced by DeepSeek.

Do we really need explicit CoT fields? What happens if we don’t have them? Will it affect reasoning?
By inspecting [sglang’s source code](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py), we can see that reasoning content is naturally emitted in messages with specific markers.
If we don’t split it out, the next-round conversation will naturally include it.
Thus, the only reason we need interleaved thinking is because we separated the reasoning content from the normal messages.

With fewer than 40 lines of code above, I implemented a simple exploration of enabling reasoning and chain-of-thought feedback for GLM-4.5/4.6.
(It’s only simple because I haven’t implemented parsing logic yet — you could easily modify the transformer to split reasoning output on response and merge it back on request, improving Claude Code’s frontend display compatibility.)

If you have better ideas, feel free to reach out — I’d love to discuss further.


## /blog/en/maybe-we-can-do-more-with-the-route.md

# Maybe We Can Do More with the Router

Since the release of `claude-code-router`, I’ve received a lot of user feedback, and quite a few issues are still open. Most of them are related to support for different providers and the lack of tool usage from the deepseek model.

Originally, I created this project for personal use, mainly to access claude code at a lower cost. So, multi-provider support wasn’t part of the initial design. But during troubleshooting, I discovered that even though most providers claim to be compatible with the OpenAI-style `/chat/completions` interface, there are many subtle differences. For example:

1. When Gemini's tool parameter type is string, the `format` field only supports `date` and `date-time`, and there’s no tool call ID.

2. OpenRouter requires `cache_control` for caching.

3. The official DeepSeek API has a `max_output` of 8192, but Volcano Engine’s limit is even higher.

Aside from these, smaller providers often have quirks in their parameter handling. So I decided to create a new project, [musistudio/llms](https://github.com/musistudio/llms), to deal with these compatibility issues. It uses the OpenAI format as a base and introduces a generic Transformer interface for transforming both requests and responses.

Once a `Transformer` is implemented for each provider, it becomes possible to mix-and-match requests between them. For example, I implemented bidirectional conversion between Anthropic and OpenAI formats in `AnthropicTransformer`, which listens to the `/v1/messages` endpoint. Similarly, `GeminiTransformer` handles Gemini <-> OpenAI format conversions and listens to `/v1beta/models/:modelAndAction`.

When both requests and responses are transformed into a common format, they can interoperate seamlessly:

```
AnthropicRequest -> AnthropicTransformer -> OpenAIRequest -> GeminiTransformer -> GeminiRequest -> GeminiServer
```

```
GeminiResponse -> GeminiTransformer -> OpenAIResponse -> AnthropicTransformer -> AnthropicResponse
```

Using a middleware layer to smooth out differences may introduce some performance overhead, but the main goal here is to enable `claude-code-router` to support multiple providers.

As for the issue of DeepSeek’s lackluster tool usage — I found that it stems from poor instruction adherence in long conversations. Initially, the model actively calls tools, but after several rounds, it starts responding with plain text instead. My first workaround was injecting a system prompt to remind the model to use tools proactively. But in long contexts, the model tends to forget this instruction.

After reading the DeepSeek documentation, I noticed it supports the `tool_choice` parameter, which can be set to `"required"` to force the model to use at least one tool. I tested this by enabling the parameter, and it significantly improved the model’s tool usage. We can remove the setting when it's no longer necessary. With the help of the `Transformer` interface in [musistudio/llms](https://github.com/musistudio/llms), we can modify the request before it’s sent and adjust the response after it’s received.

Inspired by the Plan Mode in `claude code`, I implemented a similar Tool Mode for DeepSeek:

```typescript
export class TooluseTransformer implements Transformer {
  name = "tooluse";

  transformRequestIn(request: UnifiedChatRequest): UnifiedChatRequest {
    if (request.tools?.length) {
      request.messages.push({
        role: "system",
        content: `<system-reminder>Tool mode is active. The user expects you to proactively execute the most suitable tool to help complete the task. 
Before invoking a tool, you must carefully evaluate whether it matches the current task. If no available tool is appropriate for the task, you MUST call the \`ExitTool\` to exit tool mode — this is the only valid way to terminate tool mode.
Always prioritize completing the user's task effectively and efficiently by using tools whenever appropriate.</system-reminder>`,
      });
      request.tool_choice = "required";
      request.tools.unshift({
        type: "function",
        function: {
          name: "ExitTool",
          description: `Use this tool when you are in tool mode and have completed the task. This is the only valid way to exit tool mode.
IMPORTANT: Before using this tool, ensure that none of the available tools are applicable to the current task. You must evaluate all available options — only if no suitable tool can help you complete the task should you use ExitTool to terminate tool mode.
Examples:
1. Task: "Use a tool to summarize this document" — Do not use ExitTool if a summarization tool is available.
2. Task: "What’s the weather today?" — If no tool is available to answer, use ExitTool after reasoning that none can fulfill the task.`,
          parameters: {
            type: "object",
            properties: {
              response: {
                type: "string",
                description:
                  "Your response will be forwarded to the user exactly as returned — the tool will not modify or post-process it in any way.",
              },
            },
            required: ["response"],
          },
        },
      });
    }
    return request;
  }

  async transformResponseOut(response: Response): Promise<Response> {
    if (response.headers.get("Content-Type")?.includes("application/json")) {
      const jsonResponse = await response.json();
      if (
        jsonResponse?.choices[0]?.message.tool_calls?.length &&
        jsonResponse?.choices[0]?.message.tool_calls[0]?.function?.name ===
          "ExitTool"
      ) {
        const toolArguments = JSON.parse(toolCall.function.arguments || "{}");
        jsonResponse.choices[0].message.content = toolArguments.response || "";
        delete jsonResponse.choices[0].message.tool_calls;
      }

      // Handle non-streaming response if needed
      return new Response(JSON.stringify(jsonResponse), {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });
    } else if (response.headers.get("Content-Type")?.includes("stream")) {
      // ...
    }
    return response;
  }
}
```

This transformer ensures the model calls at least one tool. If no tools are appropriate or the task is finished, it can exit using `ExitTool`. Since this relies on the `tool_choice` parameter, it only works with models that support it.

In practice, this approach noticeably improves tool usage for DeepSeek. The tradeoff is that sometimes the model may invoke irrelevant or unnecessary tools, which could increase latency and token usage.

This update is just a small experiment — adding an `“agent”` to the router. Maybe there are more interesting things we can explore from here.

## /blog/en/progressive-disclosure-of-agent-tools-from-the-perspective-of-cli-tool-style.md

# Progressive Disclosure of Agent Tools from the Perspective of CLI Tool Style

It has been nearly two months since Anthropic released [Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills). In this release, Anthropic mentioned a term: Progressive Disclosure. What exactly is this? What problem does it solve?

Actually, in my Vibe Coding workflow, I rarely use MCP. The reason is that I find the implementation quality of MCP to be inconsistent. At its core, it’s about context injection (the essence of tools is also context injection), and I’m not sure if prompts written by others might affect my workflow, so I simply avoid using it altogether. The current implementation of MCP essentially wraps all functionalities as tools exposed to the Agent (one functionality wrapped as one tool, given a detailed description, telling the agent when to call it and what the parameter format is). This has led to the current explosion of prompts.

It wasn’t until Anthropic released Skills and I looked into it that I realized its essence is still prompt injection. If MCP provides a specification for injecting tools, then what Skills advocates is somewhat "unconventional." Skills provides a Markdown document to describe the purpose and best practices of the skill, along with some attached scripts (different from MCP).
![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F6f22d8913dbc6228e7f11a41e0b3c124d817b6d2-1650x929.jpg&w=3840&q=75)

Since these scripts run directly on the user’s local machine, there are significant security risks. If users cannot review the script code, it can easily lead to serious security issues such as data leakage or virus infections. Compared to MCP, which provides a standardized interface, Skills offer a series of script files. Different skills may have different types of script files—for example, some scripts are implemented in Node.js, while others use Python. To use these scripts, users also need to install the corresponding runtime and dependencies. This is why I describe it as "unconventional."


Is this really the best practice?

Regarding Progressive Disclosure, here is how Anthropic describes it:
> Progressive disclosure is the core design principle that makes Agent Skills flexible and scalable. Like a well-organized manual that starts with a table of contents, then specific chapters, and finally a detailed appendix, skills let Claude load information only as needed:
> ![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2Fa3bca2763d7892982a59c28aa4df7993aaae55ae-2292x673.jpg&w=3840&q=75)
> Agents with a filesystem and code execution tools don’t need to read the entirety of a skill into their context window when working on a particular task. This means that the amount of context that can be bundled into a skill is effectively unbounded.

The following diagram shows how the context window changes when a skill is triggered by a user’s message.
![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F441b9f6cc0d2337913c1f41b05357f16f51f702e-1650x929.jpg&w=3840&q=75)

Do we really need to implement it this way?

In our daily use of CLI tools, most CLI tools come with a `--help` parameter for viewing the tool's usage and instructions. Isn't that essentially the tool's user manual? For example:
```shell
> npm --help
npm <command>

Usage:

npm install        install all the dependencies in your project
npm install <foo>  add the <foo> dependency to your project
npm test           run this project's tests
npm run <foo>      run the script named <foo>
npm <command> -h   quick help on <command>
npm -l             display usage info for all commands
npm help <term>    search for help on <term>
npm help npm       more involved overview

All commands:

    access, adduser, audit, bugs, cache, ci, completion,
    config, dedupe, deprecate, diff, dist-tag, docs, doctor,
    edit, exec, explain, explore, find-dupes, fund, get, help,
    help-search, hook, init, install, install-ci-test,
    install-test, link, ll, login, logout, ls, org, outdated,
    owner, pack, ping, pkg, prefix, profile, prune, publish,
    query, rebuild, repo, restart, root, run-script, sbom,
    search, set, shrinkwrap, star, stars, start, stop, team,
    test, token, uninstall, unpublish, unstar, update, version,
    view, whoami

Specify configs in the ini-formatted file:
    /Users/xxx/.npmrc
or on the command line via: npm <command> --key=value

More configuration info: npm help config
Configuration fields: npm help 7 config
```

This manual doesn't return every possible usage of every command either. It only lists which commands are available and what functions they can perform. For the specific usage of a command, you can still obtain it by using the `--help` parameter:
```shell
> npm install --help
Install a package

Usage:
npm install [<package-spec> ...]

Options:
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer|--save-bundle]
[-E|--save-exact] [-g|--global]
[--install-strategy <hoisted|nested|shallow|linked>] [--legacy-bundling]
[--global-style] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]]
[--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only]
[--foreground-scripts] [--ignore-scripts] [--no-audit] [--no-bin-links]
[--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>] [--libc <libc>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root] [--install-links]

aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall

Run "npm help install" for more info
```
Doesn't this resemble the definition of progressive disclosure mentioned above?

Can we implement an MCP in this style to achieve progressive disclosure of tools without needing skills? I used Codex to convert the official PDF Skill into an MCP, exposing only a single tool:
```json
{
    "name": "mcp__pdf__pdf",
    "description": "Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.You need to pass in the --help parameter to obtain the usage of this tool first.",
    "input_schema": {
        "type": "object",
        "properties": {
            "params": {
                "$ref": "#/$defs/PdfCommandInput"
            }
        },
        "required": [
            "params"
        ],
        "$defs": {
            "PdfCommandInput": {
                "additionalProperties": false,
                "properties": {
                    "argv": {
                        "description": "Command tokens excluding the leading 'pdf' executable name",
                        "items": {
                            "type": "string"
                        },
                        "title": "Argv",
                        "type": "array"
                    },
                    "command": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "default": null,
                        "description": "Optional command string; if set, it will be split like a shell",
                        "title": "Command"
                    }
                },
                "title": "PdfCommandInput",
                "type": "object"
            }
        },
        "title": "pdfArguments"
    }
}
```

Below is the change in the context window when using this MCP:
```json
User: Extract all the text from test.pdf and write it into result.txt
Agent: Tool(name="pdf", args=["--help"])
Tool: pdf - Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale在使用之前你需要先传入--help参数以获取该工具的用法\n\nUsage:\n  pdf [--help] <domain> <command> [options]\n\nDomains:\n  forms      Fill, extract, and validate PDF form fields.\n  documents  Create, merge, and split PDFs.\n  text       Extract text content from PDFs.\n  tables     Extract tables (heuristic, whitespace-based).\n  images     Render PDF pages to PNG images.\n\nRun `pdf <domain> --help` for domain help.\nRun `pdf <domain> <command> --help` for command help.
Agent: Tool(name="pdf", args=["text", "--help"])
Tool: text - Extract text content from PDFs.\n\nUsage:\n  pdf text <command> [options]\n\nCommands:\n  extract  Extract text content from a PDF.\n\nRun `pdf text <command> --help` for details.
Agent: Tool(name="pdf", args=["text", "extract", "--help"])
Tool: usage: pdf text extract --pdf PDF_PATH [--pages PAGES] [--out OUTPUT_PATH]\n                        [--format {text,json}] [--overwrite]\n\nExtract text content from a PDF.\n\noptions:\n  --pdf PDF_PATH        Input PDF\n  --pages PAGES         Page selection like 1,3-5 or 'all' (default: all)\n  --out OUTPUT_PATH     Write extracted text to a file\n  --format {text,json}  Output format (default: text)\n  --overwrite           Overwrite output file if it exists\n
Agent: Tool(name="pdf", args=["text", "extract", "--pdf", "test.pdf", "--out", "result.txt", "--overwrite"])
Tool: {"result": {"stdout": "xx","data": {"pdf_path": "test.pdf","page_count": 6,"pages": [xxx],"format": "text","output_path": "result.txt"}}}
Agent: xxx
```

From the perspective of context changes, progressive disclosure has been fully realized. This MCP code is open-source (entirely written by Codex, serving only to validate the idea without any review): https://github.com/musistudio/pdf-skill-mcp 

If you have any thoughts or ideas, I’d also welcome the opportunity to discuss them with you. email: [m@musiiot.top](mailto://m@musiiot.top )


## /blog/en/project-motivation-and-how-it-works.md

# Project Motivation and Principles

As early as the day after Claude Code was released (2025-02-25), I began and completed a reverse engineering attempt of the project. At that time, using Claude Code required registering for an Anthropic account, applying for a waitlist, and waiting for approval. However, due to well-known reasons, Anthropic blocks users from mainland China, making it impossible for me to use the service through normal means. Based on known information, I discovered the following:

1. Claude Code is installed via npm, so it's very likely developed with Node.js.
2. Node.js offers various debugging methods: simple `console.log` usage, launching with `--inspect` to hook into Chrome DevTools, or even debugging obfuscated code using `d8`.

My goal was to use Claude Code without an Anthropic account. I didn’t need the full source code—just a way to intercept and reroute requests made by Claude Code to Anthropic’s models to my own custom endpoint. So I started the reverse engineering process:

1. First, install Claude Code:
```bash
npm install -g @anthropic-ai/claude-code
```

2. After installation, the project is located at `~/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code`(this may vary depending on your Node version manager and version).

3. Open the package.json to analyze the entry point:
```package.json
{
  "name": "@anthropic-ai/claude-code",
  "version": "1.0.24",
  "main": "sdk.mjs",
  "types": "sdk.d.ts",
  "bin": {
    "claude": "cli.js"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "type": "module",
  "author": "Boris Cherny <boris@anthropic.com>",
  "license": "SEE LICENSE IN README.md",
  "description": "Use Claude, Anthropic's AI assistant, right from your terminal. Claude can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.",
  "homepage": "https://github.com/anthropics/claude-code",
  "bugs": {
    "url": "https://github.com/anthropics/claude-code/issues"
  },
  "scripts": {
    "prepare": "node -e \"if (!process.env.AUTHORIZED) { console.error('ERROR: Direct publishing is not allowed.\\nPlease use the publish-external.sh script to publish this package.'); process.exit(1); }\"",
    "preinstall": "node scripts/preinstall.js"
  },
  "dependencies": {},
  "optionalDependencies": {
    "@img/sharp-darwin-arm64": "^0.33.5",
    "@img/sharp-darwin-x64": "^0.33.5",
    "@img/sharp-linux-arm": "^0.33.5",
    "@img/sharp-linux-arm64": "^0.33.5",
    "@img/sharp-linux-x64": "^0.33.5",
    "@img/sharp-win32-x64": "^0.33.5"
  }
}
```

The key entry is `"claude": "cli.js"`. Opening cli.js, you'll see the code is minified and obfuscated. But using WebStorm’s `Format File` feature, you can reformat it for better readability:
![webstorm-formate-file](../images/webstorm-formate-file.png)

Now you can begin understanding Claude Code’s internal logic and prompt structure by reading the code. To dig deeper, you can insert console.log statements or launch in debug mode with Chrome DevTools using:

```bash
NODE_OPTIONS="--inspect-brk=9229" claude
```

This command starts Claude Code in debug mode and opens port 9229. Visit chrome://inspect/ in Chrome and click inspect to begin debugging:
![chrome-devtools](../images/chrome-inspect.png)
![chrome-devtools](../images/chrome-devtools.png)

By searching for the keyword api.anthropic.com, you can easily locate where Claude Code makes its API calls. From the surrounding code, it's clear that baseURL can be overridden with the `ANTHROPIC_BASE_URL` environment variable, and `apiKey` and `authToken` can be configured similarly:
![search](../images/search.png)

So far, we’ve discovered some key information:

1. Environment variables can override Claude Code's `baseURL` and `apiKey`.

2. Claude Code adheres to the Anthropic API specification.

Therefore, we need:
1. A service to convert OpenAI API–compatible requests into Anthropic API format.

2. Set the environment variables before launching Claude Code to redirect requests to this service.

Thus, `claude-code-router` was born. This project uses `Express.js` to implement the `/v1/messages` endpoint. It leverages middlewares to transform request/response formats and supports request rewriting (useful for prompt tuning per model).

Back in February, the full DeepSeek model series had poor support for Function Calling, so I initially used `qwen-max`. It worked well—but without KV cache support, it consumed a large number of tokens and couldn’t provide the native `Claude Code` experience.

So I experimented with a Router-based mode using a lightweight model to dispatch tasks. The architecture included four roles: `router`, `tool`, `think`, and `coder`. Each request passed through a free lightweight model that would decide whether the task involved reasoning, coding, or tool usage. Reasoning and coding tasks looped until a tool was invoked to apply changes. However, the lightweight model lacked the capability to route tasks accurately, and architectural issues prevented it from effectively driving Claude Code.

Everything changed at the end of May when the official Claude Code was launched, and `DeepSeek-R1` model (released 2025-05-28) added Function Call support. I redesigned the system. With the help of AI pair programming, I fixed earlier request/response transformation issues—especially the handling of models that return JSON instead of Function Call outputs.

This time, I used the `DeepSeek-V3`  model. It performed better than expected: supporting most tool calls, handling task decomposition and stepwise planning, and—most importantly—costing less than one-tenth the price of Claude 3.5 Sonnet.

The official Claude Code organizes agents differently from the beta version, so I restructured my Router mode to include four roles: the default model, `background`, `think`, and `longContext`.

- The default model handles general tasks and acts as a fallback.

- The `background` model manages lightweight background tasks. According to Anthropic, Claude Haiku 3.5 is often used here, so I routed this to a local `ollama` service.

- The `think` model is responsible for reasoning and planning mode tasks. I use `DeepSeek-R1` here, though it doesn’t support cost control, so `Think` and `UltraThink` behave identically.

- The `longContext` model handles long-context scenarios. The router uses `tiktoken` to calculate token lengths in real time, and if the context exceeds 32K, it switches to this model to compensate for DeepSeek's long-context limitations.

This describes the evolution and reasoning behind the project. By cleverly overriding environment variables, we can forward and modify requests without altering Claude Code’s source—allowing us to benefit from official updates while using our own models and custom prompts.

This project offers a practical approach to running Claude Code under Anthropic’s regional restrictions, balancing `cost`, `performance`, and `customizability`. That said, the official `Max Plan` still offers the best experience if available.

## /blog/images/alipay.jpg

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/alipay.jpg

## /blog/images/chrome-devtools.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/chrome-devtools.png

## /blog/images/chrome-inspect.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/chrome-inspect.png

## /blog/images/claude-code-router-img.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/claude-code-router-img.png

## /blog/images/claude-code.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/claude-code.png

## /blog/images/models.gif

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/models.gif

## /blog/images/roadmap.svg

```svg path="/blog/images/roadmap.svg" 
<svg viewBox="0 0 1200 420" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style>
      .road { stroke: #7aa2ff; stroke-width: 6; fill: none; filter: drop-shadow(0 6px 18px rgba(122,162,255,0.25)); }
      .dash { stroke: rgba(122,162,255,0.25); stroke-width: 6; fill: none; stroke-dasharray: 2 18; }
      .node { filter: drop-shadow(0 3px 10px rgba(126,240,193,0.35)); }
      .node-circle { fill: #7ef0c1; }
      .node-core { fill: #181b22; stroke: white; stroke-width: 1.5; }
      .label-bg { fill: rgba(24,27,34,0.8); stroke: rgba(255,255,255,0.12); rx: 12; }
      .label-text { fill: #e8ecf1; font-weight: 700; font-size: 14px; font-family: Arial, sans-serif; }
      .label-sub { fill: #9aa6b2; font-weight: 500; font-size: 12px; font-family: Arial, sans-serif; }
      .spark { fill: none; stroke: #ffd36e; stroke-width: 1.6; stroke-linecap: round; }
    </style>
  </defs>

  <!-- Background road with dash -->
  <path class="dash" d="M60,330 C320,260 460,100 720,160 C930,205 990,260 1140,260"/>

  <!-- Main road -->
  <path class="road" d="M60,330 C320,260 460,100 720,160 C930,205 990,260 1140,260"/>

  <!-- New Documentation Node -->
  <g class="node" transform="translate(200,280)">
    <circle class="node-circle" r="10"/>
    <circle class="node-core" r="6"/>
  </g>

  <!-- New Documentation Label -->
  <g transform="translate(80,120)">
    <rect class="label-bg" width="260" height="92"/>
    <text class="label-text" x="16" y="34">New Documentation</text>
    <text class="label-sub" x="16" y="58">Clear structure, examples &amp; best practices</text>
  </g>

  <!-- Plugin Marketplace Node -->
  <g class="node" transform="translate(640,150)">
    <circle class="node-circle" r="10"/>
    <circle class="node-core" r="6"/>
  </g>

  <!-- Plugin Marketplace Label -->
  <g transform="translate(560,20)">
    <rect class="label-bg" width="320" height="100"/>
    <text class="label-text" x="16" y="34">Plugin Marketplace</text>
    <text class="label-sub" x="16" y="58">Community submissions, ratings &amp; version constraints</text>
  </g>

  <!-- One More Thing Node -->
  <g class="node" transform="translate(1080,255)">
    <circle class="node-circle" r="10"/>
    <circle class="node-core" r="6"/>
  </g>

  <!-- One More Thing Label -->
  <g transform="translate(940,300)">
    <rect class="label-bg" width="250" height="86"/>
    <text class="label-text" x="16" y="34">One More Thing</text>
    <text class="label-sub" x="16" y="58">🚀 Confidential project · Revealing soon</text>
  </g>

  <!-- Spark decorations -->
  <g transform="translate(1125,290)">
    <path class="spark" d="M0 0 L8 0 M4 -4 L4 4"/>
    <path class="spark" d="M14 -2 L22 -2 M18 -6 L18 2"/>
    <path class="spark" d="M-10 6 L-2 6 M-6 2 L-6 10"/>
  </g>
</svg>

```

## /blog/images/search.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/search.png

## /blog/images/sponsors/glm-en.jpg

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/sponsors/glm-en.jpg

## /blog/images/sponsors/glm-zh.jpg

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/sponsors/glm-zh.jpg

## /blog/images/statusline-config.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/statusline-config.png

## /blog/images/statusline.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/statusline.png

## /blog/images/ui.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/ui.png

## /blog/images/webstorm-formate-file.png

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/webstorm-formate-file.png

## /blog/images/wechat.jpg

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/wechat.jpg

## /blog/images/wechat_group.jpg

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/blog/images/wechat_group.jpg

## /blog/zh/GLM-4.6支持思考及思维链回传.md

# GLM-4.6支持思考及思维链回传

## GLM-4.6在cluade code中启用思考
GLM从4.5开始就对claude code进行了支持,我之前也一直在关注,很多用户反映在claude code中无法启用思考,刚好最近收到了来自智谱的赞助,就着手进行研究。

首先根据[官方文档](https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8),我们发现`/chat/completions`端点是默认启用思考的,但是是由模型判断是否需要进行思考

```
thinking object
仅 GLM-4.5 及以上模型支持此参数配置. 控制大模型是否开启思维链。

thinking.type enum<string> default:enabled
是否开启思维链(当开启后 GLM-4.6 GLM-4.5 为模型自动判断是否思考,GLM-4.5V 为强制思考), 默认: enabled.

Available options: enabled, disabled 
```

在claude code本身大量的提示词干扰下,会严重阻碍GLM模型本身的判断机制,导致模型很少进行思考。所以我们需要对模型进行引导,让模型认为需要进行思考。但是`claude-code-router`作为proxy,能做的只能是修改提示词/参数。

在最开始,我尝试直接删除claude code的系统提示词,模型确实进行了思考,但是这样就无法驱动claude code。所以我们需要进行提示词注入,明确告知模型需要进行思考。

```javascript
// transformer.ts
import { UnifiedChatRequest } from "../types/llm";
import { Transformer } from "../types/transformer";

export class ForceReasoningTransformer implements Transformer {
  name = "forcereasoning";

  async transformRequestIn(
    request: UnifiedChatRequest
  ): Promise<UnifiedChatRequest> {
    const systemMessage = request.messages.find(
      (item) => item.role === "system"
    );
    if (Array.isArray(systemMessage?.content)) {
      systemMessage.content.push({
        type: "text",
        text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
      });
    }
    const lastMessage = request.messages[request.messages.length - 1];
    if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
      lastMessage.content.push({
        type: "text",
        text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
      });
    }
    if (lastMessage.role === "tool") {
      request.messages.push({
        role: "user",
        content: [
          {
            type: "text",
            text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
          },
        ],
      });
    }
    return request;
  }
}
```

至于为什么让模型将思考内容放入reasoning_content标签而不是think标签有两个原因:
1. 直接使用think标签不能很好的激活思考,猜测是训练模型时以think标签作为数据集进行训练。
2. 如果使用think标签,模型的推理内容会被拆分到单独的字段,这就涉及到我们接下来要说的思维链回传问题。


## 思维链回传

近期Minimax发布了Minimax-m2,与此同时,他们还发布了一篇[文章](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2)介绍思维链回传。但是太阳底下无新鲜事,刚好借此来剖析一下。       
1. 我们首先来看一下为什么需要回传思维链?     
Minimax在文章中说的是Chat Completion API不支持在后续请求中传递推理内容。我们知道ChatGPT是最先支持推理的,但是OpenAI最初没有开放思维链给用户,所以对于Chat Completion API来讲并不需要支持思维链相关的东西。就连CoT的字段也是DeepSeek率先在Chat Completion API中加入的。

2. 我们真的需要这些字段吗?
如果没有这些字段会怎么样?会影响到模型的思考吗?可以查看一下[sglang的源码](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py)发现思维链的信息原本就会在消息中按照特定的标记进行输出,假如我们不对其进行拆分,正常情况下在下轮对话中会自然包含这些信息。所以需要思维链回传的原因就是我们对模型的思维链内容进行拆分。

我用上面不到40行的代码完成了对GLM-4.5/6支持思考以及思维链回传的简单探索(单纯是因为没时间做拆分,完全可以在transformer中响应时先做拆分,请求时再进行合并,这样对cc前端的展示适配会更好),如果你有什么更好的想法也欢迎与我联系。






## /blog/zh/从CLI工具风格看工具渐进式披露.md

# 从CLI工具风格看Agent工具渐进式披露

距离Anthropic发布[Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)也过去将近两个月的时间了,其中Anthropic提到了一个术语渐进式披露(Progressive Disclosure),这到底是什么东西?解决了什么问题?

其实在我的Vibe Coding流程中,我很少使用MCP。因为我觉得MCP实现质量层次不齐,本质是上下文注入(工具的本质也是上下文注入),我不确定别人写的提示词会不会影响到我的工作流,干脆直接不用。现在的MCP实现基本上就是把所有的功能全都包装成工具暴露给Agent(一个功能包装成一个工具,给定详细的描述,告诉agent在什么时候进行调用,参数格式是什么),这就导致了现在的提示词爆炸。    

直到Anthropic发布了Skills,研究了一下发现本质仍然是提示词注入。如果说MCP是提供了一套注入工具的规范,那么Skills所提倡的则是“离经叛道”。Skills给了一个Markdown文档用于描述该skill的用途和最佳用法,附带提供了一些脚本(与MCP不同)。
![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F6f22d8913dbc6228e7f11a41e0b3c124d817b6d2-1650x929.jpg&w=3840&q=75)
由于这些脚本直接在用户本地运行,存在极大的安全风险。如果用户不能对脚本代码进行review,很容易造成数据泄露、感染病毒等严重安全性问题。相比于MCP提供一个标准化的接口,Skill提供一系列的脚本文件,不同的skill可能拥有不同类型的脚本文件,比如有些脚本使用node.js实现,有些脚本使用Python实现,要使用这些脚本还需要用户安装对应的运行时和脚本所需要的依赖。这也是我说“离经叛道”的原因所在。

这真的是最佳实践吗?

关于渐进式披露,Anthropic是这样描述的:
> 渐进式披露是使代理技能灵活且可扩展的核心设计原则。就像一本组织良好的手册,从目录开始,然后是具体章节,最后是详细的附录一样,技能允许 Claude 仅在需要时加载信息:
> ![image](https://www.ant# 从CLI工具风格看Agent工具渐进式披露

距离Anthropic发布[Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)也过去将近两个月的时间了,其中Anthropic提到了一个术语渐进式披露(Progressive Disclosure),这到底是什么东西?解决了什么问题?

其实在我的Vibe Coding流程中,我很少使用MCP。因为我觉得MCP实现质量层次不齐,本质是上下文注入(工具的本质也是上下文注入),我不确定别人写的提示词会不会影响到我的工作流,干脆直接不用。现在的MCP实现基本上就是把所有的功能全都包装成工具暴露给Agent(一个功能包装成一个工具,给定详细的描述,告诉agent在什么时候进行调用,参数格式是什么),这就导致了现在的提示词爆炸。

直到Anthropic发布了Skills,研究了一下发现本质仍然是提示词注入。如果说MCP是提供了一套注入工具的规范,那么Skills所提倡的则是“离经叛道”。Skills给了一个Markdown文档用于描述该skill的用途和最佳用法,附带提供了一些脚本(与MCP不同)。
![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F6f22d8913dbc6228e7f11a41e0b3c124d817b6d2-1650x929.jpg&w=3840&q=75)
由于这些脚本直接在用户本地运行,存在极大的安全风险。如果用户不能对脚本代码进行review,很容易造成数据泄露、感染病毒等严重安全性问题。相比于MCP提供一个标准化的接口,Skill提供一系列的脚本文件,不同的skill可能拥有不同类型的脚本文件,比如有些脚本使用node.js实现,有些脚本使用Python实现,要使用这些脚本还需要用户安装对应的运行时和脚本所需要的依赖。这也是我说“离经叛道”的原因所在。

这真的是最佳实践吗?

关于渐进式披露,Anthropic是这样描述的:
> 渐进式披露是使代理技能灵活且可扩展的核心设计原则。就像一本组织良好的手册,从目录开始,然后是具体章节,最后是详细的附录一样,技能允许 Claude 仅在需要时加载信息:
> ![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2Fa3bca2763d7892982a59c28aa4df7993aaae55ae-2292x673.jpg&w=3840&q=75)
> 拥有文件系统和代码执行工具的智能体在执行特定任务时,无需将技能的全部内容读取到上下文窗口中。这意味着技能中可以包含的上下文信息量实际上是无限的。

下图是使用Skill的上下文窗口变化
![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F441b9f6cc0d2337913c1f41b05357f16f51f702e-1650x929.jpg&w=3840&q=75)

我们真的需要这样去实现吗?

在我们平时使用CLI工具时,一般的CLI工具都会带有一个`--help`参数,用于查看该工具的用法和说明,这不就是该工具的使用手册吗?比如:
```shell
> npm --help
npm <command>

Usage:

npm install        install all the dependencies in your project
npm install <foo>  add the <foo> dependency to your project
npm test           run this project's tests
npm run <foo>      run the script named <foo>
npm <command> -h   quick help on <command>
npm -l             display usage info for all commands
npm help <term>    search for help on <term>
npm help npm       more involved overview

All commands:

    access, adduser, audit, bugs, cache, ci, completion,
    config, dedupe, deprecate, diff, dist-tag, docs, doctor,
    edit, exec, explain, explore, find-dupes, fund, get, help,
    help-search, hook, init, install, install-ci-test,
    install-test, link, ll, login, logout, ls, org, outdated,
    owner, pack, ping, pkg, prefix, profile, prune, publish,
    query, rebuild, repo, restart, root, run-script, sbom,
    search, set, shrinkwrap, star, stars, start, stop, team,
    test, token, uninstall, unpublish, unstar, update, version,
    view, whoami

Specify configs in the ini-formatted file:
    /Users/xxx/.npmrc
or on the command line via: npm <command> --key=value

More configuration info: npm help config
Configuration fields: npm help 7 config
```

这份手册也不会返回所有的命令所有的用法,它只会返回它有哪些命令可以实现什么功能,对于命令的具体用法你仍然可以通过`--help`参数获得:
```shell
> npm install --help
Install a package

Usage:
npm install [<package-spec> ...]

Options:
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer|--save-bundle]
[-E|--save-exact] [-g|--global]
[--install-strategy <hoisted|nested|shallow|linked>] [--legacy-bundling]
[--global-style] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]]
[--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only]
[--foreground-scripts] [--ignore-scripts] [--no-audit] [--no-bin-links]
[--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>] [--libc <libc>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root] [--install-links]

aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall

Run "npm help install" for more info
```
这是不是很像上面的渐进式披露的定义?

我们是不是可以按照这种风格去实现一个MCP来实现无需skill的工具渐进式披露?我使用Codex将官方的PDF Skill转换成了一个MCP,只暴露一个工具:
```json
{
    "name": "mcp__pdf__pdf",
    "description": "Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.You need to pass in the --help parameter to obtain the usage of this tool first.",
    "input_schema": {
        "type": "object",
        "properties": {
            "params": {
                "$ref": "#/$defs/PdfCommandInput"
            }
        },
        "required": [
            "params"
        ],
        "$defs": {
            "PdfCommandInput": {
                "additionalProperties": false,
                "properties": {
                    "argv": {
                        "description": "Command tokens excluding the leading 'pdf' executable name",
                        "items": {
                            "type": "string"
                        },
                        "title": "Argv",
                        "type": "array"
                    },
                    "command": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "default": null,
                        "description": "Optional command string; if set, it will be split like a shell",
                        "title": "Command"
                    }
                },
                "title": "PdfCommandInput",
                "type": "object"
            }
        },
        "title": "pdfArguments"
    }
}
```
下面是使用该MCP的上下文窗口变化
```json
User: Extract all the text from test.pdf and write it into result.txt
Agent: Tool(name="pdf", args=["--help"])
Tool: pdf - Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale在使用之前你需要先传入--help参数以获取该工具的用法\n\nUsage:\n  pdf [--help] <domain> <command> [options]\n\nDomains:\n  forms      Fill, extract, and validate PDF form fields.\n  documents  Create, merge, and split PDFs.\n  text       Extract text content from PDFs.\n  tables     Extract tables (heuristic, whitespace-based).\n  images     Render PDF pages to PNG images.\n\nRun `pdf <domain> --help` for domain help.\nRun `pdf <domain> <command> --help` for command help.
Agent: Tool(name="pdf", args=["text", "--help"])
Tool: text - Extract text content from PDFs.\n\nUsage:\n  pdf text <command> [options]\n\nCommands:\n  extract  Extract text content from a PDF.\n\nRun `pdf text <command> --help` for details.
Agent: Tool(name="pdf", args=["text", "extract", "--help"])
Tool: usage: pdf text extract --pdf PDF_PATH [--pages PAGES] [--out OUTPUT_PATH]\n                        [--format {text,json}] [--overwrite]\n\nExtract text content from a PDF.\n\noptions:\n  --pdf PDF_PATH        Input PDF\n  --pages PAGES         Page selection like 1,3-5 or 'all' (default: all)\n  --out OUTPUT_PATH     Write extracted text to a file\n  --format {text,json}  Output format (default: text)\n  --overwrite           Overwrite output file if it exists\n
Agent: Tool(name="pdf", args=["text", "extract", "--pdf", "test.pdf", "--out", "result.txt", "--overwrite"])
Tool: {"result": {"stdout": "xx","data": {"pdf_path": "test.pdf","page_count": 6,"pages": [xxx],"format": "text","output_path": "result.txt"}}}
Agent: xxx
```
从上下文变化情况来看,完全实现了渐进式披露,该MCP代码开源(代码完全由codex编写,只验证想法,未做任何审查): https://github.com/musistudio/pdf-skill-mcp

如果你有什么想法也欢迎与我进行交流hropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2Fa3bca2763d7892982a59c28aa4df7993aaae55ae-2292x673.jpg&w=3840&q=75)
> 拥有文件系统和代码执行工具的智能体在执行特定任务时,无需将技能的全部内容读取到上下文窗口中。这意味着技能中可以包含的上下文信息量实际上是无限的。    

下图是使用Skill的上下文窗口变化
![image](https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F441b9f6cc0d2337913c1f41b05357f16f51f702e-1650x929.jpg&w=3840&q=75)

我们真的需要这样去实现吗?

在我们平时使用CLI工具时,一般的CLI工具都会带有一个`--help`参数,用于查看该工具的用法和说明,这不就是该工具的使用手册吗?比如:
```shell
> npm --help
npm <command>

Usage:

npm install        install all the dependencies in your project
npm install <foo>  add the <foo> dependency to your project
npm test           run this project's tests
npm run <foo>      run the script named <foo>
npm <command> -h   quick help on <command>
npm -l             display usage info for all commands
npm help <term>    search for help on <term>
npm help npm       more involved overview

All commands:

    access, adduser, audit, bugs, cache, ci, completion,
    config, dedupe, deprecate, diff, dist-tag, docs, doctor,
    edit, exec, explain, explore, find-dupes, fund, get, help,
    help-search, hook, init, install, install-ci-test,
    install-test, link, ll, login, logout, ls, org, outdated,
    owner, pack, ping, pkg, prefix, profile, prune, publish,
    query, rebuild, repo, restart, root, run-script, sbom,
    search, set, shrinkwrap, star, stars, start, stop, team,
    test, token, uninstall, unpublish, unstar, update, version,
    view, whoami

Specify configs in the ini-formatted file:
    /Users/xxx/.npmrc
or on the command line via: npm <command> --key=value

More configuration info: npm help config
Configuration fields: npm help 7 config
```

这份手册也不会返回所有的命令所有的用法,它只会返回它有哪些命令可以实现什么功能,对于命令的具体用法你仍然可以通过`--help`参数获得:
```shell
> npm install --help
Install a package

Usage:
npm install [<package-spec> ...]

Options:
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer|--save-bundle]
[-E|--save-exact] [-g|--global]
[--install-strategy <hoisted|nested|shallow|linked>] [--legacy-bundling]
[--global-style] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]]
[--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only]
[--foreground-scripts] [--ignore-scripts] [--no-audit] [--no-bin-links]
[--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>] [--libc <libc>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root] [--install-links]

aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall

Run "npm help install" for more info
```
这是不是很像上面的渐进式披露的定义?

我们是不是可以按照这种风格去实现一个MCP来实现无需skill的工具渐进式披露?我使用Codex将官方的PDF Skill转换成了一个MCP,只暴露一个工具:
```json
{
    "name": "mcp__pdf__pdf",
    "description": "Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.You need to pass in the --help parameter to obtain the usage of this tool first.",
    "input_schema": {
        "type": "object",
        "properties": {
            "params": {
                "$ref": "#/$defs/PdfCommandInput"
            }
        },
        "required": [
            "params"
        ],
        "$defs": {
            "PdfCommandInput": {
                "additionalProperties": false,
                "properties": {
                    "argv": {
                        "description": "Command tokens excluding the leading 'pdf' executable name",
                        "items": {
                            "type": "string"
                        },
                        "title": "Argv",
                        "type": "array"
                    },
                    "command": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "default": null,
                        "description": "Optional command string; if set, it will be split like a shell",
                        "title": "Command"
                    }
                },
                "title": "PdfCommandInput",
                "type": "object"
            }
        },
        "title": "pdfArguments"
    }
}
```
下面是使用该MCP的上下文窗口变化
```json
User: Extract all the text from test.pdf and write it into result.txt
Agent: Tool(name="pdf", args=["--help"])
Tool: pdf - Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale在使用之前你需要先传入--help参数以获取该工具的用法\n\nUsage:\n  pdf [--help] <domain> <command> [options]\n\nDomains:\n  forms      Fill, extract, and validate PDF form fields.\n  documents  Create, merge, and split PDFs.\n  text       Extract text content from PDFs.\n  tables     Extract tables (heuristic, whitespace-based).\n  images     Render PDF pages to PNG images.\n\nRun `pdf <domain> --help` for domain help.\nRun `pdf <domain> <command> --help` for command help.
Agent: Tool(name="pdf", args=["text", "--help"])
Tool: text - Extract text content from PDFs.\n\nUsage:\n  pdf text <command> [options]\n\nCommands:\n  extract  Extract text content from a PDF.\n\nRun `pdf text <command> --help` for details.
Agent: Tool(name="pdf", args=["text", "extract", "--help"])
Tool: usage: pdf text extract --pdf PDF_PATH [--pages PAGES] [--out OUTPUT_PATH]\n                        [--format {text,json}] [--overwrite]\n\nExtract text content from a PDF.\n\noptions:\n  --pdf PDF_PATH        Input PDF\n  --pages PAGES         Page selection like 1,3-5 or 'all' (default: all)\n  --out OUTPUT_PATH     Write extracted text to a file\n  --format {text,json}  Output format (default: text)\n  --overwrite           Overwrite output file if it exists\n
Agent: Tool(name="pdf", args=["text", "extract", "--pdf", "test.pdf", "--out", "result.txt", "--overwrite"])
Tool: {"result": {"stdout": "xx","data": {"pdf_path": "test.pdf","page_count": 6,"pages": [xxx],"format": "text","output_path": "result.txt"}}}
Agent: xxx
```
从上下文变化情况来看,完全实现了渐进式披露,该MCP代码开源(代码完全由codex编写,只验证想法,未做任何审查): https://github.com/musistudio/pdf-skill-mcp 

如果你有什么想法也欢迎与我进行交流 email: [m@musiiot.top](mailto://m@musiiot.top )


## /blog/zh/或许我们能在Router中做更多事情.md

# 或许我们能在 Router 中做更多事情

自从`claude-code-router`发布以来,我收到了很多用户的反馈,至今还有不少的 issues 未处理。其中大多都是关于不同的供应商的支持和`deepseek`模型调用工具不积极的问题。
之前开发这个项目主要是为了我自己能以较低成本使用上`claude code`,所以一开始的设计并没有考虑到多供应商的情况。在实际的排查问题中,我发现尽管市面上所有的供应商几乎都宣称兼容`OpenAI`格式调用,即通过`/chat/compeletions`接口调用,但是其中的细节差异非常多。例如:

1. Gemini 的工具参数类型是 string 时,`format`参数只支持`date`和`date-time`,并且没有工具调用 ID。

2. OpenRouter 需要使用`cache_control`进行缓存。

3. DeepSeek 官方 API 的 `max_output` 为 8192,而火山引擎的会更大。

除了这些问题之外,还有一些其他的小的供应商,他们或多或少参数都有点问题。于是,我打算开发一个新的项目[musistudio/llms](https://github.com/musistudio/llms)来处理这种不同服务商的兼容问题。该项目使用 OpenAI 格式为基础的通用格式,提供了一个`Transformer`接口,该接口用于处理转换请求和响应。当我们给不同的服务商都实现了`Transformer`后,我们可以实现不同服务商的混合调用。比如我在`AnthropicTransformer`中实现了`Anthropic`<->`OpenAI`格式的互相转换,并监听了`/v1/messages`端点,在`GeminiTransformer`中实现了`Gemini`<->`OpenAI`格式的互相转换,并监听了`/v1beta/models/:modelAndAction`端点,当他们的请求和响应都被转换成一个通用格式的时候,就可以实现他们的互相调用。

```
AnthropicRequest -> AnthropicTransformer -> OpenAIRequest -> GeminiTransformer -> GeminiRequest -> GeminiServer
```

```
GeminiReseponse -> GeminiTransformer -> OpenAIResponse -> AnthropicTransformer -> AnthropicResponse
```

虽然使用中间层抹平差异可能会带来一些性能问题,但是该项目最初的目的是为了让`claude-code-router`支持不同的供应商。

至于`deepseek`模型调用工具不积极的问题,我发现这是由于`deepseek`在长上下文中的指令遵循不佳导致的。现象就是刚开始模型会主动调用工具,但是在经过几轮对话后模型只会返回文本。一开始的解决方案是通过注入一个系统提示词告知模型需要积极去使用工具以解决用户的问题,但是后面测试发现在长上下文中模型会遗忘该指令。
查看`deepseek`文档后发现模型支持`tool_choice`参数,可以强制让模型最少调用 1 个工具,我尝试将该值设置为`required`,发现模型调用工具的积极性大大增加,现在我们只需要在合适的时候取消这个参数即可。借助[musistudio/llms](https://github.com/musistudio/llms)的`Transformer`可以让我们在发送请求前和收到响应后做点什么,所以我参考`claude code`的`Plan Mode`,实现了一个使用与`deepseek`的`Tool Mode`

```typescript
export class TooluseTransformer implements Transformer {
  name = "tooluse";

  transformRequestIn(request: UnifiedChatRequest): UnifiedChatRequest {
    if (request.tools?.length) {
      request.messages.push({
        role: "system",
        content: `<system-reminder>Tool mode is active. The user expects you to proactively execute the most suitable tool to help complete the task. 
Before invoking a tool, you must carefully evaluate whether it matches the current task. If no available tool is appropriate for the task, you MUST call the \`ExitTool\` to exit tool mode — this is the only valid way to terminate tool mode.
Always prioritize completing the user's task effectively and efficiently by using tools whenever appropriate.</system-reminder>`,
      });
      request.tool_choice = "required";
      request.tools.unshift({
        type: "function",
        function: {
          name: "ExitTool",
          description: `Use this tool when you are in tool mode and have completed the task. This is the only valid way to exit tool mode.
IMPORTANT: Before using this tool, ensure that none of the available tools are applicable to the current task. You must evaluate all available options — only if no suitable tool can help you complete the task should you use ExitTool to terminate tool mode.
Examples:
1. Task: "Use a tool to summarize this document" — Do not use ExitTool if a summarization tool is available.
2. Task: "What’s the weather today?" — If no tool is available to answer, use ExitTool after reasoning that none can fulfill the task.`,
          parameters: {
            type: "object",
            properties: {
              response: {
                type: "string",
                description:
                  "Your response will be forwarded to the user exactly as returned — the tool will not modify or post-process it in any way.",
              },
            },
            required: ["response"],
          },
        },
      });
    }
    return request;
  }

  async transformResponseOut(response: Response): Promise<Response> {
    if (response.headers.get("Content-Type")?.includes("application/json")) {
      const jsonResponse = await response.json();
      if (
        jsonResponse?.choices[0]?.message.tool_calls?.length &&
        jsonResponse?.choices[0]?.message.tool_calls[0]?.function?.name ===
          "ExitTool"
      ) {
        const toolArguments = JSON.parse(toolCall.function.arguments || "{}");
        jsonResponse.choices[0].message.content = toolArguments.response || "";
        delete jsonResponse.choices[0].message.tool_calls;
      }

      // Handle non-streaming response if needed
      return new Response(JSON.stringify(jsonResponse), {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });
    } else if (response.headers.get("Content-Type")?.includes("stream")) {
      // ...
    }
    return response;
  }
}
```

该工具将始终让模型至少调用一个工具,如果没有合适的工具或者任务已完成可以调用`ExitTool`来退出工具模式,因为是依靠`tool_choice`参数实现的,所以仅适用于支持该参数的模型。经过测试,该工具能显著增加`deepseek`的工具调用次数,弊端是可能会有跟任务无关或者没有必要的工具调用导致增加任务执行事件和消耗的 `token` 数。

这次更新仅仅是在 Router 中实现一个`agent`的一次小探索,或许还能做更多其他有趣的事也说不定...


## /blog/zh/项目初衷及原理.md

# 项目初衷及原理

早在 Claude Code 发布的第二天(2025-02-25),我就尝试并完成了对该项目的逆向。当时要使用 Claude Code 你需要注册一个 Anthropic 账号,然后申请 waitlist,等待通过后才能使用。但是因为众所周知的原因,Anthropic 屏蔽了中国区的用户,所以通过正常手段我无法使用,通过已知的信息,我发现:

1. Claude Code 使用 npm 进行安装,所以很大可能其使用 Node.js 进行开发。
2. Node.js 调试手段众多,可以简单使用`console.log`获取想要的信息,也可以使用`--inspect`将其接入`Chrome Devtools`,甚至你可以使用`d8`去调试某些加密混淆的代码。

由于我的目标是让我在没有 Anthropic 账号的情况下使用`Claude Code`,我并不需要获得完整的源代码,只需要将`Claude Code`请求 Anthropic 模型时将其转发到我自定义的接口即可。接下来我就开启了我的逆向过程:

1. 首先安装`Claude Code`

```bash
npm install -g @anthropic-ai/claude-code
```

2. 安装后该项目被放在了`~/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code`中,因为我使用了`nvm`作为我的 node 版本控制器,当前使用`node-v20.10.0`,所以该路径会因人而异。
3. 找到项目路径之后可通过 package.json 分析包入口,内容如下:

```package.json
{
  "name": "@anthropic-ai/claude-code",
  "version": "1.0.24",
  "main": "sdk.mjs",
  "types": "sdk.d.ts",
  "bin": {
    "claude": "cli.js"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "type": "module",
  "author": "Boris Cherny <boris@anthropic.com>",
  "license": "SEE LICENSE IN README.md",
  "description": "Use Claude, Anthropic's AI assistant, right from your terminal. Claude can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.",
  "homepage": "https://github.com/anthropics/claude-code",
  "bugs": {
    "url": "https://github.com/anthropics/claude-code/issues"
  },
  "scripts": {
    "prepare": "node -e \"if (!process.env.AUTHORIZED) { console.error('ERROR: Direct publishing is not allowed.\\nPlease use the publish-external.sh script to publish this package.'); process.exit(1); }\"",
    "preinstall": "node scripts/preinstall.js"
  },
  "dependencies": {},
  "optionalDependencies": {
    "@img/sharp-darwin-arm64": "^0.33.5",
    "@img/sharp-darwin-x64": "^0.33.5",
    "@img/sharp-linux-arm": "^0.33.5",
    "@img/sharp-linux-arm64": "^0.33.5",
    "@img/sharp-linux-x64": "^0.33.5",
    "@img/sharp-win32-x64": "^0.33.5"
  }
}
```

其中`"claude": "cli.js"`就是我们要找的入口,打开 cli.js,发现代码被压缩混淆过了。没关系,借助`webstorm`的`Formate File`功能可以重新格式化,让代码变得稍微好看一点。就像这样:
![webstorm-formate-file](../images/webstorm-formate-file.png)

现在,你可以通过阅读部分代码来了解`Claude Code`的内容工具原理与提示词。你也可以在关键地方使用`console.log`来获得更多信息,当然,也可以使用`Chrome Devtools`来进行断点调试,使用以下命令启动`Claude Code`:

```bash
NODE_OPTIONS="--inspect-brk=9229" claude
```

该命令会以调试模式启动`Claude Code`,并将调试的端口设置为`9229`。这时候通过 Chrome 访问`chrome://inspect/`即可看到当前的`Claude Code`进程,点击`inspect`即可进行调试。
![chrome-devtools](../images/chrome-inspect.png)
![chrome-devtools](../images/chrome-devtools.png)

通过搜索关键字符`api.anthropic.com`很容易能找到`Claude Code`用来发请求的地方,根据上下文的查看,很容易发现这里的`baseURL`可以通过环境变量`ANTHROPIC_BASE_URL`进行覆盖,`apiKey`和`authToken`也同理。
![search](../images/search.png)

到目前为止,我们获得关键信息:

1. 可以使用环境变量覆盖`Claude Code`的`BaseURL`和`apiKey`的配置

2. `Claude Code`使用[Anthropic API](https://docs.anthropic.com/en/api/overview)的规范

所以我们需要:

1. 实现一个服务用来将`OpenAI API`的规范转换成`Anthropic API`格式。

2. 启动`Claude Code`之前写入环境变量将`baseURL`指向到该服务。

于是,`claude-code-router`就诞生了,该项目使用`Express.js`作为 HTTP 服务,实现`/v1/messages`端点,使用`middlewares`处理请求/响应的格式转换以及请求重写功能(可以用来重写 Claude Code 的提示词以针对单个模型进行调优)。
在 2 月份由于`DeepSeek`全系列模型对`Function Call`的支持不佳导致无法直接使用`DeepSeek`模型,所以在当时我选择了`qwen-max`模型,一切表现的都很好,但是`qwen-max`不支持`KV Cache`,意味着我要消耗大量的 token,但是却无法获取`Claude Code`原生的体验。
所以我又尝试了`Router`模式,即使用一个小模型对任务进行分发,一共分为四个模型:`router`、`tool`、`think`和`coder`,所有的请求先经过一个免费的小模型,由小模型去判断应该是进行思考还是编码还是调用工具,再进行任务的分发,如果是思考和编码任务将会进行循环调用,直到最终使用工具写入或修改文件。但是实践下来发现免费的小模型不足以很好的完成任务的分发,再加上整个 Agnet 的设计存在缺陷,导致并不能很好的驱动`Claude Code`。
直到 5 月底,`Claude Code`被正式推出,这时`DeepSeek`全系列模型(R1 于 05-28)均支持`Function Call`,我开始重新设计该项目。在与 AI 的结对编程中我修复了之前的请求和响应转换问题,在某些场景下模型输出 JSON 响应而不是`Function Call`。这次直接使用`DeepSeek-v3`模型,它工作的比我想象中要好:能完成绝大多数工具调用,还支持用步骤规划解决任务,最关键的是`DeepSeek`的价格不到`claude Sonnet 3.5`的十分之一。正式发布的`Claude Code`对 Agent 的组织也不同于测试版,于是在分析了`Claude Code`的请求调用之后,我重新组织了`Router`模式:现在它还是四个模型:默认模型、`background`、`think`和`longContext`。

- 默认模型作为最终的兜底和日常处理

- `background`是用来处理一些后台任务,据 Anthropic 官方说主要用`Claude Haiku 3.5`模型去处理一些小任务,如俳句生成和对话摘要,于是我将其路由到了本地的`ollama`服务。

- `think`模型用于让`Claude Code`进行思考或者在`Plan Mode`下使用,这里我使用的是`DeepSeek-R1`,由于其不支持推理成本控制,所以`Think`和`UltraThink`是一样的逻辑。

- `longContext`是用于处理长下上文的场景,该项目会对每次请求使用tiktoken实时计算上下文长度,如果上下文大于32K则使用该模型,旨在弥补`DeepSeek`在长上下文处理不佳的情况。

以上就是该项目的发展历程以及我的一些思考,通过巧妙的使用环境变量覆盖的手段在不修改`Claude Code`源码的情况下完成请求的转发和修改,这就使得在可以得到 Anthropic 更新的同时使用自己的模型,自定义自己的提示词。该项目只是在 Anthropic 封禁中国区用户的情况下使用`Claude Code`并且达到成本和性能平衡的一种手段。如果可以的话,还是官方的Max Plan体验最好。


## /custom-router.example.js

```js path="/custom-router.example.js" 
module.exports = async function router(req, config) {
  return "deepseek,deepseek-chat";
};

```

## /docs/.gitignore

```gitignore path="/docs/.gitignore" 
# Docusaurus build output
build/
dist/

# Docusaurus generated files
.docusaurus/

# Node modules
node_modules/

# Environment variables
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/

# OS
.DS_Store
Thumbs.db

# Logs
*.log

# Misc
*.swp
*.swo

```

## /docs/README.md

# Claude Code Router Documentation

This directory contains the documentation website built with [Docusaurus](https://docusaurus.io/).

## Development

### Install Dependencies

```bash
cd docs
pnpm install
```

### Start Development Server

```bash
# From docs directory
pnpm start

# Or from root directory
pnpm dev:docs
```

Open [http://localhost:3000](http://localhost:3000) to view the documentation.

## Build

```bash
# From docs directory
pnpm build

# Or from root directory
pnpm build:docs
```

The built files will be in the `build/` directory.

## Serve Built Files

```bash
# From docs directory
pnpm serve

# Or from root directory
pnpm serve:docs
```

## Structure

```
docs/
├── docs/              # Markdown documentation files
│   ├── intro.md       # Introduction page
│   ├── installation.md
│   ├── config/        # Configuration docs
│   ├── advanced/      # Advanced topics
│   └── cli/           # CLI reference
├── src/               # React components and pages
│   ├── components/    # Custom React components
│   ├── pages/         # Additional pages
│   ├── css/           # Custom CSS
│   └── theme/         # Docusaurus theme customization
├── static/            # Static assets (images, etc.)
├── i18n/              # Internationalization files
├── docusaurus.config.ts  # Docusaurus configuration
└── sidebars.ts        # Documentation sidebar structure
```

## Adding Documentation

### Adding New Docs

Create a new Markdown file in the `docs/` directory and add it to `sidebars.ts`.

### Adding New Pages

Add React components to `src/pages/`.

### Customizing Styles

Edit `src/css/custom.css`.

## Internationalization

Documentation supports both English and Chinese.

- English: `docs/` and `src/`
- Chinese: `i18n/zh/docusaurus-plugin-content-docs/current/`

To add Chinese translations:

1. Create corresponding files in `i18n/zh/docusaurus-plugin-content-docs/current/`
2. Translate the content

## Deployment

The documentation can be deployed to:

- GitHub Pages
- Netlify
- Vercel
- Any static hosting service

See [Docusaurus deployment docs](https://docusaurus.io/docs/deployment) for details.


## /docs/blog/2025-02-25-project-motivation.md

---
title: Project Motivation and Principles
date: 2025-02-25
tags: [claude-code, reverse-engineering, tutorial]
---

# Project Motivation and Principles

As early as the day after Claude Code was released (2025-02-25), I began and completed a reverse engineering attempt of the project. At that time, using Claude Code required registering for an Anthropic account, applying for a waitlist, and waiting for approval. However, due to well-known reasons, Anthropic blocks users from mainland China, making it impossible for me to use the service through normal means. Based on known information, I discovered the following:

1. Claude Code is installed via npm, so it's very likely developed with Node.js.
2. Node.js offers various debugging methods: simple `console.log` usage, launching with `--inspect` to hook into Chrome DevTools, or even debugging obfuscated code using `d8`.

My goal was to use Claude Code without an Anthropic account. I didn't need the full source code—just a way to intercept and reroute requests made by Claude Code to Anthropic's models to my own custom endpoint. So I started the reverse engineering process:

1. First, install Claude Code:
```bash
npm install -g @anthropic-ai/claude-code
```

2. After installation, the project is located at `~/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code`(this may vary depending on your Node version manager and version).

3. Open the package.json to analyze the entry point:
```package.json
{
  "name": "@anthropic-ai/claude-code",
  "version": "1.0.24",
  "main": "sdk.mjs",
  "types": "sdk.d.ts",
  "bin": {
    "claude": "cli.js"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "type": "module",
  "author": "Boris Cherny <boris@anthropic.com>",
  "license": "SEE LICENSE IN README.md",
  "description": "Use Claude, Anthropic's AI assistant, right from your terminal. Claude can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.",
  "homepage": "https://github.com/anthropics/claude-code",
  "bugs": {
    "url": "https://github.com/anthropics/claude-code/issues"
  },
  "scripts": {
    "prepare": "node -e \"if (!process.env.AUTHORIZED) { console.error('ERROR: Direct publishing is not allowed.\\nPlease use the publish-external.sh script to publish this package.'); process.exit(1); }\"",
    "preinstall": "node scripts/preinstall.js"
  },
  "dependencies": {},
  "optionalDependencies": {
    "@img/sharp-darwin-arm64": "^0.33.5",
    "@img/sharp-darwin-x64": "^0.33.5",
    "@img/sharp-linux-arm": "^0.33.5",
    "@img/sharp-linux-arm64": "^0.33.5",
    "@img/sharp-linux-x64": "^0.33.5",
    "@img/sharp-win32-x64": "^0.33.5"
  }
}
```

The key entry is `"claude": "cli.js"`. Opening cli.js, you'll see the code is minified and obfuscated. But using WebStorm's `Format File` feature, you can reformat it for better readability:
![webstorm-formate-file](/blog-images/webstorm-formate-file.png)

Now you can begin understanding Claude Code's internal logic and prompt structure by reading the code. To dig deeper, you can insert console.log statements or launch in debug mode with Chrome DevTools using:

```bash
NODE_OPTIONS="--inspect-brk=9229" claude
```

This command starts Claude Code in debug mode and opens port 9229. Visit chrome://inspect/ in Chrome and click inspect to begin debugging:
![chrome-devtools](/blog-images/chrome-inspect.png)
![chrome-devtools](/blog-images/chrome-devtools.png)

By searching for the keyword api.anthropic.com, you can easily locate where Claude Code makes its API calls. From the surrounding code, it's clear that baseURL can be overridden with the `ANTHROPIC_BASE_URL` environment variable, and `apiKey` and `authToken` can be configured similarly:
![search](/blog-images/search.png)

So far, we've discovered some key information:

1. Environment variables can override Claude Code's `baseURL` and `apiKey`.

2. Claude Code adheres to the Anthropic API specification.

Therefore, we need:
1. A service to convert OpenAI API-compatible requests into Anthropic API format.

2. Set the environment variables before launching Claude Code to redirect requests to this service.

Thus, `claude-code-router` was born. This project uses `Express.js` to implement the `/v1/messages` endpoint. It leverages middlewares to transform request/response formats and supports request rewriting (useful for prompt tuning per model).

Back in February, the full DeepSeek model series had poor support for Function Calling, so I initially used `qwen-max`. It worked well—but without KV cache support, it consumed a large number of tokens and couldn't provide the native `Claude Code` experience.

So I experimented with a Router-based mode using a lightweight model to dispatch tasks. The architecture included four roles: `router`, `tool`, `think`, and `coder`. Each request passed through a free lightweight model that would decide whether the task involved reasoning, coding, or tool usage. Reasoning and coding tasks looped until a tool was invoked to apply changes. However, the lightweight model lacked the capability to route tasks accurately, and architectural issues prevented it from effectively driving Claude Code.

Everything changed at the end of May when the official Claude Code was launched, and `DeepSeek-R1` model (released 2025-05-28) added Function Call support. I redesigned the system. With the help of AI pair programming, I fixed earlier request/response transformation issues—especially the handling of models that return JSON instead of Function Call outputs.

This time, I used the `DeepSeek-V3`  model. It performed better than expected: supporting most tool calls, handling task decomposition and stepwise planning, and—most importantly—costing less than one-tenth the price of Claude 3.5 Sonnet.

The official Claude Code organizes agents differently from the beta version, so I restructured my Router mode to include four roles: the default model, `background`, `think`, and `longContext`.

- The default model handles general tasks and acts as a fallback.

- The `background` model manages lightweight background tasks. According to Anthropic, Claude Haiku 3.5 is often used here, so I routed this to a local `ollama` service.

- The `think` model is responsible for reasoning and planning mode tasks. I use `DeepSeek-R1` here, though it doesn't support cost control, so `Think` and `UltraThink` behave identically.

- The `longContext` model handles long-context scenarios. The router uses `tiktoken` to calculate token lengths in real time, and if the context exceeds 32K, it switches to this model to compensate for DeepSeek's long-context limitations.

This describes the evolution and reasoning behind the project. By cleverly overriding environment variables, we can forward and modify requests without altering Claude Code's source—allowing us to benefit from official updates while using our own models and custom prompts.

This project offers a practical approach to running Claude Code under Anthropic's regional restrictions, balancing `cost`, `performance`, and `customizability`. That said, the official `Max Plan` still offers the best experience if available.


## /docs/blog/2025-11-18-glm-reasoning.md

---
title: GLM-4.6 Supports Reasoning and Interleaved Thinking
date: 2025-11-18
tags: [glm, reasoning, chain-of-thought]
---

# GLM-4.6 Supports Reasoning and Interleaved Thinking

## Enabling Reasoning in Claude Code with GLM-4.6

Starting from version 4.5, GLM has supported Claude Code. I've been following its progress closely, and many users have reported that reasoning could not be enabled within Claude Code. Recently, thanks to sponsorship from Zhipu, I decided to investigate this issue in depth. According to the [official documentation](https://docs.z.ai/api-reference/llm/chat-completion), the`/chat/completions` endpoint has reasoning enabled by default, but the model itself decides whether to think:

```
thinking.type enum<string> default:enabled

Whether to enable the chain of thought(When enabled, GLM-4.6, GLM-4.5 and others will automatically determine whether to think, while GLM-4.5V will think compulsorily), default: enabled

Available options: enabled, disabled
```

However, within Claude Code, its heavy system prompt interference disrupts GLM's internal reasoning judgment, causing the model to rarely think.
Therefore, we need to explicitly guide the model to believe reasoning is required. Since claude-code-router functions as a proxy, the only feasible approach is modifying prompts or parameters.

Initially, I tried completely removing Claude Code's system prompt — and indeed, the model started reasoning — but that broke Claude Code's workflow.
So instead, I used prompt injection to clearly instruct the model to think step by step.


```javascript
// transformer.ts
import { UnifiedChatRequest } from "../types/llm";
import { Transformer } from "../types/transformer";

export class ForceReasoningTransformer implements Transformer {
  name = "forcereasoning";

  async transformRequestIn(
    request: UnifiedChatRequest
  ): Promise<UnifiedChatRequest> {
    const systemMessage = request.messages.find(
      (item) => item.role === "system"
    );
    if (Array.isArray(systemMessage?.content)) {
      systemMessage.content.push({
        type: "text",
        text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
      });
    }
    const lastMessage = request.messages[request.messages.length - 1];
    if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
      lastMessage.content.push({
        type: "text",
        text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
      });
    }
    if (lastMessage.role === "tool") {
      request.messages.push({
        role: "user",
        content: [
          {
            type: "text",
            text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
          },
        ],
      });
    }
    return request;
  }
}
```

Why use `<reasoning_content>` instead of the `<think>` tag? Two reasons:

1. Using the `<think>` tag doesn't effectively trigger reasoning — likely because the model was trained on data where `<think>` had special behavior.

2. If we use `<think>`, the reasoning output is split into a separate field, which directly relates to the chain-of-thought feedback problem discussed below.

## Chain-of-Thought Feedback
Recently, Minimax released `Minimax-m2`, along with [an article](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2) explaining interleaved thinking.
While the idea isn't entirely new, it's a good opportunity to analyze it.

Why do we need to interleaved thinking?
Minimax's article mentions that the Chat Completion API does not support passing reasoning content between requests.
We know ChatGPT was the first to support reasoning, but OpenAI initially didn't expose the chain of thought to users.
Therefore, the Chat Completion API didn't need to support it. Even the CoT field was first introduced by DeepSeek.

Do we really need explicit CoT fields? What happens if we don't have them? Will it affect reasoning?
By inspecting [sglang's source code](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py), we can see that reasoning content is naturally emitted in messages with specific markers.
If we don't split it out, the next-round conversation will naturally include it.
Thus, the only reason we need interleaved thinking is because we separated the reasoning content from the normal messages.

With fewer than 40 lines of code above, I implemented a simple exploration of enabling reasoning and chain-of-thought feedback for GLM-4.5/4.6.
(It's only simple because I haven't implemented parsing logic yet — you could easily modify the transformer to split reasoning output on response and merge it back on request, improving Claude Code's frontend display compatibility.)

If you have better ideas, feel free to reach out — I'd love to discuss further.


## /docs/blog/2025-11-18-router-exploration.md

---
title: Maybe We Can Do More with the Router
date: 2025-11-18
tags: [router, transformer, deepseek]
---

# Maybe We Can Do More with the Router

Since the release of `claude-code-router`, I've received a lot of user feedback, and quite a few issues are still open. Most of them are related to support for different providers and the lack of tool usage from the deepseek model.

Originally, I created this project for personal use, mainly to access claude code at a lower cost. So, multi-provider support wasn't part of the initial design. But during troubleshooting, I discovered that even though most providers claim to be compatible with the OpenAI-style `/chat/completions` interface, there are many subtle differences. For example:

1. When Gemini's tool parameter type is string, the `format` field only supports `date` and `date-time`, and there's no tool call ID.

2. OpenRouter requires `cache_control` for caching.

3. The official DeepSeek API has a `max_output` of 8192, but Volcano Engine's limit is even higher.

Aside from these, smaller providers often have quirks in their parameter handling. So I decided to create a new project, [musistudio/llms](https://github.com/musistudio/llms), to deal with these compatibility issues. It uses the OpenAI format as a base and introduces a generic Transformer interface for transforming both requests and responses.

Once a `Transformer` is implemented for each provider, it becomes possible to mix-and-match requests between them. For example, I implemented bidirectional conversion between Anthropic and OpenAI formats in `AnthropicTransformer`, which listens to the `/v1/messages` endpoint. Similarly, `GeminiTransformer` handles Gemini &lt;-&gt; OpenAI format conversions and listens to `/v1beta/models/:modelAndAction`.

When both requests and responses are transformed into a common format, they can interoperate seamlessly:

```
AnthropicRequest -> AnthropicTransformer -> OpenAIRequest -> GeminiTransformer -> GeminiRequest -> GeminiServer
```

```
GeminiResponse -> GeminiTransformer -> OpenAIResponse -> AnthropicTransformer -> AnthropicResponse
```

Using a middleware layer to smooth out differences may introduce some performance overhead, but the main goal here is to enable `claude-code-router` to support multiple providers.

As for the issue of DeepSeek's lackluster tool usage — I found that it stems from poor instruction adherence in long conversations. Initially, the model actively calls tools, but after several rounds, it starts responding with plain text instead. My first workaround was injecting a system prompt to remind the model to use tools proactively. But in long contexts, the model tends to forget this instruction.

After reading the DeepSeek documentation, I noticed it supports the `tool_choice` parameter, which can be set to `"required"` to force the model to use at least one tool. I tested this by enabling the parameter, and it significantly improved the model's tool usage. We can remove the setting when it's no longer necessary. With the help of the `Transformer` interface in [musistudio/llms](https://github.com/musistudio/llms), we can modify the request before it's sent and adjust the response after it's received.

Inspired by the Plan Mode in `claude code`, I implemented a similar Tool Mode for DeepSeek:

```typescript
export class TooluseTransformer implements Transformer {
  name = "tooluse";

  transformRequestIn(request: UnifiedChatRequest): UnifiedChatRequest {
    if (request.tools?.length) {
      request.messages.push({
        role: "system",
        content: `<system-reminder>Tool mode is active. The user expects you to proactively execute the most suitable tool to help complete the task.
Before invoking a tool, you must carefully evaluate whether it matches the current task. If no available tool is appropriate for the task, you MUST call the \`ExitTool\` to exit tool mode — this is the only valid way to terminate tool mode.
Always prioritize completing the user's task effectively and efficiently by using tools whenever appropriate.</system-reminder>`,
      });
      request.tool_choice = "required";
      request.tools.unshift({
        type: "function",
        function: {
          name: "ExitTool",
          description: `Use this tool when you are in tool mode and have completed the task. This is the only valid way to exit tool mode.
IMPORTANT: Before using this tool, ensure that none of the available tools are applicable to the current task. You must evaluate all available options — only if no suitable tool can help you complete the task should you use ExitTool to terminate tool mode.
Examples:
1. Task: "Use a tool to summarize this document" — Do not use ExitTool if a summarization tool is available.
2. Task: "What's the weather today?" — If no tool is available to answer, use ExitTool after reasoning that none can fulfill the task.`,
          parameters: {
            type: "object",
            properties: {
              response: {
                type: "string",
                description:
                  "Your response will be forwarded to the user exactly as returned — the tool will not modify or post-process it in any way.",
              },
            },
            required: ["response"],
          },
        },
      });
    }
    return request;
  }

  async transformResponseOut(response: Response): Promise<Response> {
    if (response.headers.get("Content-Type")?.includes("application/json")) {
      const jsonResponse = await response.json();
      if (
        jsonResponse?.choices[0]?.message.tool_calls?.length &&
        jsonResponse?.choices[0]?.message.tool_calls[0]?.function?.name ===
          "ExitTool"
      ) {
        const toolArguments = JSON.parse(toolCall.function.arguments || "{}");
        jsonResponse.choices[0].message.content = toolArguments.response || "";
        delete jsonResponse.choices[0].message.tool_calls;
      }

      // Handle non-streaming response if needed
      return new Response(JSON.stringify(jsonResponse), {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });
    } else if (response.headers.get("Content-Type")?.includes("stream")) {
      // ...
    }
    return response;
  }
}
```

This transformer ensures the model calls at least one tool. If no tools are appropriate or the task is finished, it can exit using `ExitTool`. Since this relies on the `tool_choice` parameter, it only works with models that support it.

In practice, this approach noticeably improves tool usage for DeepSeek. The tradeoff is that sometimes the model may invoke irrelevant or unnecessary tools, which could increase latency and token usage.

This update is just a small experiment — adding an `"agent"` to the router. Maybe there are more interesting things we can explore from here.


## /docs/docs/cli/commands/model.md

---
sidebar_position: 2
---

# ccr model

Interactive model selection and configuration.

## Usage

```bash
ccr model [command]
```

## Commands

### Select Model

Interactively select a model:

```bash
ccr model
```

This will display an interactive menu with available providers and models.

### Set Default Model

Set the default model directly:

```bash
ccr model set <provider>,<model>
```

Example:

```bash
ccr model set deepseek,deepseek-chat
```

### List Models

List all configured models:

```bash
ccr model list
```

### Add Model

Add a new model to configuration:

```bash
ccr model add <provider>,<model>
```

Example:

```bash
ccr model add groq,llama-3.3-70b-versatile
```

### Remove Model

Remove a model from configuration:

```bash
ccr model remove <provider>,<model>
```

## Examples

### Interactive selection

```bash
$ ccr model

? Select a provider: deepseek
? Select a model: deepseek-chat

Default model set to: deepseek,deepseek-chat
```

### Direct configuration

```bash
ccr model set deepseek,deepseek-chat
```

### View current configuration

```bash
ccr model list
```

Output:

```
Configured Models:
  deepseek,deepseek-chat (default)
  groq,llama-3.3-70b-versatile
  gemini,gemini-1.5-pro
```

## Related Commands

- [ccr start](/docs/cli/start) - Start the server
- [ccr config](/docs/cli/other-commands#ccr-config) - Edit configuration


## /docs/docs/cli/commands/other.md

---
sidebar_position: 4
---

# Other Commands

Additional CLI commands for managing Claude Code Router.

## ccr stop

Stop the running server.

```bash
ccr stop
```

## ccr restart

Restart the server.

```bash
ccr restart
```

## ccr code

Execute a claude command through the router.

```bash
ccr code [args...]
```

## ccr ui

Open the Web UI in your browser.

```bash
ccr ui
```

## ccr activate

Output shell environment variables for integration with external tools.

```bash
ccr activate
```

## Global Options

These options can be used with any command:

| Option | Description |
|--------|-------------|
| `-h, --help` | Show help |
| `-v, --version` | Show version number |
| `--config <path>` | Path to configuration file |
| `--verbose` | Enable verbose output |

## Examples

### Stop the server

```bash
ccr stop
```

### Restart with custom config

```bash
ccr restart --config /path/to/config.json
```

### Open Web UI

```bash
ccr ui
```

## Related Documentation

- [Getting Started](/docs/intro) - Introduction to Claude Code Router
- [Configuration](/docs/config/basic) - Configuration guide


## /docs/docs/cli/commands/preset.md

---
sidebar_position: 5
---

# ccr preset

Manage presets - configuration templates that can be shared and reused.

## Overview

Presets allow you to:
- Save your current configuration as a reusable template
- Share configurations with others
- Install pre-configured setups from the community
- Switch between different configurations easily

## Commands

### export

Export your current configuration as a preset.

```bash
ccr preset export <name> [options]
```

**Options:**
- `--output <path>` - Custom output directory path
- `--description <text>` - Preset description
- `--author <name>` - Preset author
- `--tags <tags>` - Comma-separated keywords
- `--include-sensitive` - Include API keys and sensitive data (not recommended)

**Example:**
```bash
ccr preset export my-config --description "My production setup" --author "Your Name"
```

**What happens:**
1. Reads current configuration from `~/.claude-code-router/config.json`
2. Prompts for description, author, and keywords (if not provided)
3. Sanitizes sensitive fields (API keys become placeholders)
4. Creates preset directory at `~/.claude-code-router/presets/<name>/`
5. Generates `manifest.json` with configuration and metadata

### install

Install a preset from a local directory.

```bash
ccr preset install <source>
```

**Sources:**
- Local directory path: `/path/to/preset-directory`
- Preset name (for reconfiguring an already installed preset): `preset-name`

**Example:**
```bash
# Install from directory
ccr preset install ./my-preset

# Reconfigure an installed preset
ccr preset install my-preset
```

**What happens:**
1. Reads `manifest.json` from the preset directory
2. Validates the preset structure
3. If the preset has a `schema`, prompts for required values (API keys, etc.)
4. Copies preset to `~/.claude-code-router/presets/<name>/`
5. Saves user inputs in `manifest.json`

**Note:** URL installation is not currently supported. Download the preset directory first.

### list

List all installed presets.

```bash
ccr preset list
```

**Example output:**
```
Available presets:

• my-config (v1.0.0)
  My production setup
  by Your Name

• openai-setup
  Basic OpenAI configuration
```

### info

Show detailed information about a preset.

```bash
ccr preset info <name>
```

**Shows:**
- Version, description, author, keywords
- Configuration summary (Providers, Router rules)
- Required inputs (if any)

**Example:**
```bash
ccr preset info my-config
```

### delete / rm / remove

Delete an installed preset.

```bash
ccr preset delete <name>
ccr preset rm <name>
ccr preset remove <name>
```

**Example:**
```bash
ccr preset delete my-config
```

## Preset Structure

A preset is a directory containing a `manifest.json` file:

```json
{
  "name": "my-preset",
  "version": "1.0.0",
  "description": "My configuration",
  "author": "Author Name",
  "keywords": ["openai", "production"],

  "Providers": [
    {
      "name": "openai",
      "api_base_url": "https://api.openai.com/v1/chat/completions",
      "api_key": "{{apiKey}}",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    }
  ],

  "Router": {
    "default": "openai,gpt-4"
  },

  "schema": [
    {
      "id": "apiKey",
      "type": "password",
      "label": "OpenAI API Key",
      "prompt": "Enter your OpenAI API key"
    }
  ]
}
```

### Schema System

The `schema` field defines inputs that users must provide during installation:

**Field types:**
- `password` - Hidden input (for API keys)
- `input` - Text input
- `select` - Single selection from options
- `multiselect` - Multiple selection
- `confirm` - Yes/No confirmation
- `editor` - Multi-line text
- `number` - Numeric input

**Dynamic options:**
```json
{
  "id": "provider",
  "type": "select",
  "label": "Select Provider",
  "options": {
    "type": "providers"
  }
}
```

**Conditional fields:**
```json
{
  "id": "model",
  "type": "select",
  "label": "Select Model",
  "when": {
    "field": "provider",
    "operator": "exists"
  },
  "options": {
    "type": "models",
    "providerField": "#{selectedProvider}"
  }
}
```

## Sharing Presets

To share a preset:

1. **Export your configuration:**
   ```bash
   ccr preset export my-preset
   ```

2. **Share the directory:**
   ```bash
   ~/.claude-code-router/presets/my-preset/
   ```

3. **Distribution methods:**
   - Upload to GitHub repository
   - Create a GitHub Gist
   - Share as a zip file
   - Publish on npm (future feature)

4. **Users install with:**
   ```bash
   ccr preset install /path/to/my-preset
   ```

## Security

### Automatic Sanitization

By default, `export` sanitizes sensitive fields:
- Fields named `api_key`, `apikey`, `password`, `secret` are replaced with `{{fieldName}}` placeholders
- These placeholders become required inputs in the schema
- Users are prompted to provide their own values during installation

### Include Sensitive Data

To include actual values (not recommended):
```bash
ccr preset export my-preset --include-sensitive
```

**Warning:** Never share presets containing sensitive data!

## Related Documentation

- [Configuration Guide](/docs/cli/config/basic) - Basic configuration
- [Project-Level Configuration](/docs/cli/config/project-level) - Project-specific settings
- [Presets](/docs/presets/intro) - Advanced preset topics


## /docs/docs/cli/commands/start.md

---
sidebar_position: 1
---

# ccr start

Start the Claude Code Router server.

## Usage

```bash
ccr start [options]
```

## Options

| Option | Alias | Description |
|--------|-------|-------------|
| `--port <number>` | `-p` | Port to listen on (default: 8080) |
| `--config <path>` | `-c` | Path to configuration file |
| `--daemon` | `-d` | Run as daemon (background process) |
| `--log-level <level>` | `-l` | Log level (fatal/error/warn/info/debug/trace) |

## Examples

### Start with default settings

```bash
ccr start
```

### Start on custom port

```bash
ccr start --port 3000
```

### Start with custom config

```bash
ccr start --config /path/to/config.json
```

### Start as daemon

```bash
ccr start --daemon
```

### Start with debug logging

```bash
ccr start --log-level debug
```

## Environment Variables

You can also configure the server using environment variables:

| Variable | Description |
|----------|-------------|
| `PORT` | Port to listen on |
| `CONFIG_PATH` | Path to configuration file |
| `LOG_LEVEL` | Logging level |
| `CUSTOM_ROUTER_PATH` | Path to custom router function |
| `HOST` | Host to bind to (default: 0.0.0.0) |

## Output

When started successfully, you'll see:

```
Claude Code Router is running on http://localhost:8080
API endpoint: http://localhost:8080/v1
```

## Related Commands

- [ccr stop](/docs/cli/other-commands#ccr-stop) - Stop the server
- [ccr restart](/docs/cli/other-commands#ccr-restart) - Restart the server
- [ccr status](/docs/cli/other-commands#ccr-status) - Check server status


## /docs/docs/cli/commands/status.md

---
sidebar_position: 3
---

# ccr status

Show the current status of the Claude Code Router server.

## Usage

```bash
ccr status
```

## Output

### Running Server

When the server is running:

```
Claude Code Router Status: Running
Version: 2.0.0
PID: 12345
Port: 8080
Uptime: 2h 34m
Configuration: /home/user/.claude-code-router/config.json
```

### Stopped Server

When the server is not running:

```
Claude Code Router Status: Stopped
```

## Exit Codes

| Code | Description |
|------|-------------|
| 0 | Server is running |
| 1 | Server is stopped |
| 2 | Error checking status |

## Examples

```bash
$ ccr status

Claude Code Router Status: Running
Version: 2.0.0
PID: 12345
Port: 8080
Uptime: 2h 34m
```

## Related Commands

- [ccr start](/docs/cli/start) - Start the server
- [ccr stop](/docs/cli/other-commands#ccr-stop) - Stop the server
- [ccr restart](/docs/cli/other-commands#ccr-restart) - Restart the server


## /docs/docs/cli/commands/statusline.md

---
sidebar_position: 5
---

# ccr statusline

Display a customizable status bar showing real-time information about your Claude Code session, including workspace, Git branch, model, token usage, and more.

## Overview

The `ccr statusline` command reads JSON data from stdin and renders a beautifully formatted status bar in your terminal. It's designed to integrate with Claude Code's hook system to display real-time session information.

## Usage

### Basic Usage

```bash
ccr statusline
```

The command expects JSON data via stdin, typically piped from a Claude Code hook:

```bash
echo '{"hook_event_name":"...","session_id":"...","..."}' | ccr statusline
```

### Hook Integration

Configure in your Claude Code settings:

```json
{
  "hooks": {
    "postResponse": {
      "command": "ccr statusline",
      "input": "json"
    }
  }
}
```

## Available Themes

### Default Theme

A clean, minimal theme with Nerd Font icons and colored text:

```
 󰉋 my-project   main  󰚩 claude-3-5-sonnet-20241022  ↑ 12.3k  ↓ 5.2k
```

### Powerline Theme

A vim-powerline inspired style with colored backgrounds and arrow separators:

```
 󰉋 my-project   main  󰚩 claude-3-5-sonnet-20241022  ↑ 12.3k  ↓ 5.2k
```

Activate by setting `currentStyle: "powerline"` in your config.

### Simple Theme

Fallback theme without icons for terminals that don't support Nerd Fonts:

```
my-project  main  claude-3-5-sonnet-20241022  ↑ 12.3k  ↓ 5.2k
```

Automatically used when `USE_SIMPLE_ICONS=true` or on unsupported terminals.

## Available Modules

Status line modules display different types of information:

| Module | Description | Variables |
|--------|-------------|-----------|
| **workDir** | Current working directory name | `{{workDirName}}` |
| **gitBranch** | Current Git branch | `{{gitBranch}}` |
| **model** | Model being used | `{{model}}` |
| **usage** | Token usage (input/output) | `{{inputTokens}}`, `{{outputTokens}}` |
| **context** | Context window usage | `{{contextPercent}}`, `{{contextWindowSize}}` |
| **speed** | Token processing speed | `{{tokenSpeed}}`, `{{isStreaming}}` |
| **cost** | API cost | `{{cost}}` |
| **duration** | Session duration | `{{duration}}` |
| **lines** | Code changes | `{{linesAdded}}`, `{{linesRemoved}}` |
| **script** | Custom script output | Dynamic |

## Configuration

Configure statusline in `~/.claude-code-router/config.json`:

### Default Style Example

```json
{
  "StatusLine": {
    "currentStyle": "default",
    "default": {
      "modules": [
        {
          "type": "workDir",
          "icon": "󰉋",
          "text": "{{workDirName}}",
          "color": "bright_blue"
        },
        {
          "type": "gitBranch",
          "icon": "",
          "text": "{{gitBranch}}",
          "color": "bright_magenta"
        },
        {
          "type": "model",
          "icon": "󰚩",
          "text": "{{model}}",
          "color": "bright_cyan"
        },
        {
          "type": "usage",
          "icon": "↑",
          "text": "{{inputTokens}}",
          "color": "bright_green"
        },
        {
          "type": "usage",
          "icon": "↓",
          "text": "{{outputTokens}}",
          "color": "bright_yellow"
        }
      ]
    }
  }
}
```

### Powerline Style Example

```json
{
  "StatusLine": {
    "currentStyle": "powerline",
    "powerline": {
      "modules": [
        {
          "type": "workDir",
          "icon": "󰉋",
          "text": "{{workDirName}}",
          "color": "white",
          "background": "bg_bright_blue"
        },
        {
          "type": "gitBranch",
          "icon": "",
          "text": "{{gitBranch}}",
          "color": "white",
          "background": "bg_bright_magenta"
        }
      ]
    }
  }
}
```

### Full Featured Example

```json
{
  "StatusLine": {
    "currentStyle": "default",
    "default": {
      "modules": [
        {
          "type": "workDir",
          "icon": "󰉋",
          "text": "{{workDirName}}",
          "color": "bright_blue"
        },
        {
          "type": "gitBranch",
          "icon": "",
          "text": "{{gitBranch}}",
          "color": "bright_magenta"
        },
        {
          "type": "model",
          "icon": "󰚩",
          "text": "{{model}}",
          "color": "bright_cyan"
        },
        {
          "type": "context",
          "icon": "🪟",
          "text": "{{contextPercent}}% / {{contextWindowSize}}",
          "color": "bright_green"
        },
        {
          "type": "speed",
          "icon": "⚡",
          "text": "{{tokenSpeed}} t/s {{isStreaming}}",
          "color": "bright_yellow"
        },
        {
          "type": "cost",
          "icon": "💰",
          "text": "{{cost}}",
          "color": "bright_magenta"
        },
        {
          "type": "duration",
          "icon": "⏱️",
          "text": "{{duration}}",
          "color": "bright_white"
        },
        {
          "type": "lines",
          "icon": "📝",
          "text": "+{{linesAdded}}/-{{linesRemoved}}",
          "color": "bright_cyan"
        }
      ]
    }
  }
}
```

## Custom Scripts

You can create custom modules by executing scripts:

```json
{
  "type": "script",
  "icon": "🔧",
  "scriptPath": "/path/to/script.js",
  "options": {
    "customOption": "value"
  }
}
```

Script format (CommonJS):

```javascript
// my-status-module.js
module.exports = function(variables, options) {
  // Access variables like model, gitBranch, etc.
  // Access options from configuration
  return `Custom: ${variables.model}`;
};

// Or async
module.exports = async function(variables, options) {
  const data = await fetchSomeData();
  return data;
};
```

## Color Options

### Standard Colors

- `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`
- `bright_black`, `bright_red`, `bright_green`, `bright_yellow`, `bright_blue`, `bright_magenta`, `bright_cyan`, `bright_white`

### Background Colors

Prefix with `bg_`: `bg_blue`, `bg_bright_red`, etc.

### Hexadecimal Colors

Use 24-bit TrueColor with hex codes:

```json
{
  "color": "#FF5733",
  "background": "bg_#1E90FF"
}
```

## Available Variables

All variables are accessible in module text using `{{variableName}}`:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{workDirName}}` | Current directory name | `my-project` |
| `{{gitBranch}}` | Git branch name | `main` |
| `{{model}}` | Model name | `claude-3-5-sonnet-20241022` |
| `{{inputTokens}}` | Input tokens (formatted) | `12.3k` |
| `{{outputTokens}}` | Output tokens (formatted) | `5.2k` |
| `{{tokenSpeed}}` | Tokens per second | `45` |
| `{{isStreaming}}` | Streaming status | `streaming` or empty |
| `{{contextPercent}}` | Context usage percentage | `45` |
| `{{contextWindowSize}}` | Total context window | `200k` |
| `{{cost}}` | Total cost | `$0.15` |
| `{{duration}}` | Session duration | `2m34s` |
| `{{linesAdded}}` | Lines added | `150` |
| `{{linesRemoved}}` | Lines removed | `25` |
| `{{sessionId}}` | Session ID (first 8 chars) | `a1b2c3d4` |

## Environment Variables

Control behavior with environment variables:

| Variable | Values | Description |
|----------|--------|-------------|
| `USE_SIMPLE_ICONS` | `true`/`false` | Force simple theme without icons |
| `NERD_FONT` | Any value | Auto-detect Nerd Font support |

## Examples

### Minimal Status Line

```json
{
  "StatusLine": {
    "default": {
      "modules": [
        {
          "type": "model",
          "text": "{{model}}"
        },
        {
          "type": "usage",
          "text": "↑{{inputTokens}} ↓{{outputTokens}}"
        }
      ]
    }
  }
}
```

Output: `claude-3-5-sonnet-20241022 ↑12.3k ↓5.2k`

### Developer Productivity Focus

```json
{
  "StatusLine": {
    "default": {
      "modules": [
        {
          "type": "gitBranch",
          "icon": "",
          "text": "{{gitBranch}}",
          "color": "bright_magenta"
        },
        {
          "type": "lines",
          "icon": "📝",
          "text": "+{{linesAdded}}/-{{linesRemoved}}",
          "color": "bright_cyan"
        },
        {
          "type": "duration",
          "icon": "⏱️",
          "text": "{{duration}}",
          "color": "bright_white"
        }
      ]
    }
  }
}
```

Output: ` feature/auth  📝 +150/-25  ⏱️ 2m34s`

## Preset Integration

Statusline themes can be included in presets. When you install a preset with statusline configuration, it will automatically apply when you activate that preset.

See [Presets](/docs/presets/intro) for more information.

## Troubleshooting

### Icons Not Displaying

Set `USE_SIMPLE_ICONS=true` in your environment:

```bash
export USE_SIMPLE_ICONS=true
```

### Colors Not Working

Ensure your terminal supports TrueColor (24-bit color):

```bash
export COLORTERM=truecolor
```

### Git Branch Not Showing

Ensure you're in a Git repository and have the `git` command installed.

## Related Commands

- [ccr status](/docs/cli/commands/status) - Check server status
- [ccr preset](/docs/cli/commands/preset) - Manage presets with statusline themes


## /docs/docs/cli/config/basic.md

---
title: Basic Configuration
---

# Basic Configuration

CLI uses the same configuration file as Server: `~/.claude-code-router/config.json`

## Configuration Methods

You can configure Claude Code Router in two ways:

### Option 1: Edit Configuration File Directly

Edit `~/.claude-code-router/config.json` with your favorite editor:

```bash
nano ~/.claude-code-router/config.json
```

### Option 2: Use Web UI

Open the web interface and configure visually:

```bash
ccr ui
```

## Restart After Configuration Changes

After modifying the configuration file or making changes through the Web UI, you must restart the service:

```bash
ccr restart
```

Or restart directly through the Web UI.

## Configuration File Location

```bash
~/.claude-code-router/config.json
```

## Minimal Configuration Example

```json5
{
  // API key (optional, used to protect service)
  "APIKEY": "your-api-key-here",

  // LLM providers
  "Providers": [
    {
      "name": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "$OPENAI_API_KEY",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    }
  ],

  // Default routing
  "Router": {
    "default": "openai,gpt-4"
  }
}
```

## Environment Variables

Configuration supports environment variable interpolation:

```json5
{
  "Providers": [
    {
      "apiKey": "$OPENAI_API_KEY"  // Read from environment variable
    }
  ]
}
```

Set in `.bashrc` or `.zshrc`:

```bash
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
```

## Common Configuration Options

### HOST and PORT

```json5
{
  "HOST": "127.0.0.1",  // Listen address
  "PORT": 3456          // Listen port
}
```

### Logging Configuration

```json5
{
  "LOG": true,          // Enable logging
  "LOG_LEVEL": "info"   // Log level
}
```

### Routing Configuration

```json5
{
  "Router": {
    "default": "openai,gpt-4",
    "background": "openai,gpt-3.5-turbo",
    "think": "openai,gpt-4",
    "longContext": "anthropic,claude-3-opus"
  }
}
```

## Configuration Validation

Configuration file is automatically validated. Common errors:

- **Missing Providers**: Must configure at least one provider
- **Missing API Key**: If Providers are configured, must provide API Key
- **Model doesn't exist**: Ensure model is in provider's models list

## Configuration Backup

Configuration is automatically backed up on each update:

```
~/.claude-code-router/config.backup.{timestamp}.json
```

## Apply Configuration Changes

After modifying the configuration file or making changes through the Web UI, restart the service:

```bash
ccr restart
```

Or restart directly through the Web UI by clicking the "Save and Restart" button.

## View Current Configuration

```bash
# View via API
curl http://localhost:3456/api/config

# Or view configuration file
cat ~/.claude-code-router/config.json
```

## Example Configurations

### OpenAI

```json5
{
  "Providers": [
    {
      "name": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "$OPENAI_API_KEY",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    }
  ],
  "Router": {
    "default": "openai,gpt-4"
  }
}
```

### Anthropic

```json5
{
  "Providers": [
    {
      "name": "anthropic",
      "baseUrl": "https://api.anthropic.com/v1",
      "apiKey": "$ANTHROPIC_API_KEY",
      "models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
    }
  ],
  "Router": {
    "default": "anthropic,claude-3-5-sonnet-20241022"
  }
}
```

### Multiple Providers

```json5
{
  "Providers": [
    {
      "name": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "$OPENAI_API_KEY",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    },
    {
      "name": "anthropic",
      "baseUrl": "https://api.anthropic.com/v1",
      "apiKey": "$ANTHROPIC_API_KEY",
      "models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
    }
  ],
  "Router": {
    "default": "openai,gpt-4",
    "think": "anthropic,claude-3-5-sonnet-20241022",
    "background": "openai,gpt-3.5-turbo"
  }
}
```


## /docs/docs/cli/config/project-level.md

---
title: Project-Level Configuration
---

# Project-Level Configuration

In addition to global configuration, `ccr` also supports setting different routing rules for specific projects.

## Project Configuration File

Project configuration file is located at:

```
~/.claude/projects/<project-id>/claude-code-router.json
```

Where `<project-id>` is the unique identifier of the Claude Code project.

## Project Configuration Structure

```json5
{
  "Router": {
    "default": "openai,gpt-4",
    "background": "openai,gpt-3.5-turbo"
  }
}
```

## Finding Project ID

### Method 1: Using CLI

```bash
# Run in project directory
ccr status
```

Output will show current project ID:

```
Project: my-project (abc123def456)
```

### Method 2: Check Claude Code Configuration

```bash
cat ~/.claude.json
```

Find your project ID:

```json
{
  "projects": {
    "abc123def456": {
      "path": "/path/to/your/project",
      "name": "my-project"
    }
  }
}
```

## Creating Project Configuration

### Manual Creation

```bash
# Create project configuration directory
mkdir -p ~/.claude/projects/abc123def456

# Create configuration file
cat > ~/.claude/projects/abc123def456/claude-code-router.json << 'EOF'
{
  "Router": {
    "default": "anthropic,claude-3-5-sonnet-20241022",
    "background": "openai,gpt-3.5-turbo"
  }
}
EOF
```

### Using ccr model Command

```bash
# Run in project directory
cd /path/to/your/project
ccr model --project
```

## Configuration Priority

Routing configuration priority (from high to low):

1. **Custom routing function** (`CUSTOM_ROUTER_PATH`)
2. **Project-level configuration** (`~/.claude/projects/<id>/claude-code-router.json`)
3. **Global configuration** (`~/.claude-code-router/config.json`)
4. **Built-in routing rules**

## Use Cases

### Scenario 1: Different Projects Use Different Models

```json5
// Web project uses GPT-4
~/.claude/projects/web-project-id/claude-code-router.json:
{
  "Router": {
    "default": "openai,gpt-4"
  }
}

// AI project uses Claude
~/.claude/projects/ai-project-id/claude-code-router.json:
{
  "Router": {
    "default": "anthropic,claude-3-5-sonnet-20241022"
  }
}
```

### Scenario 2: Test Projects Use Low-Cost Models

```json5
~/.claude/projects/test-project-id/claude-code-router.json:
{
  "Router": {
    "default": "openai,gpt-3.5-turbo",
    "background": "openai,gpt-3.5-turbo"
  }
}
```

### Scenario 3: Long Context Projects

```json5
~/.claude/projects/long-context-project-id/claude-code-router.json:
{
  "Router": {
    "default": "anthropic,claude-3-opus-20240229",
    "longContext": "anthropic,claude-3-opus-20240229"
  }
}
```

## Verify Project Configuration

```bash
# View routing used by current project
ccr status

# Check logs to confirm routing decisions
tail -f ~/.claude-code-router/claude-code-router.log
```

## Delete Project Configuration

```bash
rm ~/.claude/projects/<project-id>/claude-code-router.json
```

After deletion, falls back to global configuration.

## Complete Example

Assume you have two projects:

### Global Configuration (`~/.claude-code-router/config.json`)

```json5
{
  "Providers": [
    {
      "name": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "$OPENAI_API_KEY",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    },
    {
      "name": "anthropic",
      "baseUrl": "https://api.anthropic.com/v1",
      "apiKey": "$ANTHROPIC_API_KEY",
      "models": ["claude-3-5-sonnet-20241022"]
    }
  ],
  "Router": {
    "default": "openai,gpt-4",
    "background": "openai,gpt-3.5-turbo"
  }
}
```

### Web Project Configuration

```json5
{
  "Router": {
    "default": "openai,gpt-4"
  }
}
```

### AI Project Configuration

```json5
{
  "Router": {
    "default": "anthropic,claude-3-5-sonnet-20241022",
    "think": "anthropic,claude-3-5-sonnet-20241022"
  }
}
```

This way:
- Web project uses GPT-4
- AI project uses Claude
- All projects' background tasks use GPT-3.5-turbo (inherit global configuration)


## /docs/docs/cli/installation.md

---
sidebar_position: 2
---

# Installation

Install Claude Code Router globally using your preferred package manager.

## Prerequisites

- **Node.js**: >= 18.0.0
- **pnpm**: >= 8.0.0 (if using pnpm)
- An API key from your preferred LLM provider

## Install via npm

```bash
npm install -g @musistudio/claude-code-router
```

## Install via pnpm

```bash
pnpm add -g @musistudio/claude-code-router
```

## Install via Yarn

```bash
yarn global add @musistudio/claude-code-router
```

## Verify Installation

After installation, verify that `ccr` is available:

```bash
ccr --version
```

You should see the version number displayed.

## Next Steps

Once installed, proceed to [Quick Start](/docs/quick-start) to configure and start using the router.


## /docs/docs/cli/intro.md

---
title: CLI Introduction
---

# CLI Introduction

Claude Code Router CLI (`ccr`) is a command-line tool for managing and controlling the Claude Code Router service.

## Feature Overview

`ccr` provides the following functionality:

- **Service Management**: Start, stop, restart service
- **Configuration Management**: Interactive model selection configuration
- **Status Viewing**: View service running status
- **Code Execution**: Directly execute `claude` command
- **Environment Integration**: Output environment variables for shell integration
- **Web UI**: Open Web management interface
- **Status Bar**: Display customizable session status with `ccr statusline`

## Installation

```bash
npm install -g @musistudio/claude-code-router
```

## Basic Usage

### Configuration

Before using Claude Code Router, you need to configure your providers. You can either:

1. **Edit configuration file directly**: Edit `~/.claude-code-router/config.json` manually
2. **Use Web UI**: Run `ccr ui` to open the web interface and configure visually

After making configuration changes, restart the service:

```bash
ccr restart
```

Or restart directly through the Web UI.

### Start Claude Code

Once configured, you can start Claude Code with:

```bash
ccr code
```

This will launch Claude Code and route your requests through the configured provider.

### Service Management

```bash
ccr start    # Start the router service
ccr status   # View service status
ccr stop     # Stop the router service
ccr restart  # Restart the router service
```

### Web UI

```bash
ccr ui       # Open Web management interface
```

## Configuration File

`ccr` uses the configuration file at `~/.claude-code-router/config.json`

Configure once, and both CLI and Server will use it.

## Next Steps

- [Installation Guide](/docs/cli/installation) - Detailed installation instructions
- [Quick Start](/docs/cli/quick-start) - Get started in 5 minutes
- [Command Reference](/docs/category/cli-commands) - Complete command list
- [Status Line](/docs/cli/commands/statusline) - Customize your status bar
- [Configuration Guide](/docs/category/cli-config) - Configuration file details


## /docs/docs/cli/quick-start.md

---
sidebar_position: 3
---

# Quick Start

Get up and running with Claude Code Router in 5 minutes.

## 1. Configure the Router

Before using Claude Code Router, you need to configure your LLM providers. You can either:

### Option A: Edit Configuration File Directly

Edit `~/.claude-code-router/config.json`:

```json
{
  "HOST": "0.0.0.0",
  "PORT": 8080,
  "Providers": [
    {
      "name": "openai",
      "api_base_url": "https://api.openai.com/v1/chat/completions",
      "api_key": "your-api-key-here",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    }
  ],
  "Router": {
    "default": "openai,gpt-4"
  }
}
```

### Option B: Use Web UI

```bash
ccr ui
```

This will open the web interface where you can configure providers visually.

## 2. Start the Router

```bash
ccr start
```

The router will start on `http://localhost:8080` by default.

## 3. Use Claude Code

Now you can use Claude Code normally:

```bash
ccr code
```

Your requests will be routed through Claude Code Router to your configured provider.

## Restart After Configuration Changes

If you modify the configuration file or make changes through the Web UI, restart the service:

```bash
ccr restart
```

Or restart directly through the Web UI.

## What's Next?

- [Basic Configuration](/docs/cli/config/basic) - Learn about configuration options
- [Routing](/docs/cli/config/routing) - Configure smart routing rules
- [CLI Commands](/docs/category/cli-commands) - Explore all CLI commands


## /docs/docs/presets/intro.md

---
sidebar_position: 3
---

# Presets

Use predefined configurations for quick setup.

## What are Presets?

Presets are pre-configured settings that include provider configurations, routing rules, and transformers optimized for specific use cases.

## Using Presets

### CLI Mode (Command Line)

CLI mode is suitable for developers who prefer command-line operations.

#### Installing Presets

**Install from local directory:**

```bash
ccr preset install /path/to/preset-directory
```

**Reconfigure an installed preset:**

```bash
ccr preset install my-preset
```

#### Using Presets

After installing a preset, you can use the preset name to start Claude Code:

```bash
# Start with a specific preset
ccr my-preset "your prompt"
```

The preset will:
- Automatically load pre-configured Providers
- Apply preset routing rules
- Use transformers configured in the preset

#### List All Presets

```bash
ccr preset list
```

This will display all installed presets with their names, versions, and descriptions.

#### View Preset Information

```bash
ccr preset info my-preset
```

#### Delete Preset

```bash
ccr preset delete my-preset
```

### Web UI Mode

Web UI provides a more friendly visual interface with additional installation methods.

#### Access Web UI

```bash
ccr ui
```

Then open `http://localhost:3000` in your browser.

#### Install from GitHub Repository

1. Click the "Preset Market" button
2. Select the preset you want to install from the list
3. Click the "Install" button

#### Reconfigure Preset

1. Click the "View Details" icon next to the preset
2. Modify configuration items in the detail page
3. Click "Apply" to save configuration

#### Manage Presets

- **View**: Click the info icon on the right side of the preset
- **Delete**: Click the delete icon on the right side of the preset

## Creating Custom Presets

### Preset Directory Structure

Presets are stored as directories with the following structure:

```
~/.claude-code-router/presets/<preset-name>/
├── manifest.json           # Required: Preset configuration file
├── transformers/           # Optional: Custom transformers
│   └── custom-transformer.js
├── scripts/               # Optional: Custom scripts
│   └── status.js
└── README.md              # Optional: Documentation
```

### Dynamic Configuration System

CCR introduces a powerful dynamic configuration system that supports:

- **Multiple Input Types**: Selectors, multi-select, confirm boxes, text input, number input, etc.
- **Conditional Logic**: Dynamically show/hide configuration fields based on user input
- **Variable References**: Configuration fields can reference each other
- **Dynamic Options**: Option lists can be dynamically generated from preset configuration or user input

#### Schema Field Types

| Type | Description | Example |
|------|-------------|---------|
| `password` | Password input (hidden) | API Key |
| `input` | Single-line text input | Base URL |
| `number` | Number input | Max tokens |
| `select` | Single-select dropdown | Choose Provider |
| `multiselect` | Multi-select | Enable features |
| `confirm` | Confirmation box | Use proxy |
| `editor` | Multi-line text editor | Custom config |

#### Condition Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `eq` | Equals | `{"field": "provider", "operator": "eq", "value": "openai"}` |
| `ne` | Not equals | `{"field": "advanced", "operator": "ne", "value": true}` |
| `in` | In (array) | `{"field": "feature", "operator": "in", "value": ["a", "b"]}` |
| `nin` | Not in (array) | `{"field": "type", "operator": "nin", "value": ["x", "y"]}` |
| `exists` | Field exists | `{"field": "apiKey", "operator": "exists"}` |
| `gt/lt/gte/lte` | Greater/less than (or equal) | For number comparisons |

#### Dynamic Options Types

##### static - Static Options
```json
"options": {
  "type": "static",
  "options": [
    {"label": "Option 1", "value": "value1"},
    {"label": "Option 2", "value": "value2"}
  ]
}
```

##### providers - Extract from Providers Configuration
```json
"options": {
  "type": "providers"
}
```
Automatically extracts names from the `Providers` array as options.

##### models - Extract from Specified Provider's Models
```json
"options": {
  "type": "models",
  "providerField": "{{selectedProvider}}"
}
```
Dynamically displays models based on the user-selected provider.

#### Template Variables

Use `{{variableName}}` syntax to reference user input in the template:

```json
"template": {
  "Providers": [
    {
      "name": "{{providerName}}",
      "api_key": "{{apiKey}}"
    }
  ]
}
```

#### Configuration Mappings

For complex configuration needs, use `configMappings` to precisely control value placement:

```json
"configMappings": [
  {
    "target": "Providers[0].api_key",
    "value": "{{apiKey}}"
  },
  {
    "target": "PROXY_URL",
    "value": "{{proxyUrl}}",
    "when": {
      "field": "useProxy",
      "operator": "eq",
      "value": true
    }
  }
]
```

#### Complete Example

```json
{
  "name": "multi-provider-example",
  "version": "1.0.0",
  "description": "Multi-provider configuration example - Switch between OpenAI and DeepSeek",
  "author": "CCR Team",
  "keywords": ["openai", "deepseek", "multi-provider"],
  "ccrVersion": "2.0.0",
  "schema": [
    {
      "id": "primaryProvider",
      "type": "select",
      "label": "Primary Provider",
      "prompt": "Select your primary LLM provider",
      "options": {
        "type": "static",
        "options": [
          {
            "label": "OpenAI",
            "value": "openai",
            "description": "Use OpenAI's GPT models"
          },
          {
            "label": "DeepSeek",
            "value": "deepseek",
            "description": "Use DeepSeek's cost-effective models"
          }
        ]
      },
      "required": true,
      "defaultValue": "openai"
    },
    {
      "id": "apiKey",
      "type": "password",
      "label": "API Key",
      "prompt": "Enter your API Key",
      "placeholder": "sk-...",
      "required": true
    },
    {
      "id": "defaultModel",
      "type": "select",
      "label": "Default Model",
      "prompt": "Select the default model to use",
      "options": {
        "type": "static",
        "options": [
          {"label": "GPT-4o", "value": "gpt-4o"},
          {"label": "GPT-4o-mini", "value": "gpt-4o-mini"}
        ]
      },
      "required": true,
      "defaultValue": "gpt-4o",
      "when": {
        "field": "primaryProvider",
        "operator": "eq",
        "value": "openai"
      }
    },
    {
      "id": "enableProxy",
      "type": "confirm",
      "label": "Enable Proxy",
      "prompt": "Access API through a proxy?",
      "defaultValue": false
    },
    {
      "id": "proxyUrl",
      "type": "input",
      "label": "Proxy URL",
      "prompt": "Enter proxy server address",
      "placeholder": "http://127.0.0.1:7890",
      "required": true,
      "when": {
        "field": "enableProxy",
        "operator": "eq",
        "value": true
      }
    }
  ],
  "template": {
    "Providers": [
      {
        "name": "{{primaryProvider}}",
        "api_base_url": "https://api.openai.com/v1/chat/completions",
        "api_key": "{{apiKey}}",
        "models": ["{{defaultModel}}"]
      }
    ],
    "Router": {
      "default": "{{primaryProvider}},{{defaultModel}}"
    },
    "PROXY_URL": "{{proxyUrl}}"
  },
  "configMappings": [
    {
      "target": "PROXY_URL",
      "value": "{{proxyUrl}}",
      "when": {
        "field": "enableProxy",
        "operator": "eq",
        "value": true
      }
    }
  ]
}
```

### manifest.json Complete Field Reference

`manifest.json` is the core configuration file of a preset, using JSON5 format (comments supported).

#### 1. Metadata Fields

These fields describe basic information about the preset:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | ✓ | Preset name (unique identifier) |
| `version` | string | ✓ | Version number (follows semver) |
| `description` | string | - | Preset description |
| `author` | string | - | Author information |
| `homepage` | string | - | Project homepage URL |
| `repository` | string | - | Source repository URL |
| `license` | string | - | License type |
| `keywords` | string[] | - | Keyword tags |
| `ccrVersion` | string | - | Compatible CCR version |

Example:

```json
{
  "name": "my-preset",
  "version": "1.0.0",
  "description": "My custom preset",
  "author": "Your Name",
  "homepage": "https://github.com/yourname/ccr-presets",
  "repository": "https://github.com/yourname/ccr-presets.git",
  "license": "MIT",
  "keywords": ["openai", "production"],
  "ccrVersion": "2.0.0"
}
```

#### 2. Configuration Fields

These fields are directly merged into CCR's configuration. All fields supported in `config.json` can be used here:

| Field | Type | Description |
|-------|------|-------------|
| `Providers` | array | Provider configuration array |
| `Router` | object | Routing configuration |
| `transformers` | array | Transformer configuration |
| `StatusLine` | object | Status bar configuration |
| `NON_INTERACTIVE_MODE` | boolean | Enable non-interactive mode (for CI/CD) |

**CLI-Only Fields** (these fields only work in CLI mode and are not used by the server):

| Field | Type | Description |
|-------|------|-------------|
| `noServer` | boolean | Skip local server startup and use provider's API directly |
| `claudeCodeSettings` | object | Claude Code specific settings (env, statusLine, etc.) |

Example:

```json
{
  "Providers": [
    {
      "name": "openai",
      "api_base_url": "https://api.openai.com/v1/chat/completions",
      "api_key": "${OPENAI_API_KEY}",
      "models": ["gpt-4o", "gpt-4o-mini"]
    }
  ],
  "Router": {
    "default": "openai,gpt-4o",
    "background": "openai,gpt-4o-mini"
  },
  "PORT": 8080
}
```

#### 3. Dynamic Configuration System Fields

These fields are used to create interactive configuration templates:

| Field | Type | Description |
|-------|------|-------------|
| `schema` | array | Configuration input form definition |
| `template` | object | Configuration template (with variable references) |
| `configMappings` | array | Configuration mapping rules |
| `userValues` | object | User-filled values (used at runtime) |

**Schema Field Types:**

| Type | Description | Use Case |
|------|-------------|----------|
| `password` | Password input (hidden) | API Key |
| `input` | Single-line text input | URL |
| `number` | Number input | Port number |
| `select` | Single-select dropdown | Select Provider |
| `multiselect` | Multi-select | Enable features |
| `confirm` | Confirmation box | Enable/disable |
| `editor` | Multi-line text editor | Custom config |

Dynamic configuration example:

```json
{
  "schema": [
    {
      "id": "apiKey",
      "type": "password",
      "label": "API Key",
      "prompt": "Enter your API Key",
      "required": true
    },
    {
      "id": "provider",
      "type": "select",
      "label": "Provider",
      "options": {
        "type": "static",
        "options": [
          {"label": "OpenAI", "value": "openai"},
          {"label": "DeepSeek", "value": "deepseek"}
        ]
      },
      "defaultValue": "openai"
    }
  ],
  "template": {
    "Providers": [
      {
        "name": "#{provider}",
        "api_key": "#{apiKey}"
      }
    ]
  }
}
```

### Creating Preset Examples

#### Example 1: Simple Preset (No Dynamic Configuration)

```bash
# Create preset directory
mkdir -p ~/.claude-code-router/presets/simple-openai

# Create manifest.json
cat > ~/.claude-code-router/presets/simple-openai/manifest.json << 'EOF'
{
  "name": "simple-openai",
  "version": "1.0.0",
  "description": "Simple OpenAI configuration",
  "author": "Your Name",

  "Providers": [
    {
      "name": "openai",
      "api_base_url": "https://api.openai.com/v1/chat/completions",
      "api_key": "${OPENAI_API_KEY}",
      "models": ["gpt-4o", "gpt-4o-mini"]
    }
  ],

  "Router": {
    "default": "openai,gpt-4o",
    "background": "openai,gpt-4o-mini"
  }
}
EOF

# Configure preset (input API Key)
ccr preset install simple-openai

# Use preset
ccr simple-openai "your prompt"
```

#### Example 2: Advanced Preset (Dynamic Configuration)

```bash
# Create preset directory
mkdir -p ~/.claude-code-router/presets/advanced-config

# Create manifest.json
cat > ~/.claude-code-router/presets/advanced-config/manifest.json << 'EOF'
{
  "name": "advanced-config",
  "version": "1.0.0",
  "description": "Advanced configuration with multi-provider support",
  "author": "Your Name",
  "keywords": ["openai", "deepseek", "multi-provider"],

  "schema": [
    {
      "id": "provider",
      "type": "select",
      "label": "Select Provider",
      "prompt": "Choose your primary LLM provider",
      "options": {
        "type": "static",
        "options": [
          {
            "label": "OpenAI",
            "value": "openai",
            "description": "Use OpenAI's GPT models"
          },
          {
            "label": "DeepSeek",
            "value": "deepseek",
            "description": "Use DeepSeek's cost-effective models"
          }
        ]
      },
      "defaultValue": "openai",
      "required": true
    },
    {
      "id": "apiKey",
      "type": "password",
      "label": "API Key",
      "prompt": "Enter your API Key",
      "placeholder": "sk-...",
      "required": true
    },
    {
      "id": "enableProxy",
      "type": "confirm",
      "label": "Enable Proxy",
      "prompt": "Access API through a proxy?",
      "defaultValue": false
    },
    {
      "id": "proxyUrl",
      "type": "input",
      "label": "Proxy URL",
      "prompt": "Enter proxy server address",
      "placeholder": "http://127.0.0.1:7890",
      "required": true,
      "when": {
        "field": "enableProxy",
        "operator": "eq",
        "value": true
      }
    }
  ],

  "template": {
    "Providers": [
      {
        "name": "#{provider}",
        "api_base_url": "#{provider === 'openai' ? 'https://api.openai.com/v1/chat/completions' : 'https://api.deepseek.com/v1/chat/completions'}",
        "api_key": "#{apiKey}",
        "models": ["gpt-4o", "gpt-4o-mini"]
      }
    ],
    "Router": {
      "default": "#{provider},gpt-4o",
      "background": "#{provider},gpt-4o-mini"
    }
  },

  "configMappings": [
    {
      "target": "PROXY_URL",
      "value": "#{proxyUrl}",
      "when": {
        "field": "enableProxy",
        "operator": "eq",
        "value": true
      }
    }
  ]
}
EOF

# Configure preset (will prompt for input)
ccr preset install advanced-config

# Use preset
ccr advanced-config "your prompt"
```

### Export Current Configuration as Preset

If you have already configured CCR, you can export the current configuration:

```bash
# Export current configuration
ccr preset export my-exported-preset
```

Export will automatically:
- Identify sensitive fields (like `api_key`) and replace with environment variable placeholders
- Generate `schema` for collecting user input
- Generate `template` and `configMappings`

Optional flags:

```bash
ccr preset export my-exported-preset \
  --description "Exported configuration" \
  --author "Your Name" \
  --tags "production,openai"
```

## Preset File Location

Presets are stored in:

```
~/.claude-code-router/presets/
```

Each preset is a directory containing a `manifest.json` file.

## Best Practices

1. **Use Dynamic Configuration**: Use the schema system for configuration items that require user input
2. **Provide Defaults**: Set reasonable defaults for optional fields
3. **Conditional Display**: Use `when` conditions to avoid unnecessary inputs
4. **Clear Labels**: Provide clear `label` and `prompt` for each field
5. **Validate Input**: Use `validator` to ensure input validity
6. **Version Control**: Keep commonly used presets in version control
7. **Document**: Add descriptions and version info for custom presets

## Next Steps

- [CLI Reference](/docs/cli/start) - Complete CLI command reference
- [Configuration](/docs/config/basic) - Detailed configuration guide


## /docs/docs/server/advanced/custom-router.md

---
sidebar_position: 1
---

# Custom Router

Write your own routing logic in JavaScript.

## Creating a Custom Router

Create a JavaScript file that exports a routing function:

```javascript
// custom-router.js
module.exports = function(config, context) {
  const { scenario, projectId, tokenCount, request } = context;

  // Your custom logic here
  if (scenario === 'background') {
    return 'groq,llama-3.3-70b-versatile';
  }

  if (tokenCount > 100000) {
    return 'gemini,gemini-1.5-pro';
  }

  // Check request content
  if (request && request.system && request.system.includes('code')) {
    return 'deepseek,deepseek-coder';
  }

  // Default
  return 'deepseek,deepseek-chat';
};
```

## Context Object

The router function receives a context object with:

| Field | Type | Description |
|-------|------|-------------|
| `scenario` | string | Detected scenario (background, think, webSearch, image, etc.) |
| `projectId` | string | Project ID from Claude Code |
| `tokenCount` | number | Estimated token count of the request |
| `request` | object | Full request object |

## Configuration

Set the environment variable to use your custom router:

```bash
export CUSTOM_ROUTER_PATH="/path/to/custom-router.js"
```

Or set it in your shell configuration:

```bash
# ~/.bashrc or ~/.zshrc
export CUSTOM_ROUTER_PATH="/path/to/custom-router.js"
```

## Return Format

The router function should return a string in the format:

```
{provider-name},{model-name}
```

Example:

```
deepseek,deepseek-chat
```

## Error Handling

If your router function throws an error or returns an invalid format, the router will fall back to the default routing configuration.

## Example: Time-Based Routing

```javascript
module.exports = function(config, context) {
  const hour = new Date().getHours();

  // Use faster models during work hours
  if (hour >= 9 && hour <= 18) {
    return 'groq,llama-3.3-70b-versatile';
  }

  // Use more capable models outside work hours
  return 'deepseek,deepseek-chat';
};
```

## Example: Cost Optimization

```javascript
module.exports = function(config, context) {
  const { tokenCount } = context;

  // Use cheaper models for large requests
  if (tokenCount > 50000) {
    return 'groq,llama-3.3-70b-versatile';
  }

  // Use default for smaller requests
  return 'deepseek,deepseek-chat';
};
```

## Testing Your Router

Test your custom router by checking the logs:

```bash
tail -f ~/.claude-code-router/claude-code-router.log
```

Look for routing decisions to see which model is being selected.

## Next Steps

- [Agents](/docs/advanced/agents) - Extend functionality with agents
- [Presets](/docs/advanced/presets) - Use predefined configurations


## /docs/docs/server/api/config-api.md

---
title: Configuration API
---

# Configuration API

## GET /api/config

Get current server configuration.

### Request Example

```bash
curl http://localhost:3456/api/config \
  -H "x-api-key: your-api-key"
```

### Response Example

```json
{
  "HOST": "0.0.0.0",
  "PORT": 3456,
  "APIKEY": "sk-xxxxx",
  "Providers": [
    {
      "name": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "sk-...",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    }
  ],
  "Router": {
    "default": "openai,gpt-4"
  },
  "transformers": [
    "anthropic"
  ]
}
```

## POST /api/config

Update server configuration. Old configuration is automatically backed up before updating.

### Request Example

```bash
curl -X POST http://localhost:3456/api/config \
  -H "x-api-key: your-api-key" \
  -H "content-type: application/json" \
  -d '{
    "HOST": "0.0.0.0",
    "PORT": 3456,
    "Providers": [
      {
        "name": "openai",
        "baseUrl": "https://api.openai.com/v1",
        "apiKey": "$OPENAI_API_KEY",
        "models": ["gpt-4"]
      }
    ],
    "Router": {
      "default": "openai,gpt-4"
    }
  }'
```

### Configuration Object Structure

#### Basic Configuration

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `HOST` | string | No | Listen address (default 127.0.0.1) |
| `PORT` | integer | No | Listen port (default 3456) |
| `APIKEY` | string | No | API key |
| `LOG` | boolean | No | Enable logging (default true) |
| `LOG_LEVEL` | string | No | Log level (debug/info/warn/error) |

#### Providers Configuration

```json
{
  "Providers": [
    {
      "name": "provider-name",
      "baseUrl": "https://api.example.com/v1",
      "apiKey": "your-api-key",
      "models": ["model-1", "model-2"]
    }
  ]
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Provider name |
| `baseUrl` | string | Yes | API base URL |
| `apiKey` | string | Yes | API key |
| `models` | array | Yes | List of supported models |

#### Router Configuration

```json
{
  "Router": {
    "default": "provider,model",
    "longContextThreshold": 100000,
    "routes": {
      "background": "lightweight-model",
      "think": "powerful-model",
      "longContext": "long-context-model",
      "webSearch": "search-model",
      "image": "vision-model"
    }
  }
}
```

#### Transformers Configuration

```json
{
  "transformers": [
    {
      "name": "anthropic",
      "provider": "provider-name",
      "models": ["model-1"],
      "options": {}
    }
  ]
}
```

### Response Example

Success:

```json
{
  "success": true,
  "message": "Config saved successfully"
}
```

### Configuration Backup

Every time configuration is updated, old configuration is automatically backed up to:

```
~/.claude-code-router/config.backup.{timestamp}.json
```

Keeps the last 3 backups.

## GET /api/transformers

Get list of all transformers loaded by the server.

### Request Example

```bash
curl http://localhost:3456/api/transformers \
  -H "x-api-key: your-api-key"
```

### Response Example

```json
{
  "transformers": [
    {
      "name": "anthropic",
      "endpoint": null
    },
    {
      "name": "openai",
      "endpoint": null
    },
    {
      "name": "gemini",
      "endpoint": "https://generativelanguage.googleapis.com"
    }
  ]
}
```

### Transformer List

Built-in transformers:

- `anthropic` - Anthropic Claude format
- `openai` - OpenAI format
- `deepseek` - DeepSeek format
- `gemini` - Google Gemini format
- `openrouter` - OpenRouter format
- `groq` - Groq format
- `maxtoken` - Adjust max_tokens parameter
- `tooluse` - Tool use conversion
- `reasoning` - Reasoning mode conversion
- `enhancetool` - Enhance tool functionality

## Environment Variable Interpolation

Configuration supports environment variable interpolation:

```json
{
  "Providers": [
    {
      "apiKey": "$OPENAI_API_KEY"
    }
  ]
}
```

Or use `${VAR_NAME}` format:

```json
{
  "baseUrl": "${API_BASE_URL}"
}
```


## /docs/docs/server/api/logs-api.md

---
title: Logs API
---

# Logs API

## GET /api/logs/files

Get list of all available log files.

### Request Example

```bash
curl http://localhost:3456/api/logs/files \
  -H "x-api-key: your-api-key"
```

### Response Example

```json
[
  {
    "name": "ccr-20241226143022.log",
    "path": "/home/user/.claude-code-router/logs/ccr-20241226143022.log",
    "size": 1024000,
    "lastModified": "2024-12-26T14:30:22.000Z"
  },
  {
    "name": "ccr-20241226143021.log",
    "path": "/home/user/.claude-code-router/logs/ccr-20241226143021.log",
    "size": 980000,
    "lastModified": "2024-12-26T14:30:21.000Z"
  }
]
```

### Field Description

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | File name |
| `path` | string | Complete file path |
| `size` | integer | File size (bytes) |
| `lastModified` | string | Last modification time (ISO 8601) |

Files are sorted by modification time in descending order.

## GET /api/logs

Get content of specified log file.

### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file` | string | No | Log file path (default uses app.log) |

### Request Example (Get Default Log)

```bash
curl "http://localhost:3456/api/logs" \
  -H "x-api-key: your-api-key"
```

### Request Example (Get Specific File)

```bash
curl "http://localhost:3456/api/logs?file=/home/user/.claude-code-router/logs/ccr-20241226143022.log" \
  -H "x-api-key: your-api-key"
```

### Response Example

```json
[
  "{\"level\":30,\"time\":1703550622000,\"pid\":12345,\"hostname\":\"server\",\"msg\":\"Incoming request\",\"req\":{\"id\":1,\"method\":\"POST\",\"url\":\"/v1/messages\",\"remoteAddress\":\"127.0.0.1\"}}",
  "{\"level\":30,\"time\":1703550622500,\"pid\":12345,\"hostname\":\"server\",\"msg\":\"Request completed\",\"res\":{\"statusCode\":200,\"responseTime\":500}}",
  "..."
]
```

Returns an array of log lines, each line is a JSON string.

### Log Format

Logs use Pino format:

```json
{
  "level": 30,
  "time": 1703550622000,
  "pid": 12345,
  "hostname": "server",
  "msg": "Incoming request",
  "req": {
    "id": 1,
    "method": "POST",
    "url": "/v1/messages",
    "remoteAddress": "127.0.0.1"
  }
}
```

### Log Levels

| Level | Value | Description |
|-------|-------|-------------|
| `trace` | 10 | Most verbose logs |
| `debug` | 20 | Debug information |
| `info` | 30 | General information |
| `warn` | 40 | Warning information |
| `error` | 50 | Error information |
| `fatal` | 60 | Fatal error |

## DELETE /api/logs

Clear content of specified log file.

### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file` | string | No | Log file path (default uses app.log) |

### Request Example (Clear Default Log)

```bash
curl -X DELETE "http://localhost:3456/api/logs" \
  -H "x-api-key: your-api-key"
```

### Request Example (Clear Specific File)

```bash
curl -X DELETE "http://localhost:3456/api/logs?file=/home/user/.claude-code-router/logs/ccr-20241226143022.log" \
  -H "x-api-key: your-api-key"
```

### Response Example

```json
{
  "success": true,
  "message": "Logs cleared successfully"
}
```

## Log Locations

### Server Logs

Location: `~/.claude-code-router/logs/`

File naming: `ccr-{YYYYMMDD}{HH}{MM}{SS}.log`

Content: HTTP requests, API calls, server events

### Application Logs

Location: `~/.claude-code-router/claude-code-router.log`

Content: Routing decisions, business logic events

## Log Rotation

Server logs use rotating-file-stream for automatic rotation:

- **maxFiles**: 3 - Keep last 3 log files
- **interval**: 1d - Rotate daily
- **maxSize**: 50M - Maximum 50MB per file


## /docs/docs/server/api/messages-api.md

---
title: Messages API
---

# Messages API

## POST /v1/messages

Send messages to LLM, compatible with Anthropic Claude API format.

### Request Format

```bash
curl -X POST http://localhost:3456/v1/messages \
  -H "x-api-key: your-api-key" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-3-5-sonnet-20241022",
    "max_tokens": 1024,
    "messages": [
      {
        "role": "user",
        "content": "Hello, Claude!"
      }
    ]
  }'
```

### Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `model` | string | Yes | Model name (will be routed to actual provider) |
| `messages` | array | Yes | Array of messages |
| `max_tokens` | integer | Yes | Maximum tokens to generate |
| `system` | string | No | System prompt |
| `tools` | array | No | List of available tools |
| `stream` | boolean | No | Whether to use streaming response (default false) |
| `temperature` | number | No | Temperature parameter (0-1) |

### Message Object Format

```json
{
  "role": "user|assistant",
  "content": "string | array"
}
```

### Response Format (Non-streaming)

```json
{
  "id": "msg_xxx",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "Hello! How can I help you today?"
    }
  ],
  "model": "claude-3-5-sonnet-20241022",
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 10,
    "output_tokens": 20
  }
}
```

### Streaming Response

Set `stream: true` to enable streaming response:

```json
{
  "model": "claude-3-5-sonnet-20241022",
  "max_tokens": 1024,
  "messages": [...],
  "stream": true
}
```

Streaming response event types:

- `message_start` - Message start
- `content_block_start` - Content block start
- `content_block_delta` - Content increment
- `content_block_stop` - Content block end
- `message_delta` - Message metadata (usage)
- `message_stop` - Message end

### Tool Use

Supports function calling (Tool Use):

```json
{
  "model": "claude-3-5-sonnet-20241022",
  "max_tokens": 1024,
  "messages": [
    {
      "role": "user",
      "content": "What's the weather like?"
    }
  ],
  "tools": [
    {
      "name": "get_weather",
      "description": "Get the current weather",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "City name"
          }
        },
        "required": ["location"]
      }
    }
  ]
}
```

### Multimodal Support

Supports image input:

```json
{
  "role": "user",
  "content": [
    {
      "type": "image",
      "source": {
        "type": "base64",
        "media_type": "image/png",
        "data": "iVBORw0KGgo..."
      }
    },
    {
      "type": "text",
      "text": "Describe this image"
    }
  ]
}
```

## POST /v1/messages/count_tokens

Count tokens in messages.

### Request Format

```bash
curl -X POST http://localhost:3456/v1/messages/count_tokens \
  -H "x-api-key: your-api-key" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-3-5-sonnet-20241022",
    "messages": [
      {
        "role": "user",
        "content": "Hello!"
      }
    ],
    "tools": [],
    "system": "You are a helpful assistant."
  }'
```

### Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `model` | string | Yes | Model name |
| `messages` | array | Yes | Array of messages |
| `tools` | array | No | List of tools |
| `system` | string | No | System prompt |

### Response Format

```json
{
  "input_tokens": 42
}
```

## Error Responses

### 400 Bad Request

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "messages is required"
  }
}
```

### 401 Unauthorized

```json
{
  "error": {
    "type": "authentication_error",
    "message": "Invalid API key"
  }
}
```

### 500 Internal Server Error

```json
{
  "error": {
    "type": "api_error",
    "message": "Failed to connect to provider"
  }
}
```


## /docs/docs/server/api/overview.md

---
title: API Overview
---

# API Overview

Claude Code Router Server provides a complete HTTP API with support for:

- **Messages API**: Message interface compatible with Anthropic Claude API
- **Configuration API**: Read and update server configuration
- **Logs API**: View and manage service logs
- **Tools API**: Calculate token counts

## Basic Information

**Base URL**: `http://localhost:3456`

**Authentication**: API Key (via `x-api-key` header)

```bash
curl -H "x-api-key: your-api-key" http://localhost:3456/api/config
```

## API Endpoints

### Messages

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/messages` | POST | Send message (compatible with Anthropic API) |
| `/v1/messages/count_tokens` | POST | Count tokens in messages |

### Configuration Management

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/config` | GET | Get current configuration |
| `/api/config` | POST | Update configuration |
| `/api/transformers` | GET | Get list of available transformers |

### Log Management

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/logs/files` | GET | Get list of log files |
| `/api/logs` | GET | Get log content |
| `/api/logs` | DELETE | Clear logs |

### Service Management

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/restart` | POST | Restart service |
| `/ui` | GET | Web management interface |
| `/ui/` | GET | Web management interface (redirect) |

## Authentication

### API Key Authentication

Add API Key in request header:

```bash
curl -X POST http://localhost:3456/v1/messages \
  -H "x-api-key: your-api-key" \
  -H "content-type: application/json" \
  -d '...'
```

## Streaming Responses

The Messages API supports streaming responses (Server-Sent Events):

```bash
curl -X POST http://localhost:3456/v1/messages \
  -H "x-api-key: your-api-key" \
  -H "content-type: application/json" \
  -d '{"stream": true, ...}'
```

Streaming response format:

```
event: message_start
data: {"type":"message_start","message":{...}}

event: content_block_delta
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}

event: message_stop
data: {"type":"message_stop"}
```


## /docs/docs/server/config/basic.md

---
sidebar_position: 1
---

# Basic Configuration

Learn how to configure Claude Code Router to suit your needs.

## Configuration File Location

The configuration file is located at:

```
~/.claude-code-router/config.json
```

## Configuration Structure

### Providers

Configure LLM providers to route requests to:

```json
{
  "Providers": [
    {
      "NAME": "deepseek",
      "HOST": "https://api.deepseek.com",
      "APIKEY": "your-api-key",
      "MODELS": ["deepseek-chat", "deepseek-coder"]
    },
    {
      "NAME": "groq",
      "HOST": "https://api.groq.com/openai/v1",
      "APIKEY": "your-groq-api-key",
      "MODELS": ["llama-3.3-70b-versatile"]
    }
  ]
}
```

### Router

Configure which model to use by default:

```json
{
  "Router": {
    "default": "deepseek,deepseek-chat"
  }
}
```

Format: `{provider-name},{model-name}`

### Transformers

Apply transformations to requests/responses:

```json
{
  "transformers": [
    {
      "name": "anthropic",
      "providers": ["deepseek", "groq"]
    }
  ]
}
```

### Environment Variables

Use environment variables in your configuration:

```json
{
  "Providers": [
    {
      "NAME": "deepseek",
      "HOST": "https://api.deepseek.com",
      "APIKEY": "$DEEPSEEK_API_KEY"
    }
  ]
}
```

Both `$VAR_NAME` and `${VAR_NAME}` syntax are supported.

## Complete Example

```json
{
  "port": 8080,
  "Providers": [
    {
      "NAME": "deepseek",
      "HOST": "https://api.deepseek.com",
      "APIKEY": "$DEEPSEEK_API_KEY",
      "MODELS": ["deepseek-chat", "deepseek-coder"],
      "transformers": ["anthropic"]
    },
    {
      "NAME": "groq",
      "HOST": "https://api.groq.com/openai/v1",
      "APIKEY": "$GROQ_API_KEY",
      "MODELS": ["llama-3.3-70b-versatile"],
      "transformers": ["anthropic"]
    }
  ],
  "Router": {
    "default": "deepseek,deepseek-chat",
    "longContextThreshold": 100000,
    "background": "groq,llama-3.3-70b-versatile"
  },
  "transformers": [
    {
      "name": "anthropic",
      "providers": ["deepseek", "groq"]
    }
  ]
}
```

## Editing Configuration

Use the CLI to edit the configuration:

```bash
ccr config edit
```

This will open the configuration file in your default editor.

## Reloading Configuration

After editing the configuration, restart the router:

```bash
ccr restart
```

## Next Steps

- [Providers Configuration](/docs/config/providers) - Detailed provider configuration
- [Routing Configuration](/docs/config/routing) - Configure routing rules
- [Transformers](/docs/config/transformers) - Apply transformations


## /docs/docs/server/config/providers.md

---
sidebar_position: 2
---

# Providers Configuration

Detailed guide for configuring LLM providers.

## Supported Providers

### DeepSeek

```json
{
  "NAME": "deepseek",
  "HOST": "https://api.deepseek.com",
  "APIKEY": "your-api-key",
  "MODELS": ["deepseek-chat", "deepseek-coder"],
  "transformers": ["anthropic"]
}
```

### Groq

```json
{
  "NAME": "groq",
  "HOST": "https://api.groq.com/openai/v1",
  "APIKEY": "your-api-key",
  "MODELS": ["llama-3.3-70b-versatile"],
  "transformers": ["anthropic"]
}
```

### Gemini

```json
{
  "NAME": "gemini",
  "HOST": "https://generativelanguage.googleapis.com/v1beta",
  "APIKEY": "your-api-key",
  "MODELS": ["gemini-1.5-pro"],
  "transformers": ["anthropic"]
}
```

### OpenRouter

```json
{
  "NAME": "openrouter",
  "HOST": "https://openrouter.ai/api/v1",
  "APIKEY": "your-api-key",
  "MODELS": ["anthropic/claude-3.5-sonnet"],
  "transformers": ["anthropic"]
}
```

## Provider Configuration Options

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `NAME` | string | Yes | Unique provider identifier |
| `HOST` | string | Yes | API base URL |
| `APIKEY` | string | Yes | API authentication key |
| `MODELS` | string[] | No | List of available models |
| `transformers` | string[] | No | List of transformers to apply |

## Model Selection

When selecting a model in routing, use the format:

```
{provider-name},{model-name}
```

For example:

```
deepseek,deepseek-chat
```

## Next Steps

- [Routing Configuration](/docs/config/routing) - Configure how requests are routed
- [Transformers](/docs/config/transformers) - Apply transformations to requests


## /docs/docs/server/config/routing.md

---
sidebar_position: 3
---

# Routing Configuration

Configure how requests are routed to different models.

## Default Routing

Set the default model for all requests:

```json
{
  "Router": {
    "default": "deepseek,deepseek-chat"
  }
}
```

## Built-in Scenarios

### Background Tasks

Route background tasks to a lightweight model:

```json
{
  "Router": {
    "background": "groq,llama-3.3-70b-versatile"
  }
}
```

### Thinking Mode (Plan Mode)

Route thinking-intensive tasks to a more capable model:

```json
{
  "Router": {
    "think": "deepseek,deepseek-chat"
  }
}
```

### Long Context

Route requests with long context:

```json
{
  "Router": {
    "longContextThreshold": 100000,
    "longContext": "gemini,gemini-1.5-pro"
  }
}
```

### Web Search

Route web search tasks:

```json
{
  "Router": {
    "webSearch": "deepseek,deepseek-chat"
  }
}
```

### Image Tasks

Route image-related tasks:

```json
{
  "Router": {
    "image": "gemini,gemini-1.5-pro"
  }
}
```

## Fallback

When a request fails, you can configure a list of backup models. The system will try each model in sequence until one succeeds:

### Basic Configuration

```json
{
  "Router": {
    "default": "deepseek,deepseek-chat",
    "background": "ollama,qwen2.5-coder:latest",
    "think": "deepseek,deepseek-reasoner",
    "longContext": "openrouter,google/gemini-2.5-pro-preview",
    "longContextThreshold": 60000,
    "webSearch": "gemini,gemini-2.5-flash"
  },
  "fallback": {
    "default": [
      "aihubmix,Z/glm-4.5",
      "openrouter,anthropic/claude-sonnet-4"
    ],
    "background": [
      "ollama,qwen2.5-coder:latest"
    ],
    "think": [
      "openrouter,anthropic/claude-3.7-sonnet:thinking"
    ],
    "longContext": [
      "modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct"
    ],
    "webSearch": [
      "openrouter,anthropic/claude-sonnet-4"
    ]
  }
}
```

### How It Works

1. **Trigger**: When a model request fails for a routing scenario (HTTP error response)
2. **Auto-switch**: The system automatically checks the fallback configuration for that scenario
3. **Sequential retry**: Tries each backup model in order
4. **Success**: Once a model responds successfully, returns immediately
5. **All failed**: If all backup models fail, returns the original error

### Configuration Details

- **Format**: Each backup model format is `provider,model`
- **Validation**: Backup models must exist in the `Providers` configuration
- **Flexibility**: Different scenarios can have different fallback lists
- **Optional**: If a scenario doesn't need fallback, omit it or use an empty array

### Use Cases

#### Scenario 1: Primary Model Quota Exhausted

```json
{
  "Router": {
    "default": "openrouter,anthropic/claude-sonnet-4"
  },
  "fallback": {
    "default": [
      "deepseek,deepseek-chat",
      "aihubmix,Z/glm-4.5"
    ]
  }
}
```

Automatically switches to backup models when the primary model quota is exhausted.

#### Scenario 2: Service Reliability

```json
{
  "Router": {
    "background": "volcengine,deepseek-v3-250324"
  },
  "fallback": {
    "background": [
      "modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct",
      "dashscope,qwen3-coder-plus"
    ]
  }
}
```

Automatically switches to other providers when the primary service fails.

### Log Monitoring

The system logs detailed fallback process:

```
[warn] Request failed for default, trying 2 fallback models
[info] Trying fallback model: aihubmix,Z/glm-4.5
[warn] Fallback model aihubmix,Z/glm-4.5 failed: API rate limit exceeded
[info] Trying fallback model: openrouter,anthropic/claude-sonnet-4
[info] Fallback model openrouter,anthropic/claude-sonnet-4 succeeded
```

### Important Notes

1. **Cost consideration**: Backup models may incur different costs, configure appropriately
2. **Performance differences**: Different models may have varying response speeds and quality
3. **Quota management**: Ensure backup models have sufficient quotas
4. **Testing**: Regularly test the availability of backup models

## Project-Level Routing

Configure routing per project in `~/.claude/projects/<project-id>/claude-code-router.json`:

```json
{
  "Router": {
    "default": "groq,llama-3.3-70b-versatile"
  }
}
```

Project-level configuration takes precedence over global configuration.

## Custom Router

Create a custom JavaScript router function:

1. Create a router file (e.g., `custom-router.js`):

```javascript
module.exports = function(config, context) {
  // Analyze the request context
  const { scenario, projectId, tokenCount } = context;

  // Custom routing logic
  if (scenario === 'background') {
    return 'groq,llama-3.3-70b-versatile';
  }

  if (tokenCount > 100000) {
    return 'gemini,gemini-1.5-pro';
  }

  // Default
  return 'deepseek,deepseek-chat';
};
```

2. Set the `CUSTOM_ROUTER_PATH` environment variable:

```bash
export CUSTOM_ROUTER_PATH="/path/to/custom-router.js"
```

## Token Counting

The router uses `tiktoken` (cl100k_base) to estimate request token count. This is used for:

- Determining if a request exceeds `longContextThreshold`
- Custom routing logic based on token count

## Subagent Routing

Specify models for subagents using special tags:

```
<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>
Please help me analyze this code...
```

## Next Steps

- [Transformers](/docs/config/transformers) - Apply transformations to requests
- [Custom Router](/docs/advanced/custom-router) - Advanced custom routing


## /docs/docs/server/config/transformers.md

---
sidebar_position: 4
---

# Transformers

Transformers are the core mechanism for adapting API differences between LLM providers. They convert requests and responses between different formats, handle authentication, and manage provider-specific features.

## Understanding Transformers

### What is a Transformer?

A transformer is a plugin that:
- **Transforms requests** from the unified format to provider-specific format
- **Transforms responses** from provider format back to unified format
- **Handles authentication** for provider APIs
- **Modifies requests** to add or adjust parameters

### Data Flow

```
┌─────────────────┐
│ Incoming Request│ (Anthropic format from Claude Code)
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────┐
│  transformRequestOut            │ ← Parse incoming request to unified format
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  UnifiedChatRequest             │
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  transformRequestIn (optional)  │ ← Modify unified request before sending
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  Provider API Call              │
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  transformResponseIn (optional) │ ← Convert provider response to unified format
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  transformResponseOut (optional)│ ← Convert unified response to Anthropic format
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────┐
│ Outgoing Response│ (Anthropic format to Claude Code)
└─────────────────┘
```

### Transformer Interface

All transformers implement the following interface:

```typescript
interface Transformer {
  // Convert unified request to provider-specific format
  transformRequestIn?: (
    request: UnifiedChatRequest,
    provider: LLMProvider,
    context: TransformerContext
  ) => Promise<Record<string, any>>;

  // Convert provider request to unified format
  transformRequestOut?: (
    request: any,
    context: TransformerContext
  ) => Promise<UnifiedChatRequest>;

  // Convert provider response to unified format
  transformResponseIn?: (
    response: Response,
    context?: TransformerContext
  ) => Promise<Response>;

  // Convert unified response to provider format
  transformResponseOut?: (
    response: Response,
    context: TransformerContext
  ) => Promise<Response>;

  // Custom endpoint path (optional)
  endPoint?: string;

  // Transformer name (for custom transformers)
  name?: string;

  // Custom authentication handler (optional)
  auth?: (
    request: any,
    provider: LLMProvider,
    context: TransformerContext
  ) => Promise<any>;

  // Logger instance (auto-injected)
  logger?: any;
}
```

### Key Types

#### UnifiedChatRequest

```typescript
interface UnifiedChatRequest {
  messages: UnifiedMessage[];
  model: string;
  max_tokens?: number;
  temperature?: number;
  stream?: boolean;
  tools?: UnifiedTool[];
  tool_choice?: any;
  reasoning?: {
    effort?: ThinkLevel;  // "none" | "low" | "medium" | "high"
    max_tokens?: number;
    enabled?: boolean;
  };
}
```

#### UnifiedMessage

```typescript
interface UnifiedMessage {
  role: "user" | "assistant" | "system" | "tool";
  content: string | null | MessageContent[];
  tool_calls?: Array<{
    id: string;
    type: "function";
    function: {
      name: string;
      arguments: string;
    };
  }>;
  tool_call_id?: string;
  thinking?: {
    content: string;
    signature?: string;
  };
}
```

## Built-in Transformers

### anthropic

Transforms requests to be compatible with Anthropic-style APIs:

```json
{
  "transformers": [
    {
      "name": "anthropic",
      "providers": ["deepseek", "groq"]
    }
  ]
}
```

**Features:**
- Converts Anthropic message format to/from OpenAI format
- Handles tool calls and tool results
- Supports thinking/reasoning content blocks
- Manages streaming responses

### deepseek

Specialized transformer for DeepSeek API:

```json
{
  "transformers": [
    {
      "name": "deepseek",
      "providers": ["deepseek"]
    }
  ]
}
```

**Features:**
- DeepSeek-specific reasoning format
- Handles `reasoning_content` in responses
- Supports thinking budget tokens

### gemini

Transformer for Google Gemini API:

```json
{
  "transformers": [
    {
      "name": "gemini",
      "providers": ["gemini"]
    }
  ]
}
```

### maxtoken

Limits max_tokens in requests:

```json
{
  "transformers": [
    {
      "name": "maxtoken",
      "options": {
        "max_tokens": 8192
      },
      "models": ["deepseek,deepseek-chat"]
    }
  ]
}
```

### customparams

Injects custom parameters into requests:

```json
{
  "transformers": [
    {
      "name": "customparams",
      "options": {
        "include_reasoning": true,
        "custom_header": "value"
      }
    }
  ]
}
```

## Creating Custom Transformers

### Simple Transformer: Modifying Requests

The simplest transformers just modify the request before it's sent to the provider.

**Example: Add a custom header to all requests**

```javascript
// custom-header-transformer.js
module.exports = class CustomHeaderTransformer {
  name = 'custom-header';

  constructor(options) {
    this.headerName = options?.headerName || 'X-Custom-Header';
    this.headerValue = options?.headerValue || 'default-value';
  }

  async transformRequestIn(request, provider, context) {
    // Add custom header (will be used by auth method)
    request._customHeaders = {
      [this.headerName]: this.headerValue
    };
    return request;
  }

  async auth(request, provider) {
    const headers = {
      'authorization': `Bearer ${provider.apiKey}`,
      ...request._customHeaders
    };
    return {
      body: request,
      config: { headers }
    };
  }
};
```

**Usage in config:**

```json
{
  "transformers": [
    {
      "name": "custom-header",
      "path": "/path/to/custom-header-transformer.js",
      "options": {
        "headerName": "X-My-Header",
        "headerValue": "my-value"
      }
    }
  ]
}
```

### Intermediate Transformer: Request/Response Conversion

This example shows how to convert between different API formats.

**Example: Mock API format transformer**

```javascript
// mockapi-transformer.js
module.exports = class MockAPITransformer {
  name = 'mockapi';
  endPoint = '/v1/chat';  // Custom endpoint

  // Convert from MockAPI format to unified format
  async transformRequestOut(request, context) {
    const messages = request.conversation.map(msg => ({
      role: msg.sender,
      content: msg.text
    }));

    return {
      messages,
      model: request.model_id,
      max_tokens: request.max_tokens,
      temperature: request.temp
    };
  }

  // Convert from unified format to MockAPI format
  async transformRequestIn(request, provider, context) {
    return {
      model_id: request.model,
      conversation: request.messages.map(msg => ({
        sender: msg.role,
        text: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
      })),
      max_tokens: request.max_tokens || 4096,
      temp: request.temperature || 0.7
    };
  }

  // Convert MockAPI response to unified format
  async transformResponseIn(response, context) {
    const data = await response.json();

    const unifiedResponse = {
      id: data.request_id,
      object: 'chat.completion',
      created: data.timestamp,
      model: data.model,
      choices: [{
        index: 0,
        message: {
          role: 'assistant',
          content: data.reply.text
        },
        finish_reason: data.stop_reason
      }],
      usage: {
        prompt_tokens: data.tokens.input,
        completion_tokens: data.tokens.output,
        total_tokens: data.tokens.input + data.tokens.output
      }
    };

    return new Response(JSON.stringify(unifiedResponse), {
      status: response.status,
      statusText: response.statusText,
      headers: { 'Content-Type': 'application/json' }
    });
  }
};
```

### Advanced Transformer: Streaming Response Processing

This example shows how to handle streaming responses.

**Example: Add custom metadata to streaming responses**

```javascript
// streaming-metadata-transformer.js
module.exports = class StreamingMetadataTransformer {
  name = 'streaming-metadata';

  constructor(options) {
    this.metadata = options?.metadata || {};
    this.logger = null;  // Will be injected by the system
  }

  async transformResponseOut(response, context) {
    const contentType = response.headers.get('Content-Type');

    // Handle streaming response
    if (contentType?.includes('text/event-stream')) {
      return this.transformStream(response, context);
    }

    // Handle non-streaming response
    return response;
  }

  async transformStream(response, context) {
    const decoder = new TextDecoder();
    const encoder = new TextEncoder();

    const transformedStream = new ReadableStream({
      start: async (controller) => {
        const reader = response.body.getReader();
        let buffer = '';

        try {
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split('\n');
            buffer = lines.pop() || '';

            for (const line of lines) {
              if (!line.trim() || !line.startsWith('data: ')) {
                controller.enqueue(encoder.encode(line + '\n'));
                continue;
              }

              const data = line.slice(6).trim();
              if (data === '[DONE]') {
                controller.enqueue(encoder.encode(line + '\n'));
                continue;
              }

              try {
                const chunk = JSON.parse(data);

                // Add custom metadata
                if (chunk.choices && chunk.choices[0]) {
                  chunk.choices[0].metadata = this.metadata;
                }

                // Log for debugging
                this.logger?.debug({
                  chunk,
                  context: context.req.id
                }, 'Transformed streaming chunk');

                const modifiedLine = `data: ${JSON.stringify(chunk)}\n\n`;
                controller.enqueue(encoder.encode(modifiedLine));
              } catch (parseError) {
                // If parsing fails, pass through original line
                controller.enqueue(encoder.encode(line + '\n'));
              }
            }
          }
        } catch (error) {
          this.logger?.error({ error }, 'Stream transformation error');
          controller.error(error);
        } finally {
          controller.close();
          reader.releaseLock();
        }
      }
    });

    return new Response(transformedStream, {
      status: response.status,
      statusText: response.statusText,
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      }
    });
  }
};
```

### Real-World Example: Reasoning Content Transformer

This is based on the actual `reasoning.transformer.ts` from the codebase.

```typescript
// reasoning-transformer.ts
import { Transformer, TransformerOptions } from "@musistudio/llms";

export class ReasoningTransformer implements Transformer {
  static TransformerName = "reasoning";
  enable: boolean;

  constructor(private readonly options?: TransformerOptions) {
    this.enable = this.options?.enable ?? true;
  }

  // Transform request to add reasoning parameters
  async transformRequestIn(request: UnifiedChatRequest): Promise<UnifiedChatRequest> {
    if (!this.enable) {
      request.thinking = {
        type: "disabled",
        budget_tokens: -1,
      };
      request.enable_thinking = false;
      return request;
    }

    if (request.reasoning) {
      request.thinking = {
        type: "enabled",
        budget_tokens: request.reasoning.max_tokens,
      };
      request.enable_thinking = true;
    }
    return request;
  }

  // Transform response to convert reasoning_content to thinking format
  async transformResponseOut(response: Response): Promise<Response> {
    if (!this.enable) return response;

    const contentType = response.headers.get("Content-Type");

    // Handle non-streaming response
    if (contentType?.includes("application/json")) {
      const jsonResponse = await response.json();
      if (jsonResponse.choices[0]?.message.reasoning_content) {
        jsonResponse.thinking = {
          content: jsonResponse.choices[0].message.reasoning_content
        };
      }
      return new Response(JSON.stringify(jsonResponse), {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });
    }

    // Handle streaming response
    if (contentType?.includes("stream")) {
      // [Streaming transformation code here]
      // See the full implementation in the codebase
    }

    return response;
  }
}
```

## Transformer Registration

### Method 1: Static Name (Class-based)

Use this when creating a transformer in TypeScript/ES6:

```typescript
export class MyTransformer implements Transformer {
  static TransformerName = "my-transformer";

  async transformRequestIn(request: UnifiedChatRequest): Promise<any> {
    // Transformation logic
    return request;
  }
}
```

### Method 2: Instance Name (Instance-based)

Use this for JavaScript transformers:

```javascript
module.exports = class MyTransformer {
  constructor(options) {
    this.name = 'my-transformer';
    this.options = options;
  }

  async transformRequestIn(request, provider, context) {
    // Transformation logic
    return request;
  }
};
```

## Applying Transformers

### Global Application (Provider Level)

Apply to all requests for a provider:

```json
{
  "Providers": [
    {
      "NAME": "deepseek",
      "HOST": "https://api.deepseek.com",
      "APIKEY": "your-api-key",
      "transformers": ["anthropic"]
    }
  ]
}
```

### Model-Specific Application

Apply to specific models only:

```json
{
  "transformers": [
    {
      "name": "maxtoken",
      "options": {
        "max_tokens": 8192
      },
      "models": ["deepseek,deepseek-chat"]
    }
  ]
}
```

Note: The model format is `provider,model` (e.g., `deepseek,deepseek-chat`).

### Global Transformers (All Providers)

Apply transformers to all providers:

```json
{
  "transformers": [
    {
      "name": "custom-logger",
      "path": "/path/to/custom-logger.js"
    }
  ]
}
```

### Passing Options

Some transformers accept configuration options:

```json
{
  "transformers": [
    {
      "name": "maxtoken",
      "options": {
        "max_tokens": 8192
      }
    },
    {
      "name": "customparams",
      "options": {
        "custom_param_1": "value1",
        "custom_param_2": 42
      }
    }
  ]
}
```

## Best Practices

### 1. Immutability

Always create new objects rather than mutating existing ones:

```javascript
// Bad
async transformRequestIn(request) {
  request.max_tokens = 4096;
  return request;
}

// Good
async transformRequestIn(request) {
  return {
    ...request,
    max_tokens: request.max_tokens || 4096
  };
}
```

### 2. Error Handling

Always handle errors gracefully:

```javascript
async transformResponseIn(response) {
  try {
    const data = await response.json();
    // Process data
    return new Response(JSON.stringify(processedData), {
      status: response.status,
      headers: response.headers
    });
  } catch (error) {
    this.logger?.error({ error }, 'Transformation failed');
    // Return original response if transformation fails
    return response;
  }
}
```

### 3. Logging

Use the injected logger for debugging:

```javascript
async transformRequestIn(request, provider, context) {
  this.logger?.debug({
    model: request.model,
    provider: provider.name
  }, 'Transforming request');

  // Your transformation logic

  return modifiedRequest;
}
```

### 4. Stream Handling

When handling streams, always:
- Use a buffer to handle incomplete chunks
- Properly release the reader lock
- Handle errors in the stream
- Close the controller when done

```javascript
const transformedStream = new ReadableStream({
  start: async (controller) => {
    const reader = response.body.getReader();
    let buffer = '';

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        // Process stream...
      }
    } catch (error) {
      controller.error(error);
    } finally {
      controller.close();
      reader.releaseLock();
    }
  }
});
```

### 5. Context Usage

The `context` parameter contains useful information:

```javascript
async transformRequestIn(request, provider, context) {
  // Access request ID
  const requestId = context.req.id;

  // Access original request
  const originalRequest = context.req.original;

  // Your transformation logic
}
```

## Testing Your Transformer

### Manual Testing

1. Add your transformer to the config
2. Start the server: `ccr restart`
3. Check logs: `tail -f ~/.claude-code-router/logs/ccr-*.log`
4. Make a test request
5. Verify the output

### Debug Tips

- Add logging to track transformation steps
- Test with both streaming and non-streaming requests
- Verify error handling with invalid inputs
- Check that original responses are returned on error

## Next Steps

- [Advanced Topics](/docs/server/advanced/custom-router) - Advanced routing customization
- [Agents](/docs/server/advanced/agents) - Extending with agents
- [Core Package](/docs/server/intro) - Learn about @musistudio/llms


## /docs/docs/server/deployment.md

---
title: Server Deployment
---

# Server Deployment

Claude Code Router Server supports multiple deployment methods, from local development to production environments.

## Docker Deployment (Recommended)

### Using Docker Hub Image

```bash
docker run -d \
  --name claude-code-router \
  -p 3456:3456 \
  -v ~/.claude-code-router:/app/.claude-code-router \
  musistudio/claude-code-router:latest
```

### Using Docker Compose

Create `docker-compose.yml`:

```yaml
version: '3.8'
services:
  claude-code-router:
    image: musistudio/claude-code-router:latest
    container_name: claude-code-router
    ports:
      - "3456:3456"
    volumes:
      - ./config:/app/.claude-code-router
    environment:
      - LOG_LEVEL=info
      - HOST=0.0.0.0
      - PORT=3456
    restart: unless-stopped
```

Start the service:

```bash
docker-compose up -d
```

### Custom Build

Build Docker image from source:

```bash
git clone https://github.com/musistudio/claude-code-router.git
cd claude-code-router
docker build -t claude-code-router:latest .
```

## Configuration File Mounting

Mount configuration file into container:

```bash
docker run -d \
  --name claude-code-router \
  -p 3456:3456 \
  -v $(pwd)/config.json:/app/.claude-code-router/config.json \
  musistudio/claude-code-router:latest
```

Configuration file example:

```json5
{
  // Server configuration
  "HOST": "0.0.0.0",
  "PORT": 3456,
  "APIKEY": "your-api-key-here",

  // Logging configuration
  "LOG": true,
  "LOG_LEVEL": "info",

  // LLM provider configuration
  "Providers": [
    {
      "name": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "$OPENAI_API_KEY",
      "models": ["gpt-4", "gpt-3.5-turbo"]
    }
  ],

  // Routing configuration
  "Router": {
    "default": "openai,gpt-4"
  }
}
```

## Environment Variables

Override configuration through environment variables:

| Variable | Description | Default |
|----------|-------------|---------|
| `HOST` | Listen address | `127.0.0.1` |
| `PORT` | Listen port | `3456` |
| `APIKEY` | API key | - |
| `LOG_LEVEL` | Log level | `debug` |
| `LOG` | Enable logging | `true` |

## Production Recommendations

### 1. Use Reverse Proxy

Use Nginx as reverse proxy:

```nginx
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3456;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;
    }
}
```

### 2. Configure HTTPS

Use Let's Encrypt to obtain free certificate:

```bash
sudo certbot --nginx -d your-domain.com
```

### 3. Log Management

Configure log rotation and persistence:

```yaml
version: '3.8'
services:
  claude-code-router:
    image: musistudio/claude-code-router:latest
    volumes:
      - ./logs:/app/.claude-code-router/logs
    environment:
      - LOG_LEVEL=warn
```

### 4. Health Check

Configure Docker health check:

```yaml
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3456/api/config"]
  interval: 30s
  timeout: 10s
  retries: 3
```

## Access Web UI

After deployment is complete, access the Web UI:

```
http://localhost:3456/ui/
```

Through the Web UI you can:
- View and manage configuration
- Monitor logs
- Check service status

## Secondary Development

If you need to develop based on CCR Server, please see [API Reference](/docs/category/api).


## /docs/docs/server/intro.md

---
title: Server Introduction
---

# Server Introduction

Claude Code Router Server is a core service component responsible for routing Claude Code API requests to different LLM providers. It provides a complete HTTP API with support for:

- **API Request Routing**: Convert Anthropic-format requests to various provider API formats
- **Authentication & Authorization**: Support API Key authentication
- **Configuration Management**: Dynamic configuration of providers, routing rules, and transformers
- **Web UI**: Built-in management interface
- **Logging System**: Complete request logging

## Architecture Overview

```
┌─────────────┐     ┌─────────────────────────────┐     ┌──────────────┐
│ Claude Code │────▶│ CCR Server                  │────▶│ LLM Provider │
│   Client    │     │  ┌─────────────────────┐    │     │  (OpenAI/    │
└─────────────┘     │  │ @musistudio/llms    │    │     │   Gemini/etc)│
                    │  │ (Core Package)       │    │     └──────────────┘
                    │  │ - Request Transform  │    │
                    │  │ - Response Transform │    │
                    │  │ - Auth Handling      │    │
                    │  └─────────────────────┘    │
                    │                             │
                    │  - Routing Logic            │
                    │  - Agent System             │
                    │  - Configuration            │
                    └─────────────────────────────┘
                           │
                           ├─ Web UI
                           ├─ Config API
                           └─ Logs API
```

## Core Package: @musistudio/llms

The server is built on top of **@musistudio/llms**, a universal LLM API transformation library that provides the core request/response transformation capabilities.

### What is @musistudio/llms?

`@musistudio/llms` is a standalone npm package (`@musistudio/llms`) that handles:

- **API Format Conversion**: Transforms between different LLM provider APIs (Anthropic, OpenAI, Gemini, etc.)
- **Request/Response Transformation**: Converts requests and responses to a unified format
- **Authentication Handling**: Manages different authentication methods across providers
- **Streaming Support**: Handles streaming responses from different providers
- **Transformer System**: Provides an extensible architecture for adding new providers

### Key Concepts

#### 1. Unified Request/Response Format

The core package defines a unified format (`UnifiedChatRequest`, `UnifiedChatResponse`) that abstracts away provider-specific differences:

```typescript
interface UnifiedChatRequest {
  messages: UnifiedMessage[];
  model: string;
  max_tokens?: number;
  temperature?: number;
  stream?: boolean;
  tools?: UnifiedTool[];
  tool_choice?: any;
  reasoning?: {
    effort?: ThinkLevel;
    max_tokens?: number;
    enabled?: boolean;
  };
}
```

#### 2. Transformer Interface

All transformers implement a common interface:

```typescript
interface Transformer {
  transformRequestIn?: (request: UnifiedChatRequest, provider: LLMProvider, context: TransformerContext) => Promise<any>;
  transformRequestOut?: (request: any, context: TransformerContext) => Promise<UnifiedChatRequest>;
  transformResponseIn?: (response: Response, context?: TransformerContext) => Promise<Response>;
  transformResponseOut?: (response: Response, context: TransformerContext) => Promise<Response>;
  endPoint?: string;
  name?: string;
  auth?: (request: any, provider: LLMProvider, context: TransformerContext) => Promise<any>;
}
```

#### 3. Built-in Transformers

The core package includes transformers for:
- **anthropic**: Anthropic API format
- **openai**: OpenAI API format
- **gemini**: Google Gemini API format
- **deepseek**: DeepSeek API format
- **groq**: Groq API format
- **openrouter**: OpenRouter API format
- And more...

### Integration with CCR Server

The CCR server integrates `@musistudio/llms` through:

1. **Transformer Service** (`packages/core/src/services/transformer.ts`): Manages transformer registration and instantiation
2. **Provider Configuration**: Maps provider configs to core package's LLMProvider interface
3. **Request Pipeline**: Applies transformers in sequence during request processing
4. **Custom Transformers**: Supports loading external transformer plugins

### Version and Updates

The current version of `@musistudio/llms` is `1.0.51`. It's published as an independent npm package and can be used standalone or as part of CCR Server.

## Core Features

### 1. Request Routing
- Token-count-based intelligent routing
- Project-level routing configuration
- Custom routing functions
- Scenario-based routing (background, think, longContext, etc.)

### 2. Request Transformation
- Supports API format conversion for multiple LLM providers
- Built-in transformers: Anthropic, DeepSeek, Gemini, OpenRouter, Groq, etc.
- Extensible transformer system

### 3. Agent System
- Plugin-based Agent architecture
- Built-in image processing Agent
- Custom Agent support

### 4. Configuration Management
- JSON5 format configuration file
- Environment variable interpolation
- Hot configuration reload (requires service restart)

## Use Cases

### Scenario 1: Personal Local Service
Run the service locally for personal Claude Code use:

```bash
ccr start
```

### Scenario 2: Team Shared Service
Deploy using Docker to provide shared service for team members:

```bash
docker run -d -p 3456:3456 musistudio/claude-code-router
```

### Scenario 3: Secondary Development
Build custom applications based on exposed APIs:

```bash
GET /api/config
POST /v1/messages
GET /api/logs
```

## Next Steps

- [Docker Deployment Guide](/docs/server/deployment) - Learn how to deploy the service
- [API Reference](/docs/category/api) - View complete API documentation
- [Configuration Guide](/docs/category/server-config) - Understand server configuration options


## /docs/docusaurus.config.ts

```ts path="/docs/docusaurus.config.ts" 
import type { Config } from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
import { themes as prismThemes } from 'prism-react-renderer';

const config: Config = {
  title: 'Claude Code Router',
  tagline: 'Use Claude Code without an Anthropics account and route it to another LLM provider',
  favicon: 'img/favicon.ico',

  url: 'https://musistudio.github.io',
  baseUrl: '/claude-code-router/',

  organizationName: 'musistudio',
  projectName: 'claude-code-router',

  onBrokenLinks: 'warn',
  onBrokenMarkdownLinks: 'warn',
  onBrokenAnchors: 'warn',

  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'zh-CN'],
  },

  presets: [
    [
      'classic',
      {
        docs: {
          sidebarPath: './sidebars.ts',
          editUrl:
            'https://github.com/musistudio/claude-code-router/tree/main/docs',
        },
        blog: {
          showReadingTime: true,
          editUrl:
            'https://github.com/musistudio/claude-code-router/tree/main/docs',
        },
        theme: {
          customCss: './src/css/custom.css',
        },
      } satisfies Preset.Options,
    ],
  ],

  themeConfig: {
    // Disable dark mode
    colorMode: {
      defaultMode: 'light',
      disableSwitch: true,
      respectPrefersColorScheme: false,
    },

    image: 'img/docusaurus-social-card.jpg',
    navbar: {
      title: 'Claude Code Router',
      logo: {
        alt: 'Claude Code Router Logo',
        src: 'img/ccr.svg',
        width: 32,
        height: 32,
      },
      items: [
        {
          type: 'docSidebar',
          sidebarId: 'tutorialSidebar',
          position: 'left',
          label: 'Documentation',
        },
        { to: '/blog', label: 'Blog', position: 'left' },
        {
          type: 'localeDropdown',
          position: 'right',
        },
        {
          href: 'https://github.com/musistudio/claude-code-router',
          label: 'GitHub',
          position: 'right',
        },
      ],
    },
    footer: {
      style: 'light',
      links: [],
      copyright: `Copyright © ${new Date().getFullYear()} Claude Code Router. Built with Docusaurus.`,
    },
    prism: {
      theme: prismThemes.github,
      darkTheme: prismThemes.dracula,
      additionalLanguages: ['bash', 'typescript', 'javascript', 'json'],
    },
  } satisfies Preset.ThemeConfig,
};

export default config;

```

## /docs/i18n/en/docusaurus-plugin-content-blog/options.json

```json path="/docs/i18n/en/docusaurus-plugin-content-blog/options.json" 
{
  "title": {
    "message": "Blog",
    "description": "The title for the blog used in SEO"
  },
  "description": {
    "message": "Blog",
    "description": "The description for the blog used in SEO"
  },
  "sidebar.title": {
    "message": "Recent posts",
    "description": "The label for the left sidebar"
  }
}

```

## /docs/i18n/en/docusaurus-theme-classic/footer.json

```json path="/docs/i18n/en/docusaurus-theme-classic/footer.json" 
{
  "link.title.Docs": {
    "message": "Docs",
    "description": "The title of the footer links column with title=Docs in the footer"
  },
  "link.title.Community": {
    "message": "Community",
    "description": "The title of the footer links column with title=Community in the footer"
  },
  "link.title.More": {
    "message": "More",
    "description": "The title of the footer links column with title=More in the footer"
  },
  "link.item.label.Tutorial": {
    "message": "Tutorial",
    "description": "The label of footer link with label=Tutorial linking to /docs/intro"
  },
  "link.item.label.GitHub": {
    "message": "GitHub",
    "description": "The label of footer link with label=GitHub linking to https://github.com/musistudio/claude-code-router"
  },
  "link.item.label.Blog": {
    "message": "Blog",
    "description": "The label of footer link with label=Blog linking to /blog"
  },
  "copyright": {
    "message": "Copyright © 2026 Claude Code Router. Built with Docusaurus.",
    "description": "The footer copyright"
  }
}

```

## /docs/i18n/en/docusaurus-theme-classic/navbar.json

```json path="/docs/i18n/en/docusaurus-theme-classic/navbar.json" 
{
  "title": {
    "message": "Claude Code Router",
    "description": "The title in the navbar"
  },
  "logo.alt": {
    "message": "Claude Code Router Logo",
    "description": "The alt text of navbar logo"
  },
  "item.label.Documentation": {
    "message": "Documentation",
    "description": "Navbar item with label Documentation"
  },
  "item.label.Blog": {
    "message": "Blog",
    "description": "Navbar item with label Blog"
  },
  "item.label.GitHub": {
    "message": "GitHub",
    "description": "Navbar item with label GitHub"
  }
}

```

## /docs/i18n/zh-CN/docusaurus-plugin-content-blog/options.json

```json path="/docs/i18n/zh-CN/docusaurus-plugin-content-blog/options.json" 
{
  "title": {
    "message": "Blog",
    "description": "The title for the blog used in SEO"
  },
  "description": {
    "message": "Blog",
    "description": "The description for the blog used in SEO"
  },
  "sidebar.title": {
    "message": "Recent posts",
    "description": "The label for the left sidebar"
  }
}

```

## /docs/postcss.config.js

```js path="/docs/postcss.config.js" 
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

## /docs/static/blog-images/alipay.jpg

Binary file available at https://raw.githubusercontent.com/musistudio/claude-code-router/refs/heads/main/docs/static/blog-images/alipay.jpg


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!