@cokefenta/pi-switch
v0.3.9
Published
Lightweight profile switcher for pi models.json
Readme
pi-switch
TUI + CLI dual-mode profile switcher for pi agent
Manage provider profiles and run a local model-name routing gateway with failover — via an interactive TUI or CLI.
📸 Screenshots
📥 Installation
# npm (recommended)
npm install -g @cokefenta/pi-switch
# or via pi
pi install npm:@cokefenta/pi-switchBuild from source (requires Node.js >= 20, Rust 1.80+):
git clone https://github.com/user/pi-switch.git
cd pi-switch
npm install
npm run build:native
node bin/pi-switch.js tuiSystem Compatibility
Supported platforms:
- ✅ Windows (x64)
- ✅ macOS (Intel & Apple Silicon)
- ✅ Linux (x64) - glibc & musl
Linux users: This package includes prebuilt binaries for both glibc and musl systems. If you encounter a GLIBC version error, the package will automatically fallback to the musl binary which has broader compatibility.
Troubleshooting GLIBC errors:
# If you see "GLIBC_X.XX not found", build from source:
npm install -g @cokefenta/pi-switch --build-from-source🚀 Quick Start
pi-switch tui # Interactive TUI (recommended)
pi-switch webui start # Browser UI at http://127.0.0.1:43110
pi-switch doctor # Run environment diagnosticsThree ways, one core. CLI, TUI, and WebUI are thin adapters over the same Rust core. See WEBUI_GUIDE.md for the WebUI and how to keep the three interfaces in sync.
Essential CLI Commands
# Provider management
pi-switch provider add <name> [--preset <id>] [--api-key <key>]
pi-switch provider list
pi-switch provider show <name>
pi-switch provider delete <name>
pi-switch provider expose <name> <model-ids...> # Expose models to pi agent
pi-switch provider fetch-models <name> # Fetch models from API
# Proxy (gateway)
pi-switch proxy failover <p1,p2,...> # Same-model fallback chain
pi-switch proxy start --daemon # Start proxy daemon
pi-switch proxy status
# WebUI (browser config)
pi-switch webui start [--host <ip>] [--port <port>] [--daemon]
pi-switch webui status
pi-switch webui stop
# Other
pi-switch presets list # List built-in presets
pi-switch config show # Display current config
pi-switch config backups # List backup files
pi-switch config export <passphrase> # Encrypted export
pi-switch config import <path> <passphrase> # Encrypted import
pi-switch stats # View request statistics✨ Features
| Category | Highlights |
|----------|------------|
| 🔌 Provider Management | CRUD, duplicate, search/filter, model management, expose to pi agent |
| 💡 Built-in Presets | OpenRouter, Anthropic, DeepSeek, SiliconFlow, OpenAI — add profiles instantly |
| 🌉 Model-Name Gateway | Stateless routing by profile/model in the request body, SSE streaming, User-Agent disguise, request-body filtering, OpenAI ↔ Anthropic conversion, failover, circuit breaker |
| 🖥️ Interactive TUI | ratatui-powered, Dracula theme, mouse support, vim keys (hjkl) |
| 🌐 Bilingual | English / 中文, persisted to config, toggle in Settings |
| 📊 Usage Stats | Per-provider, per-model request metrics & latency |
| 💾 Backup & Sync | Auto-backup on mutation, AES-256-CBC encrypted export/import |
| 🩺 Diagnostics | doctor command checks config, models.json, structure |
🎯 Core Workflow
Gateway Routing & Failover
graph LR
subgraph Setup["⚙️ Setup"]
A[Add Provider] --> B[Configure Models]
B --> C[Expose to Pi]
C --> D[Set Failover Chain]
end
subgraph Runtime["🚀 Runtime"]
E["Request<br/>model: provider-a/gpt-5.4"] --> F{Resolve Route}
F --> G[Try provider-a]
G --> H{Success?}
H -->|✓| I[Response]
H -->|✗ 429/5xx| J[Try provider-b]
J --> K{Success?}
K -->|✓| I
K -->|✗| L[Circuit Breaker]
L --> M[60s Cooldown]
M --> N[Half-Open Probe]
N -->|✓| G
N -->|✗| M
end
Setup --> Runtime
style A fill:#50fa7b,stroke:#50fa7b,color:#282a36
style E fill:#8be9fd,stroke:#8be9fd,color:#282a36
style I fill:#50fa7b,stroke:#50fa7b,color:#282a36
style L fill:#ff5555,stroke:#ff5555,color:#f8f8f2Step by Step
1. Add a provider (CLI or TUI)
pi-switch provider add provider-a --api openai-completions --base-url https://api.example.com/v1 \
--api-key '$API_KEY' --models gpt-5.4,claude-sonnet-4-5In TUI: Profiles → a → fill form → Ctrl+S
2. Expose models to pi agent — choose which models appear in ~/.pi/agent/models.json
pi-switch provider expose provider-a gpt-5.4In TUI: Profiles → select provider → x
3. Start the proxy — it writes a single pi-switch gateway provider to pi
pi-switch proxy failover provider-b,provider-c # optional same-model fallback
pi-switch proxy start --daemon4. Use in pi — select the pi-switch provider, then pick a profile/model like provider-a/gpt-5.4
How Gateway Routing Works
Requests are routed by the model name in the request body — no out-of-band state, no "current target":
- Model-name routing —
"model": "provider-a/gpt-5.4"resolves to profileprovider-a, real modelgpt-5.4; the proxy rewrites the body before forwarding upstream - Single gateway provider — pi sees one
pi-switchprovider advertising every exposed model asprofile/realModelId; switching model in pi = sending a different model string = instant routing change - Automatic failover — same-model fallback across the configured chain on 429/5xx errors or network failures
- Circuit breaker — after 3 consecutive failures, provider enters 60s cooldown; auto-recovery on half-open probe success
- Streaming (SSE) — same-format requests (openai→openai, anthropic→anthropic) stream token-by-token; upstream response headers (Content-Type, etc.) are preserved
- OpenAI ↔ Anthropic — transparently converts between chat completions and messages APIs
- User-Agent disguise — built-in presets (Claude Code / Codex / Gemini) send the matching client's real User-Agent (and headers like
anthropic-beta) to pass upstream client checks; settable globally or per-profile
Known limitation — the OpenAI ↔ Anthropic conversion path can't stream: it parses the full JSON to convert formats. If pi sends
stream: truebut the model routes to a cross-format upstream (OpenAI request → Anthropic upstream, or vice-versa), the reply comes back as a single non-streamed response. Same-format routes stream normally.
🏗️ Architecture
pi-switch/
├── bin/pi-switch.js # CLI entry point
├── index.js # ESM wrapper for native addon
├── pi-switch-native.cjs # NAPI loader (auto platform detection)
├── src-rust/ # Rust native core (napi-rs)
│ ├── lib.rs # NAPI function exports
│ ├── config.rs # Config load/save, types
│ ├── ops.rs # Core operations
│ ├── presets.rs # Built-in provider presets
│ ├── proxy.rs # Proxy server (gateway routing, failover, circuit breaker)
│ ├── daemon.rs # Daemon lifecycle
│ ├── stats.rs # Request log aggregation
│ ├── sync.rs # Encrypted export/import
│ └── tui/ # Interactive terminal UI (ratatui)
│ ├── app.rs # State machine + key handler
│ ├── form.rs # Provider form state
│ ├── i18n.rs # Bilingual (EN/ZH)
│ └── ui/ # Rendering (chrome, pages, overlays)
├── src/ # JavaScript layer (pi extension support)
├── extensions/index.ts # Pi agent extension (/piswitch)
└── Cargo.tomlConfig files:
~/.pi-switch/config.json— profiles, proxy settings, failover chain~/.pi-switch/backups/— timestamped auto-backups on every mutation~/.pi/agent/models.json— pi's provider registry (pi-switch writes a single gateway provider)
❓ FAQ
In pi, open /model and pick any advertised profile/model (e.g. provider-a/gpt-5.4). The proxy routes by the model name in each request — no extra step needed.
To add more models, expose them in TUI (Profiles → select provider → x) or via CLI:
pi-switch provider expose <name> <model-id>...In TUI: Settings → Failover → Enter → enter comma-separated profile names → Enter.
Or via CLI:
pi-switch proxy failover provider-b,provider-cProfiles in the failover chain that expose the same model are tried in order when the primary fails.
The [proxy] badge indicates this profile is a meta-profile (with "proxy": true). Proxy profiles are used to register a pi provider that points to the local gateway. They are excluded from upstream routing.
In the current gateway mode, proxy profiles are typically not needed — the proxy automatically writes a single pi-switch gateway provider to pi's models.json on startup.
The proxy advertises every exposed model as profile/realModelId under a single pi-switch provider. When pi sends a request with "model": "provider-a/gpt-5.4", the proxy:
- Splits on the first
/— profileprovider-a, real modelgpt-5.4 - Routes to the
provider-aprofile's upstream, rewritingbody.modeltogpt-5.4 - On failure (429/5xx), tries the failover chain for any other profile exposing
gpt-5.4
# 1. Expose models (per profile)
pi-switch provider expose provider-a gpt-5.4
pi-switch provider expose provider-b gpt-5.4
# 2. Set failover chain (optional)
pi-switch proxy failover provider-b
# 3. Start proxy daemon
pi-switch proxy start --daemonIn pi, select the pi-switch provider, then provider-a/gpt-5.4. The model name in each request determines the route — no "target" to manage.
Some upstream channels only accept requests from whitelisted clients (checking the User-Agent name prefix). pi-switch has three built-in presets that send the matching client's real identity:
| Preset | User-Agent | Extra headers |
|--------|------------|---------------|
| Claude Code | claude-cli/2.1.161 (external, cli) | anthropic-version, anthropic-beta |
| Codex | codex_cli_rs/0.1.0 | — |
| Gemini | gemini-cli/0.1.5 | x-goog-api-client |
- Global:
Settings → User-Agent, cycle with←/→. - Per-profile: in a profile's detail view press
uto cycle; a per-profile value overrides the global one. Useful when only some upstreams enforce a UA whitelist.
Note: this only passes checks that look at the client name. It does not fabricate deeper per-request tokens (turn state, session ids), which strict first-party endpoints validate.
Everything under ~/.pi-switch/. Pi's own registry is ~/.pi/agent/models.json. No data leaves your machine.
🛠️ Development
npm run build:native:debug # Build Rust addon (debug)
npm run build:native # Build Rust addon (release)
cargo build # Rust-only build
cargo clippy # Lint
cargo fmt # Format
cargo test --release --lib # Run unit testsNote: Stop the TUI/daemon before npm run build:native to avoid file-lock errors on Windows.
🙏 Acknowledgments
- cc-switch — the original TUI-based profile switcher for Claude Code, which pioneered the interactive terminal UI pattern and proxy failover design
- cc-switch-cli — the CLI counterpart, providing a clean command-line interface for provider management
Thanks also to the LINUX DO community for the discussions that sparked this project.
📜 License
MIT
