Skip to main content

Local backend bridge for Claude Code, Codex, and Pi.

Project description

CCL — Claude Codex Local

License: MIT Python 3.10+ CI Code style: ruff PyPI version PyPI downloads PyPI total downloads

Hit your limit? Need privacy? Just swap the model.

One alias. Claude Code, Codex, or Pi on a local model. Skills, agents, MCP servers — all intact.

Quota hit mid-session? cc keeps you going on a local model, no context lost. Code that can't leave your machine? Everything runs offline after model download. Don't want to rewire your workflow? Your ~/.claude, skills, agents, and MCP servers carry over untouched.

Get Started → · Landing page →


Features

Feature What you get
Ollama first-class ollama launch — no duplicated config, no custom Modelfiles
Config untouched All skills, statusline, agents, plugins, and MCP servers carry over
Smart model selection llmfit analyses your hardware and picks the best quantization that fits (lazy hardware scan; pass --run-llmfit to refresh)
Resume on failure Wizard persists progress — --resume picks up from the last completed step
Idempotent aliases Re-running the wizard replaces the existing alias block, never appends
Cloud fallback Run claude / codex / pi directly (no prefix) to switch back instantly

Quick Start

Install from PyPI (recommended)

pip install claude-codex-local

Or with uv:

uv tool install claude-codex-local

Then run the setup wizard:

ccl

One-command install (no clone required)

