ccproxy

starbaser活跃维护

给 Claude Code 加钩子的本地代理,能拦截并改写请求与响应,支持自定义模型名和按逻辑智能路由模型。

Build mods for Claude Code: Hook any request, modify any response, /model "with-your-custom-model", intelligent model routing using your logic or ours

v1.2.0发布于 2025-12-06 03:34 UTC357star34fork同步于 2026-11-07 07:32 UTC

怎么上手

由 README 提炼,执行前请确认
uv tool install ai-ccproxy
ccproxy init
ccproxy start
硬件要求按 README 说明

README 未说明显存或 GPU 要求;Linux 与 WSL2 支持完整功能,macOS 仅支持反向代理,命名空间捕获需 Linux 内核特性

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

基于 mitmproxy 和 WireGuard 的 LLM 流量拦截代理,可改写请求、跨厂商路由模型

给 Claude Code 加钩子的本地代理,能拦截并改写请求与响应,支持自定义模型名和按逻辑智能路由模型。

核心能力

  • 用 rootless WireGuard 命名空间隔离进程并拦截全部流量
  • DAG 钩子管线在 inbound/outbound 阶段改写请求与响应
  • lightllm 适配层做跨厂商请求响应格式转换与 SSE 流式处理
  • 按 match_host/path/model 正则匹配做 redirect、transform、passthrough 路由
  • 支持 Anthropic、Gemini、OpenAI、DeepSeek、Perplexity 等 provider 认证注入

容易踩的坑

macOS 无法使用 ccproxy run --capture,仅反向代理可用;Linux 需预装 slirp4netns、wireguard-tools、iptables 等系统工具;Ubuntu 24.04+ 默认限制非特权用户命名空间,需调整 sysctl 或加 AppArmor 配置

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

