A cross-platform desktop app for managing and switching API keys used by AI coding agents — Claude Code, Codex CLI, Codex App, and Gemini CLI.
AI coding agents read their API keys from environment variables or config files. Switching between providers, accounts, or custom endpoints means manually editing those files every time. Keypilot centralizes that into a single UI: store multiple keys per tool, switch the active one with one click, and launch the tool directly from the app.
Key capabilities:
- Multi-key management — store any number of keys per tool, each with a provider name, API key, base URL, and model
- One-click switching — writes the selected key to the correct location for each tool (env var,
settings.json, or config files), with backup and rollback on failure - Auth source detection — reads where the current key actually comes from (process env,
settings.json, user registry, machine registry) and writes back to the same source - Tool lifecycle — install, uninstall, start, and restart tools from within the app
- System tray — switch keys without opening the main window
- Auto-import — detects the key already configured in a tool on first launch and imports it automatically
- Config backups — keeps up to 5 rotating backups of every config file it touches
- i18n — Chinese (zh-CN) and English (en-US) UI
| Tool | Config location |
|---|---|
| Claude Code | ~/.claude/settings.json → ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL env var |
| Codex CLI | ~/.codex/auth.json + ~/.codex/config.toml |
| Codex App | Same as Codex CLI (shared config group) |
| Gemini CLI | GEMINI_API_KEY / GEMINI_BASE_URL / GEMINI_MODEL env var |
| Layer | Technology |
|---|---|
| Desktop shell | Tauri 2 |
| Backend | Rust (2021 edition) |
| Frontend | React 18 + TypeScript 5 + Vite 5 |
| Serialization | serde / serde_json |
| IPC | Tauri invoke() commands + Tauri events |
| Storage | data.json — atomic write (tmp → rename) with .bak copy |
| Tray | Tauri tray-icon feature |
frontend/src/
api.ts thin invoke() wrappers — no business logic
App.tsx single-page UI
types.ts shared TypeScript types
i18n.ts zh-CN / en-US dictionaries
src-tauri/src/
models.rs KeyRecord, ToolType, SwitchResult, ToolAuthSnapshot, …
storage.rs load/save data.json (atomic write + .bak)
adapters.rs switching logic, auth detection, backup rotation
installer.rs npm install/uninstall, tool start, streaming install-log events
process.rs detect installed tools, is_tool_running, restart_tool
lib.rs Tauri command registration, tray menu, tray event handler
All business logic lives in Rust. The frontend never writes config files directly.
detect_tool_auth_methods returns snapshots sorted by priority. The first one with a value is "effective":
env_process— current process env (read-only)settings_json—~/.claude/settings.json(writable)env_user— HKCU registry on Windows /launchctlon macOS (writable)env_machine— HKLM registry on Windows (read-only)
switch_key routes the write to whichever source is currently effective.
ToolType::Codex and ToolType::CodexApp both read and write the same ~/.codex/auth.json and ~/.codex/config.toml. The frontend mirrors this with toolConfigGroup() (maps codex-app → codex).
Config files get up to 5 rotating backups: .bak1 (newest) through .bak5. The 6th rotation drops .bak5.
# Install frontend dependencies
npm install --prefix frontend
# Run in dev mode (hot-reload frontend + Rust backend)
cargo tauri devnpm run typecheck --prefix frontend
cargo check --manifest-path src-tauri/Cargo.tomlcargo test --manifest-path src-tauri/Cargo.tomlnpm run build --prefix frontend
cargo tauri build --manifest-path src-tauri/Cargo.toml --bundles msi| Platform | Path |
|---|---|
| Windows | %APPDATA%\KeyPilot\data.json |
| macOS | ~/Library/Application Support/KeyPilot/data.json |
The file is written atomically (write to .tmp, then rename). A .bak copy is kept alongside it.
- All user-visible strings must have both
zh-CNanden-USentries infrontend/src/i18n.ts - High-risk writes (config file changes) must use backup + verify + rollback
- TypeScript typecheck and
cargo checkmust pass before merging - Core unit tests must pass (
cargo test) - Commit after every logical change — do not batch unrelated changes