bash <(curl -sSL https://raw.githubusercontent.com/luongnv89/claude-codex-local/main/install.sh)

Or with wget:

bash <(wget -qO- https://raw.githubusercontent.com/luongnv89/claude-codex-local/main/install.sh)

Use bash <(...), not curl … | bash. The wizard is interactive and needs a real TTY — piping steals stdin.

Override defaults with env vars:

CCL_REF=v0.13.0 CCL_INSTALL_DIR=~/tools/claude-codex-local \
  bash <(curl -sSL https://raw.githubusercontent.com/luongnv89/claude-codex-local/main/install.sh)

Install from a clone

git clone https://github.com/luongnv89/claude-codex-local.git
cd claude-codex-local
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
ccl

After setup

Reload your shell so the alias is available:

source ~/.zshrc   # or source ~/.bashrc

Then run:

cc        # Claude Code → local model
cx        # Codex CLI → local model
cp        # Pi → local model

Wizard Steps

graph TD
    A[1. Discover environment<br>cached snapshot; lazy hardware scan] --> B[2. Defer install prompts]
    B --> C[3. Pick harness + engine<br>selected choices are checked live]
    C --> D[4. Pick model]
    D --> E[5. Smoke test engine]
    E --> F[6. Wire harness]
    F --> G[7. Install helper + aliases]
    G --> H[8. Verify launch end-to-end]
    H --> I[9. Generate guide.md]

See guide.example.md for the personalized daily-use guide the wizard generates.


Usage

ccl                                             # run the interactive first-run wizard
ccl setup --harness claude --engine ollama      # skip the prefs picker
ccl setup --harness pi --engine ollama          # Pi (https://pi.dev/) + local model
ccl setup --non-interactive                     # CI-friendly install
ccl setup --resume                              # resume after a failure
ccl find-model                                  # standalone model recommendation
ccl doctor                                      # wizard state + presence check
ccl run                                         # launch the configured session interactively
ccl run -p "what is 2+2?"                       # one-shot: drive CCL from another agent
ccl run --native-params -- --dangerously-skip-permissions
                                                # forward harness-native flags verbatim
ccl --version                                   # print version and exit

ccl run -p "<prompt>" runs the harness in non-interactive mode (Claude Code's -p, Codex's exec, Pi's --print) so external agents and CI scripts can drive a local model end-to-end without keystrokes. Without -p, behavior matches the cc / cx / cp alias and the session starts interactively.

Forwarding harness-native flags (--native-params)

ccl run --native-params -- <ARGS…> forwards everything after --native-params verbatim to the launched harness. It is a generic escape hatch for harness options that ccl does not wrap explicitly (e.g. Claude Code's --dangerously-skip-permissions). Must be the last flag on the line; use -- to make the boundary between ccl flags and native ones explicit. ccl does not validate native params — the harness does.

# Claude Code: skip permission prompts (interactive)
ccl run --native-params -- --dangerously-skip-permissions

# Codex: set extra exec-time flag (one-shot)
ccl run -p "summarize this repo" --native-params -- --some-codex-flag

# Pi: forward any pi-native option
ccl run --native-params -- --some-pi-option value

Advanced / debug (no user binary — run as a Python module):

python -m claude_codex_local.core profile      # full hardware profile as JSON
python -m claude_codex_local.core recommend    # llmfit-only model recommendation
python -m claude_codex_local.core adapters     # list all engine adapters

Sharing Context Between Agents experiment

ccl run automatically bridges conversation context across local harnesses so you can hand off mid-task between Claude Code, Codex, and Pi without re-explaining what you were doing. The bridge runs in two halves:

  • Post-run capture (both paths) — after the harness exits, CCL reads its native session file for the current $PWD (~/.claude/projects/..., ~/.codex/sessions/..., ~/.pi/agent/sessions/...) and imports the cleaned, redacted messages into ~/.claude-codex-local/sessions/<harness>.jsonl.
  • Pre-run injection (one-shot only) — when you ccl run -p PROMPT, the freshest other harness's transcript for $PWD is rendered as a [prior context, agent=…]\n…\n[end prior context] block and prepended to PROMPT. Interactive sessions don't inject — each harness has its own --resume/--continue and stdin is the user's TTY. The cc-interactive → cx-one-shot handoff is the prototypical flow.
ccl run -p "continue from where claude left off"   # auto-injects context if claude has one
ccl run --no-context -p "fresh start"              # disable bridge (also CCL_SESSION_BRIDGE=0)
ccl session list                                   # show captured per-harness JSONL files
ccl session show claude                            # print one harness's messages (JSON)
ccl session sync --from claude --to codex          # manual copy if you want to force it
ccl session truncate codex --keep 50               # keep only the last N (--keep required)
ccl session clear codex                            # delete one harness's file

Scope guards keep the bridge predictable:

  • cwd-scoped — context only flows for the same repository. Switching directories starts fresh.
  • 7-day staleness cap — native sessions older than a week are ignored so months-old transcripts can't get silently re-injected. The injection banner shows the source's age (last activity 6m ago) so you can spot a stale handoff before it ships to the model.
  • Source picker — when multiple non-self harnesses have history, the most recently modified file wins. Same-harness one-shot continuity (cc -p "foo" then cc -p "bar") is not covered — use Claude Code's own --resume for that.
  • Boilerplate filterAGENTS.md / CLAUDE.md re-dumps, slash-command echoes, skill loads, tool calls, reasoning traces, and other harness internals are dropped on import; only user/assistant text survives.
  • Redaction — best-effort scrub of common token shapes (OpenAI, Anthropic, AWS, GitHub PAT/OAuth, Slack, GitLab, Google API) on every import and sync. Treat the JSONL files as semi-sensitive — the scrub covers known patterns, not arbitrary secrets in prose.
  • Idempotent — re-running ccl run against the same native file does not duplicate messages; a content-hash dedup key skips already-imported rows.

The capture path is exercised on both interactive and one-shot via the same post-run import. The injection path is currently wired only on the one-shot (-p) branch.

State directory can be overridden with CLAUDE_CODEX_LOCAL_STATE_DIR; the native-home base can be overridden with CCL_NATIVE_HOME_OVERRIDE (useful for tests and CI). --keep is required on truncate to prevent accidental wipes; use ccl session clear if you want to remove the file entirely.


Prerequisites

  • macOS or Linux with zsh or bash
  • Python 3.10+
  • At least one harness: Claude Code, Codex CLI, or Pi (npm install -g @earendil-works/pi-coding-agent) — Pi is the model-agnostic terminal coding harness whose tagline is “There are many agent harnesses, but this one is yours.”
  • At least one engine: Ollama (recommended), LM Studio, vLLM, llama.cpp, 9router (local cloud-routing proxy), or OpenRouter (hosted SaaS)
  • llmfit on PATH (optional — for automatic model selection)

Proven Paths

Harness Engine Model Status
Claude Code Ollama gemma4:26b Verified end-to-end
Codex CLI Ollama gemma4:26b Verified
Pi Ollama any local tag Supported via isolated Pi models.json and cp alias
Claude Code LM Studio Qwen3 family Blocked — 400 thinking.type; wizard warns and recommends alternatives
Any llama.cpp any Inline-env code path exists, no live proof yet
Any vLLM any New in 0.8.0 — adapter shipped with tests
Claude Code 9router kr/claude-sonnet-4.5 New in 0.9.0 — cloud-routed via cc9 alias; existing cc is untouched
Codex CLI 9router kr/claude-sonnet-4.5 New in 0.9.0 — cloud-routed via cx9 alias; existing cx is untouched
Pi 9router kr/claude-sonnet-4.5 Cloud-routed via cp9; existing cp is untouched
Claude Code OpenRouter anthropic/claude-sonnet-4.6 Hosted SaaS via cco alias; existing cc is untouched (#83)
Codex CLI OpenRouter anthropic/claude-sonnet-4.6 Hosted SaaS via cxo alias; existing cx is untouched (#83)
Pi OpenRouter anthropic/claude-sonnet-4.6 Hosted SaaS via cpo; existing cp is untouched (#83)

9router quick-start

9router is a local proxy that exposes an OpenAI-compatible API on http://localhost:20128/v1 and routes calls to cloud models such as kr/claude-sonnet-4.5. Picking 9router as the engine adds a new cc9 (Claude), cx9 (Codex), or cp9 (Pi) alias and leaves your existing cc / cx / cp aliases untouched.

Installing and running 9router

Step 1: Install 9router

# Using npm (recommended)
npm install -g 9router

# Or using yarn
yarn global add 9router

# Or using pnpm
pnpm add -g 9router

Step 2: Get your API key

  1. Visit the 9router dashboard and sign up or log in
  2. Navigate to API Keys section
  3. Create a new API key and copy it

Step 3: Start the 9router service

# Start 9router with your API key
9router start --api-key YOUR_API_KEY_HERE

# Or set it as an environment variable
export ROUTER9_API_KEY=YOUR_API_KEY_HERE
9router start

# The service will start on http://localhost:20128

Step 4: Verify 9router is running

# Check if the service is responding
curl http://localhost:20128/v1/models

# You should see a list of available models

Step 5: Configure CCL to use 9router

# Interactive setup (wizard will prompt for API key)
ccl setup --engine 9router

# Non-interactive (CI / scripted):
CCL_9ROUTER_API_KEY=<paste-here> CCL_9ROUTER_MODEL=kr/claude-sonnet-4.5 \
  ccl setup --engine 9router --harness claude --non-interactive

How the wizard configures 9router

The wizard:

  1. Asks for the 9router API key and writes it to ~/.claude-codex-local/9router-api-key with chmod 0600. The helper script reads this file at exec time via $(cat …) — the key is never embedded in the script body or wizard state file.
  2. Verifies reachability via GET /v1/models only. It deliberately does not call /chat/completions during smoke-test or verify, because 9router routes to paid cloud models. The verification record is {"ok": true, "via": "9router-models-endpoint", "skipped_chat": true}.
  3. Installs cc9 (or cx9) into your shell rc as a new fenced block (# >>> claude-codex-local:claude9 >>>), leaving any existing cc / cx block alone.

Tip: cc9 and cc can coexist on the same machine — pick cc9 when you want to burn cloud quota for a tough prompt, and cc (Ollama / LM Studio / llama.cpp) for everyday work.

Claude Code → 9router env vars

Env var 9router
ANTHROPIC_BASE_URL http://localhost:20128/v1
ANTHROPIC_AUTH_TOKEN $(cat ~/.claude-codex-local/9router-api-key) (read at exec)
ANTHROPIC_API_KEY $(cat ~/.claude-codex-local/9router-api-key) (read at exec)
ANTHROPIC_CUSTOM_MODEL_OPTION <tag> (e.g. kr/claude-sonnet-4.5)
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME 9router <tag>
CLAUDE_CODE_ATTRIBUTION_HEADER "0"
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC "1"

For Codex: OPENAI_BASE_URL=http://localhost:20128/v1, OPENAI_API_KEY=$(cat …).


OpenRouter quick-start

OpenRouter is a hosted-SaaS cloud-routing service that exposes an OpenAI-compatible API at https://openrouter.ai/api/v1 and forwards calls to dozens of cloud models (Claude, GPT-4o, Llama 3.1, Mistral, and many more). Unlike 9router, there is no daemon to install — only an API key. Picking OpenRouter as the engine adds a new cco (Claude), cxo (Codex), or cpo (Pi) alias and leaves your existing cc / cx / cp aliases untouched.

Setting up OpenRouter

Step 1: Get your API key

  1. Visit openrouter.ai/keys and sign up or log in
  2. Create a new API key and copy it
  3. Optionally fund your account or set a per-key spending limit

Step 2: Configure CCL to use OpenRouter

# Interactive setup (wizard will prompt for API key)
ccl setup --engine openrouter

# Non-interactive (CI / scripted):
CCL_OPENROUTER_API_KEY=<paste-here> CCL_OPENROUTER_MODEL=anthropic/claude-sonnet-4.6 \
  ccl setup --engine openrouter --harness claude --non-interactive

How the wizard configures OpenRouter

The wizard:

  1. Asks for the OpenRouter API key and writes it to ~/.claude-codex-local/openrouter-api-key with chmod 0600. The helper script reads this file at exec time via $(cat …) — the key is never embedded in the script body or wizard state file.
  2. Verifies reachability via GET /models only. It deliberately does not call /chat/completions during smoke-test or verify, because OpenRouter routes to paid cloud models. The verification record is {"ok": true, "via": "openrouter-models-endpoint", "skipped_chat": true}.
  3. Installs cco (or cxo / cpo) into your shell rc as a new fenced block (# >>> claude-codex-local:claudeo >>>), leaving any existing cc / cx / cp block alone.

Tip: cco and cc can coexist on the same machine — pick cco when you want a hosted model, and cc (Ollama / LM Studio / llama.cpp) for everyday local work.

Claude Code → OpenRouter env vars

Env var OpenRouter
ANTHROPIC_BASE_URL https://openrouter.ai/api/v1
ANTHROPIC_AUTH_TOKEN $(cat ~/.claude-codex-local/openrouter-api-key) (read at exec)
ANTHROPIC_API_KEY $(cat ~/.claude-codex-local/openrouter-api-key) (read at exec)
ANTHROPIC_CUSTOM_MODEL_OPTION <tag> (e.g. anthropic/claude-sonnet-4.6)
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME OpenRouter <tag>
HTTP_REFERER https://github.com/luongnv89/ccl (OpenRouter attribution header)
X_TITLE claude-codex-local (OpenRouter attribution header)
CLAUDE_CODE_ATTRIBUTION_HEADER "0"
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC "1"

For Codex: OPENAI_BASE_URL=https://openrouter.ai/api/v1, OPENAI_API_KEY=$(cat …).

Override the base URL via CCL_OPENROUTER_BASE_URL and the model via CCL_OPENROUTER_MODEL.


Rollback

# Remove the fenced block from ~/.zshrc (between the marker lines)
rm -rf .claude-codex-local

Each fence block (claude / codex / claude9 / codex9 / claudeo / codexo) is independent — you can remove just one without touching the others. Your ~/.claude and ~/.codex are unchanged.


Architecture details

Three layers

  1. Machine profile + model recommendation (claude_codex_local/core.py) — dumps a JSON snapshot of installed harnesses/engines/llmfit/disk, runs llmfit for ranked model recommendations, and provides a doctor command for pretty-printing wizard state.

  2. Interactive wizard (claude_codex_local/wizard.py) — 9 steps from discovery to ready-to-use daily alias. Persists progress in .claude-codex-local/wizard-state.json so --resume picks up after a failure.

  3. Helper scripts + shell aliases.claude-codex-local/bin/cc (or cx) is a short bash wrapper. For Ollama it runs ollama launch claude|codex --model <tag>. For LM Studio / llama.cpp it sets inline env vars and execs the real harness. A fenced block in ~/.zshrc / ~/.bashrc declares the aliases.

Why ollama launch

ollama launch claude --model <tag> is an official Ollama subcommand that sets the right env vars internally and execs the user's real claude binary against the local daemon — using ~/.claude as-is.

This means:

  • No duplicated ~/.claude directory
  • No custom Modelfile or ollama create
  • No ANTHROPIC_CUSTOM_MODEL_OPTION to manage manually
  • cc just works

Claude Code → LM Studio / llama.cpp env vars

Env var LM Studio llama.cpp
ANTHROPIC_BASE_URL http://localhost:1234 http://localhost:8001
ANTHROPIC_API_KEY lmstudio sk-local
ANTHROPIC_CUSTOM_MODEL_OPTION <tag> <tag>
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME Local (lmstudio) <tag> Local (llamacpp) <tag>
CLAUDE_CODE_ATTRIBUTION_HEADER "0" "0"
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC "1" "1"

Codex CLI → Ollama

ollama launch codex --model <tag> -- --oss --local-provider=ollama

The --oss --local-provider=ollama flags are required after -- because Codex otherwise tries to route through the ChatGPT account and rejects non-OpenAI model names.

Project structure
.
├── claude_codex_local/
│   ├── __init__.py             # Package metadata + __version__
│   ├── wizard.py               # Interactive setup wizard + `ccl` CLI
│   └── core.py                 # Machine profile, engine adapters, llmfit bindings
├── scripts/
│   └── e2e_smoke.sh            # End-to-end smoke test
├── docs/
│   ├── poc-wizard.md           # 9-step wizard architecture
│   ├── poc-architecture.md     # System design overview
│   ├── poc-bootstrap.md        # Bootstrap / install flow
│   └── poc-proof.md            # Design rationale
├── tests/                      # pytest test suite
├── install.sh                  # One-command remote installer
└── pyproject.toml              # Project metadata and tool config
Tech stack
Layer Tool
Language Python 3.10+
UI / prompts questionary, rich
Linting ruff
Type checking mypy
Testing pytest + pytest-cov
Security bandit, detect-secrets
Pre-commit pre-commit
Local state

Everything written by the bridge goes under .claude-codex-local/. Override with CLAUDE_CODEX_LOCAL_STATE_DIR.

Contributing

Contributions are welcome. Read CONTRIBUTING.md before opening a PR.

For security issues, see SECURITY.md.


MIT — © 2026 Luong NGUYEN

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claude_codex_local-0.13.0.tar.gz (198.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_codex_local-0.13.0-py3-none-any.whl (103.8 kB view details)

Uploaded Python 3

File details

Details for the file claude_codex_local-0.13.0.tar.gz.

File metadata

  • Download URL: claude_codex_local-0.13.0.tar.gz
  • Upload date:
  • Size: 198.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for claude_codex_local-0.13.0.tar.gz
Algorithm Hash digest
SHA256 56aac3d97b33f1350d776a3d6cfa19ffe7db4f4a985c995c8df7d2ba00a3642a
MD5 bc4d1373868a1535b6641749c94cc2a3
BLAKE2b-256 5e12018c9619f453d631788db07108b135b6b49531bf8434801f4519bc50fa95

See more details on using hashes here.

File details

Details for the file claude_codex_local-0.13.0-py3-none-any.whl.

File metadata

File hashes

Hashes for claude_codex_local-0.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d6ad8f364bbc1101a59cbf492eacdc611399198be5be888eba907297b730b875
MD5 145d7896fe99862b3a6478ad73f1fcf2
BLAKE2b-256 4a1592bad8c6eec29a283def9cef950d6ede3133ee0db5b7394f3ac7131dcfcf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page