2 个版本v1.2.0
  1. v2.0.0-rc1pre-release2026-04-16 · 2026-04-16 00:45 UTC
    查看更新说明

    ccproxy v2.0.0-rc1

    Recent shifts in the landscape necessitate change: ccproxy is no longer a LiteLLM addon, or even just a proxy. It is no longer catered entirely to Claude Code, and can now serve as a universal inspector for any AI application that crosses your network.

    LiteLLM has been decapitated. It no longer runs as the executing process or starts the server; it has been demoted to an import, used solely for its cross-provider translation layer. The recent supply chain attack still motivates complete removal, but I decided against it for now to avoid breaking applications that still expect the API spec LiteLLM provided.

    The software itself is production-ready. What remains is largely its presentation, and more importantly for you, the readme and documentation. What is currently provided is sufficient but mostly machine-generated; I have at least reviewed it and it is complete. Suggestions or feedback welcome on the PR.

    Full Changelog: https://github.com/starbaser/ccproxy/commits/v2.0.0-rc1

  2. v1.2.0v1.2.0 - The Escape Plan最新正式版2025-12-06 · 2025-12-06 03:34 UTC

    This release is a step in a new direction I hope to take this project: Jailbreaking Claude Code. I see ccproxy as the bedrock for creating a near replica of the Claude Code harness, an escape hatch for bringing its capabilities to any command-line tool/harness. Currently, experimenting with a forked version of https://github.com/charmbracelet/crush/ is showing much promise. If you would like to learn more, please join my new discord.


    Features

    Keyless API Access

    Multi-provider OAuth support via oat_sources configuration. When enabled, the proxy automatically forwards the correct OAuth token based on routing, eliminating the need to manage API keys manually. Includes custom User-Agent support per token source.

    Improved Hooks

    Added hooks that accept parameters. This is compatible with inline hooks and is backwards compatible. See the ccproxy.yaml example file for reference.

    LangFuse Integration

    First-class LangFuse observability support. Claude Code sessions are automatically extracted and grouped as LangFuse sessions, with HTTP headers captured as trace metadata (sensitive values redacted). See .env.example for setting up your LangFuse credentials.

    CLI Improvements

    The ccproxy status command output has been improved to display hooks and model deployments. The handler file (ccproxy.py) is now auto-generated on startup, with detection to preserve custom handler files.

    Bug Fixes

    • Use bundled litellm from venv instead of PATH (fixes "No module named 'backoff'" error)

    Documentation

    • Add installation and troubleshooting guides
    • Fix installation commands and prioritize PyPI
    • Document auto-generated handler workflow

    Chores

    • Update Opus model references to claude-opus-4-5-20251101
    • Update ccproxy.yaml template to current format (oat_sources, capture_headers)
    • Comprehensive test coverage improvements

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    ccproxy — CLI Proxy Version

    Discord

    ccproxy is a transparent network interceptor for LLM tooling and AI harnesses, built on mitmproxy and WireGuard with full TLS inspection and Wireshark keylog export. Originally purpose-built for Claude Code, ccproxy now works with any LLM client: Aider, Cursor, OpenAI SDK, or anything else that speaks HTTP. It jails a process inside a rootless WireGuard namespace, intercepts at the network layer, and feeds it through a DAG-driven pipeline that can decompose, transform, and re-route traffic between providers. Cross-provider request and response transformation is handled by lightllm, a surgical adapter and streaming-FSM layer inside ccproxy — no LiteLLM proxy subprocess, no gateway server.

    New in 2.0: Codex/OpenAI Responses support with packaged request shaping, plus DeepSeek V4 routing for Anthropic-format requests through DeepSeek's /anthropic/v1/messages endpoint. See Configuration for the routing setup.

    The hook pipeline is your extension point for building mods and taking control of your LLM usage while respecting terms of service:

    • Cross-provider routing: redirect or transform requests between Anthropic, Gemini, OpenAI, DeepSeek, Perplexity Pro, and Anthropic-compatible forks.
    • Compliance shaping: replay packaged, sanitized SDK compliance envelopes for built-in providers while injecting your actual request content at runtime.
    • MCP bridging: add unsupported MCP features to any client: sampling via sentinel key detection, server notifications bridged into the LLM context via ccproxy’s /mcp endpoint, and experimental tasks support.

    Feedback and contributions welcome — open an issue or submit a PR.

    Installation

    Platform support

    PlatformReverse proxy (ccproxy start)WireGuard namespace jail (ccproxy run --capture)
    Linux✅✅
    Windows (WSL2)✅✅
    macOS✅❌ — requires Linux namespaces

    WSL2 is fully supported because it is Linux. Native Windows is not — use WSL2. On macOS, the reverse proxy listener (ccproxy start + SDK use) works fine, but the namespace jail (ccproxy run --capture) requires Linux kernel features (unprivileged user/net namespaces, slirp4netns, iptables NAT) that have no macOS equivalent.

    Windows via WSL2

    The recommended Windows install is the ccproxy.wsl distro artifact. It is built on NixOS-WSL and includes ccproxy plus the Linux namespace tools required by ccproxy run --capture.

    # Requires Store WSL 2.4.4 or newer.
    wsl --update
    wsl --version
    wsl --install --from-file ccproxy.wsl
    wsl -d ccproxy
    

    Inside the distro:

    ccproxy init
    ccproxy start
    ccproxy namespace status --json
    ccproxy namespace doctor --json
    

    Tier 1 Windows support is Windows 11 22H2+ with Store-distributed WSL2, systemd enabled, and mirrored networking recommended. Windows 10 and older WSL networking are best-effort. WSL1 and native Windows without WSL are unsupported.

    Advanced users can still use Ubuntu on WSL2 with systemd and Nix, but the release artifact is the primary out-of-box path.

    Linux

    The WireGuard namespace jail needs a small set of system tools on PATH: slirp4netns, wireguard-tools (wg), iproute2 (ip), iptables, util-linux (unshare, nsenter), and procps (sysctl).

    # Debian / Ubuntu
    sudo apt update
    sudo apt install -y slirp4netns wireguard-tools iproute2 iptables procps
    
    # Fedora
    sudo dnf install -y slirp4netns wireguard-tools iproute iptables-nft procps-ng
    
    # Arch
    sudo pacman -S slirp4netns wireguard-tools iproute2 iptables procps-ng
    
    # NixOS — provided via the project devShell (`nix develop`)
    

    Then install ccproxy:

    # Recommended: uv tool (isolated venv, console scripts on PATH)
    uv tool install ai-ccproxy
    
    # Alternative: pip
    pip install ai-ccproxy
    

    On Ubuntu 24.04+, unprivileged user namespaces are restricted by AppArmor by default. Either run once:

    sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
    

    …or add a path-scoped AppArmor profile (see rootless-containers/rootlesskit).

    macOS

    Only the reverse proxy is supported. No system packages are required.

    uv tool install ai-ccproxy
    # or
    pip install ai-ccproxy
    

    ccproxy start and SDK use (ANTHROPIC_BASE_URL=http://localhost:4000) work the same as on Linux. ccproxy run --capture will fail fast with a clear error listing the missing Linux-only tools.

    Verify

    ccproxy --help
    ccproxy init
    ccproxy status --proxy --inspect    # exit 3 = both down (expected, nothing running yet)
    

    Quick Start

    # Initialize ccproxy.yaml + LiteLLM-compatible config.yaml
    ccproxy init
    
    # Start the proxy and inspector stack (foreground)
    ccproxy start
    

    SDK use: point any OpenAI-compatible client at the reverse proxy listener:

    export ANTHROPIC_BASE_URL=http://localhost:4000
    claude -p "hello"
    

    Transparent capture: run a command inside the WireGuard namespace jail (all traffic intercepted):

    ccproxy run --capture -- claude -p "hello"
    

    Architecture

    Traffic enters through one of two listeners, passes through a fixed three-stage addon chain, and exits directly to the provider API.

    flowchart TD
        subgraph Listeners
            RP["Reverse Proxy :4000"]
            WG["WireGuard CLI"]
        end
        RP --> Chain
        WG --> Chain
        subgraph Chain["Addon Chain"]
            IN["inbound<br/>DAG hooks"] --> TX["transform<br/>lightllm"] --> OUT["outbound<br/>DAG hooks"]
        end
        Chain --> API["Provider API"]
    

    Addon chain (fixed order): ReadySignal → InspectorAddon → FingerprintCaptureAddon → MultiHARSaver → ShapeCaptureAddon → inbound DAG → transform → outbound DAG → TransportOverrideAddon → AuthAddon → GeminiAddon → PerplexityAddon → EgressSanitizerAddon

    AuthAddon and GeminiAddon sit after the outbound pipeline so they see ccproxy-finalized requests/responses. AuthAddon owns 401-detect → refresh → replay. GeminiAddon owns Gemini capacity fallback (sticky retry + fallback chain on 429/503) and cloudcode-pa envelope unwrapping.

    lightllm converts request and response bodies through ccproxy's own adapter layer and streaming FSMs. URL rewriting and auth injection are owned by the inspector route and Provider config, while lightllm owns wire-format conversion. LiteLLM itself is not a runtime dependency: sibling config.yaml model declarations are compiled into ccproxy-native bindings before requests enter this path.

    SSE streaming: SSEPipeline handles cross-provider streaming by parsing SSE events into ccproxy's response IR and rendering each chunk back to the listener's wire format.

    Configuration

    ccproxy init writes sibling ccproxy.yaml and config.yaml templates under ~/.config/ccproxy/. Both are also discovered beneath $CCPROXY_CONFIG_DIR.

    • ccproxy.yaml owns native runtime services: providers, authentication, hooks, inspection, shaping, and explicit transform overrides.
    • config.yaml is a LiteLLM-compatible frontend for model definitions, aliases, endpoints, credentials, metadata, and supported request defaults.

    The frontend compiles into the existing CCProxyConfig/Provider runtime; it does not restore LiteLLM's proxy process or router. Routing precedence is explicit lightllm.transforms, exact then wildcard compiled model bindings, sentinel-selected providers, then no match. See Configuration for the supported compatibility subset and trust-boundary rules.

    ccproxy.yaml:

    ccproxy:
      port: 4000
    
      # Provider entries keyed by sentinel suffix. The sentinel key
      # sk-ant-oat-ccproxy-{name} resolves to providers[name] for token
      # injection and routing.
      providers:
        anthropic:
          auth:
            type: command
            command: "jq -r '.claudeAiOauth.accessToken' ~/.claude/.credentials.json"
          base_url: https://api.anthropic.com
          path: /v1/messages
          type: anthropic
    
        deepseek:
          auth:
            type: command
            command: "printenv DEEPSEEK_API_KEY"
            header: x-api-key
          base_url: https://api.deepseek.com
          path: /anthropic/v1/messages
          type: anthropic
    
      hooks:
        inbound:
          - ccproxy.hooks.inject_auth
          - ccproxy.hooks.extract_session_id
        outbound:
          - ccproxy.hooks.gemini_cli
          - ccproxy.hooks.pplx_stamp_headers
          - ccproxy.hooks.inject_mcp_notifications
          - ccproxy.hooks.verbose_mode
          - ccproxy.hooks.shape
          - ccproxy.hooks.commitbee_compat
    
      lightllm:
        # Optional regex-matched override rules layered on top of the
        # sentinel-driven providers map. Default is empty: most routing
        # comes from `providers` via inject_auth's sentinel detection.
        transforms:
          - match_path: ^/v1/chat/completions
            match_model: ^gpt-4o
            action: transform
            dest_provider: anthropic
            dest_model: claude-haiku-4-5-20251001
    

    Transform matching: match_host (optional regex, checked against pretty_host + Host header + X-Forwarded-Host), match_path (regex, default .*), match_model (regex, optional). First match wins. Three actions: redirect (default — rewrite destination, preserve body), transform (cross-format via lightllm), passthrough (forward unchanged). Auth resolves through dest_provider → providers[name].

    Auth source types

    Provider.auth dispatches on type:. Two static loaders return whatever the underlying source holds; OAuth loaders own the refresh lifecycle in-process.

    typeWhat it isWhen to use
    commandRun a shell command, return stdoutStatic API keys, opnix/SOPS secret commands, env-var injection
    fileRead a file, return contentsStatic API keys stored in a managed secret file
    anthropic_oauthIn-process Anthropic OAuth refreshShare ~/.claude/.credentials.json with Claude Code CLI
    google_oauthIn-process Google/Gemini OAuth refreshShare ~/.gemini/oauth_creds.json with gemini-cli
    codex_oauthIn-process Codex OAuth refreshShare ~/.codex/auth.json with Codex CLI

    command and file are not OAuth — they have no expiry awareness and never call out to a refresh endpoint. ccproxy reads them on every resolve; rotation happens out-of-band through whichever secret manager produced the value.

    anthropic_oauth and google_oauth extend the same form-encoded AuthSource base. codex_oauth follows Codex's ChatGPT auth-file schema: it reads ~/.codex/auth.json, refreshes JWT access tokens through OpenAI's OAuth endpoint, atomically writes the updated token envelope, and stamps companion ChatGPT-Account-ID / X-OpenAI-Fedramp headers derived from the same file.

    A static API key for DeepSeek alongside an OAuth-refresh entry for Anthropic:

    ccproxy:
      providers:
        anthropic:
          auth:
            type: anthropic_oauth
            file_path: ~/.claude/.credentials.json
            access_path: claudeAiOauth.accessToken
            refresh_path: claudeAiOauth.refreshToken
            expiry_path: claudeAiOauth.expiresAt
            header: authorization
          base_url: https://api.anthropic.com
          path: /v1/messages
          type: anthropic
    
        deepseek:
          auth:
            type: command
            command: "printenv DEEPSEEK_API_KEY"
            header: x-api-key
          base_url: https://api.deepseek.com
          path: /anthropic/v1/messages
          type: anthropic
    

    Hook config: hooks in each stage list are topologically sorted by @hook(reads=..., writes=...) dependency declarations and executed in parallel DAG order. Hooks can be parameterized:

    hooks:
      outbound:
        - hook: ccproxy.hooks.some_hook
          params:
            key: value
    

    Per-request overrides via header: x-ccproxy-hooks: +hook_name,-other_hook.

    Sharing credentials with the Claude Code CLI

    If you also run the Claude Code CLI on the same machine, point ccproxy's anthropic provider at the CLI's own credential file. Both tools then read and write the same JSON, so a refresh from either side is visible to the other on the next read.

    ccproxy:
      providers:
        anthropic:
          auth:
            type: anthropic_oauth
            file_path: ~/.claude/.credentials.json
            access_path: claudeAiOauth.accessToken
            refresh_path: claudeAiOauth.refreshToken
            expiry_path: claudeAiOauth.expiresAt
            header: authorization
          base_url: https://api.anthropic.com
          path: /v1/messages
          type: anthropic
    

    The four glom paths declare the file's schema ({claudeAiOauth: {accessToken, refreshToken, expiresAt, ...}}), so existing siblings the CLI maintains (scopes, subscriptionType, etc.) are preserved on write. The atomic write-back (tmpfile → fsync → rename → chmod 0600) keeps the file consistent even if both tools refresh concurrently.

    Hook Pipeline

    HookStagePurpose
    inject_authinboundSentinel key (sk-ant-oat-ccproxy-{provider}) substitution from providers
    extract_session_idinboundParses metadata.user_id → stores session_id on ctx.metadata.session_id
    gemini_clioutboundSingle hook for Gemini sentinel-key traffic: v1internal envelope wrap, conditional UA masquerade, path rewrite to cloudcode-pa, and unwrap on the way back
    pplx_stamp_headersoutboundConverts the Perplexity Pro sentinel token into the browser-shaped cookie/auth header bundle
    inject_mcp_notificationsoutboundInjects buffered MCP terminal events as synthetic tool_use/tool_result
    verbose_modeoutboundStrips redact-thinking-* from anthropic-beta header
    shapeoutboundReplays a packaged or local shape and stamps content fields from the incoming request
    commitbee_compatoutboundLast-mile compatibility shim for commitbee

    Shape Replay

    Anthropic, Gemini, and Codex/OpenAI Responses traffic depend on shape replay. ccproxy ships sanitized packaged defaults for all three providers. For Anthropic, the shape is the only source of the Claude Code identity headers (user-agent, anthropic-beta, etc.) and the billing-header block — there is no synthetic-identity fallback hook anymore. For Codex, the packaged openai_responses shape carries only the public request envelope; account routing headers come from codex_oauth at runtime. Normal users do not need to capture a shape before using the packaged defaults. If a packaged shape goes stale for a future upstream SDK release, update ccproxy to a release with refreshed packaged defaults. If no fixed release is available yet, follow the manual rescue path in Request Shaping.

    Codex traffic is routed to ChatGPT's Codex backend, not the public OpenAI Responses endpoint. Use streaming Responses requests; ccproxy preserves the captured Codex instruction envelope, normalizes public SDK string input into Codex's verbose message form, and enforces store: false. Public Responses fields unsupported by the Codex backend, such as max_output_tokens, should be left unset for the default codex provider.

    CLI Reference

    ccproxy start                          # Start proxy and inspector stack (foreground)
    ccproxy run [--capture] -- <command>   # Run command with proxy env vars / WireGuard namespace jail
    ccproxy status [--json]                # Show running state
    ccproxy init [--force]                 # Initialize config in ~/.config/ccproxy/
    ccproxy logs [-f] [-n LINES]           # View logs
    
    # Flow inspection (all commands accept repeatable --jq flow-set selectors)
    ccproxy flows list [--json] [--jq FILTER]...     # List flow set
    ccproxy flows dump [--jq FILTER]...              # Multi-page HAR of flow set
    ccproxy flows diff [--jq FILTER]...              # Sliding-window diff across set
    ccproxy flows compare [--jq FILTER]...           # Per-flow client-vs-forwarded diff
    ccproxy flows clear [--all] [--jq FILTER]...     # Clear flow set (--all bypasses filters)
    
    # Shape artifacts
    ccproxy shapes audit [--directory PATH]          # Audit packaged .mflow artifacts
    ccproxy shapes save PROVIDER [--jq FILTER]...    # Advanced: write/update local shape patch
    ccproxy shapes save PROVIDER --mflow             # Advanced: write request-only .mflow override
    

    ccproxy run (without --capture) sets ANTHROPIC_BASE_URL, OPENAI_BASE_URL, and OPENAI_API_BASE in the subprocess environment and routes traffic through the reverse proxy listener.

    ccproxy run --capture wraps the command in a rootless WireGuard network namespace jail — all outbound traffic is transparently intercepted regardless of SDK configuration.

    Inspecting Flows

    All flows subcommands operate on a resolved set of flows. The set is built by a pipeline:

    GET /flows → config default_jq_filters → CLI --jq filters → final set
    

    The --jq flag is repeatable. Each filter is a flow-set selector: it must consume a JSON array and produce one JSON array of flow objects. Multiple filters chain via jq’s | operator:

    # Only Anthropic API calls
    ccproxy flows list --jq 'map(select(.request.pretty_host == "api.anthropic.com"))'
    
    # Only POST /v1/messages
    ccproxy flows list --jq 'map(select(.request.path | startswith("/v1/messages")))'
    
    # Only paths containing /api/ws
    ccproxy flows list --jq 'map(select((.request.path // "") | contains("/api/ws")))'
    
    # Chain filters: Anthropic POSTs with 200 status
    ccproxy flows list \
      --jq 'map(select(.request.pretty_host == "api.anthropic.com"))' \
      --jq 'map(select(.request.method == "POST"))' \
      --jq 'map(select(.response.status_code == 200))'
    

    --jq is for selecting the flow objects that list, dump, diff, compare, clear, repl, and shapes save operate on. For arbitrary projections or reporting, emit JSON and pipe it to jq:

    ccproxy flows list --json | jq -r '.[].request.path | select(contains("/api/ws"))'
    

    Config-level defaults apply before CLI filters, so you can set a baseline in ccproxy.yaml:

    flows:
      default_jq_filters:
        - 'map(select(.request.path | startswith("/v1/messages")))'
    

    Listing flows

    # Rich table (default)
    ccproxy flows list
    
    # Raw JSON
    ccproxy flows list --json
    
    # Filtered table
    ccproxy flows list --jq 'map(select(.request.path | startswith("/v1/messages")))'
    
    ┏━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┓
    ┃ ID       ┃ Method  ┃  Code ┃ Host      ┃ Path      ┃ UA       ┃ Time         ┃
    ┡━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━┩
    │ 3c9c224c │ POST    │   200 │ api.anth… │ /v1/mess… │ claude-… │ 42 seconds   │
    │          │         │       │           │           │ (extern… │ ago          │
    │ 6cc161e9 │ POST    │   200 │ api.anth… │ /v1/mess… │ claude-… │ 29 seconds   │
    │          │         │       │           │           │ (extern… │ ago          │
    └──────────┴─────────┴───────┴───────────┴───────────┴──────────┴──────────────┘
    

    Diffing consecutive requests

    flows diff performs a sliding-window unified diff over request bodies. For a set [f0, f1, f2], it produces diffs f0→f1 and f1→f2. Requires at least 2 flows.

    ccproxy flows diff --jq 'map(select(.request.path | startswith("/v1/messages")))'
    
    --- flow:3c9c224c
    +++ flow:6cc161e9
    @@ -26,7 +26,7 @@
             {
               "type": "text",
    -          "text": "what's 2+2",
    +          "text": "what's 3+3",
               "cache_control": {
    

    Comparing client vs forwarded requests

    flows compare diffs the pre-pipeline client request against the post-pipeline forwarded request for each flow. This shows what ccproxy’s hook pipeline and lightllm transform actually changed. Supports 1+ flows.

    ccproxy flows compare --jq 'map(select(.request.path | startswith("/v1/messages")))'
    

    When the pipeline rewrites the request (e.g. Anthropic → Gemini transform), you’ll see URL changes and body diffs:

    ╭──────── URL change — abc12345 ────────╮
    │ - https://api.anthropic.com/v1/messages│
    │ + https://generativelanguage.googleapi…│
    ╰───────────────────────────────────────╯
    ╭──────── Body diff — abc12345 ─────────╮
    │ --- client:abc12345                    │
    │ +++ forwarded:abc12345                 │
    │ @@ -1,5 +1,5 @@                       │
    │ ...                                    │
    ╰───────────────────────────────────────╯
    

    When no transform is applied (same-provider passthrough), the output confirms the bodies are identical:

    3c9c224c: request bodies are identical.
    6cc161e9: request bodies are identical.
    

    Dumping HAR

    flows dump exports the flow set as a multi-page HAR 1.2 file. Each flow becomes one page with two entries:

    EntryContent
    entries[2i]Forwarded request + upstream response
    entries[2i+1]Client request (pre-pipeline snapshot) + upstream response
    # Dump all flows to a HAR file (open in Chrome DevTools / Charles / Fiddler)
    ccproxy flows dump > all.har
    
    # Dump only LLM requests
    ccproxy flows dump --jq 'map(select(.request.path | startswith("/v1/messages")))' > llm.har
    
    # Query HAR with jq
    ccproxy flows dump | jq '.log.pages | length'           # page count
    ccproxy flows dump | jq '.log.entries[0].request.url'    # first forwarded URL
    

    Clearing flows

    # Clear only matching flows (respects --jq filters)
    ccproxy flows clear --jq 'map(select(.request.path | startswith("/v1/messages")))'
    # => Cleared 2 flow(s).
    
    # Clear everything (bypasses all filters)
    ccproxy flows clear --all
    

    Development

    git clone https://github.com/starbaser/ccproxy.git
    cd ccproxy
    direnv allow        # activates the nix devShell
    
    just up             # start dev services (process-compose, detached, port 4001)
    just down           # stop dev services
    just test           # uv run pytest
    just lint           # uv run ruff check .
    just fmt            # uv run ruff format .
    just typecheck      # uv run mypy src/ccproxy
    

    The dev instance runs on port 4001 (production default: 4000). Inspector UI at port 8083. Config and cert store at .ccproxy/ inside the project directory.

    Troubleshooting

    Inspector prerequisites

    See Installation for the per-distro system package list. ccproxy run --capture checks slirp4netns, wg, unshare, nsenter, ip on PATH and prints the missing ones with package hints. The reverse proxy (ccproxy start) does not require any of these and works on macOS too.

    Auth token errors

    Auth tokens are loaded at startup from each providers[name].auth source. If a token command fails or returns an empty string, the sentinel key substitution is skipped and the raw sentinel key is forwarded — which will be rejected by the provider. Verify your token command works standalone:

    jq -r '.claudeAiOauth.accessToken' ~/.claude/.credentials.json
    

    OAuth-source providers (anthropic_oauth, google_oauth) refresh in-process via AuthSource.resolve() whenever the cached access token is within 60s of expiry — this fires at startup (_load_credentials()) and on each header injection. On a 401 from upstream, AuthAddon re-resolves the credential source and replays the request with the new token. Static command / file loaders have no refresh capability — they read whatever's on disk every time and rely on whichever secret manager owns rotation. Fix your providers entries and restart ccproxy start if static tokens were stale at startup.

    TLS certificate errors in ccproxy run

    ccproxy run (without --capture) does not intercept TLS. It only sets env vars pointing at the reverse proxy HTTP listener. If the target tool performs its own TLS verification against the upstream API, no cert installation is needed.

    ccproxy run --capture intercepts all traffic including TLS. The mitmproxy CA is combined with system CAs and injected via SSL_CERT_FILE, NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, and CURL_CA_BUNDLE into the subprocess environment automatically.

    If a tool still fails certificate verification, ensure the mitmproxy CA (~/.config/ccproxy/mitmproxy-ca-cert.pem) is trusted by the tool’s runtime.