Last active
February 6, 2026 09:10
-
-
Save wbern/93afa23eaa07a684fd177a4f73ac4dad to your computer and use it in GitHub Desktop.
Setup script for running Gemini through Claude Code Router
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Setup script for running Gemini through Claude Code Router (macOS) | |
| # https://github.com/wbern/claude-code-router | |
| set -e | |
| echo "π Setting up Gemini for Claude Code..." | |
| # Check if CCR is installed | |
| if ! command -v ccr &> /dev/null; then | |
| echo "β Claude Code Router (ccr) is not installed." | |
| echo "" | |
| echo "Install it first:" | |
| echo " git clone https://github.com/wbern/claude-code-router.git ~/.claude-code-router/fork" | |
| echo " cd ~/.claude-code-router/fork && pnpm install && pnpm build && pnpm link --global" | |
| exit 1 | |
| fi | |
| # Backup existing configs | |
| backup_if_exists() { | |
| if [ -f "$1" ]; then | |
| BACKUP="$1.backup.$(date +%Y%m%d_%H%M%S)" | |
| cp "$1" "$BACKUP" | |
| echo "β οΈ Backed up existing $1 to $BACKUP" | |
| fi | |
| } | |
| backup_if_exists ~/.claude-code-router/config.json | |
| backup_if_exists ~/.claude-code-router/custom-router.js | |
| backup_if_exists ~/.claude-gemini/settings.json | |
| # Generate random API key for local router | |
| APIKEY="ccr-local-$(openssl rand -hex 8)" | |
| # Create directories | |
| mkdir -p ~/.claude-gemini | |
| mkdir -p ~/.claude-code-router/logs | |
| # 1. Router config | |
| cat > ~/.claude-code-router/config.json << EOF | |
| { | |
| "HOST": "127.0.0.1", | |
| "PORT": 3456, | |
| "APIKEY": "$APIKEY", | |
| "CUSTOM_ROUTER_PATH": "$HOME/.claude-code-router/custom-router.js", | |
| "Providers": [ | |
| { | |
| "name": "gemini", | |
| "api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/", | |
| "api_key": "FROM_KEYCHAIN", | |
| "models": ["gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-2.5-flash", "gemini-2.5-pro"], | |
| "transformer": { "use": ["gemini"] } | |
| } | |
| ], | |
| "Router": { | |
| "default": "gemini,gemini-3-pro-preview", | |
| "background": "gemini,gemini-3-flash-preview", | |
| "think": "gemini,gemini-3-pro-preview" | |
| } | |
| } | |
| EOF | |
| echo "β Created ~/.claude-code-router/config.json" | |
| # 2. Custom router for summarization (fixes Gemini 3 Pro empty response issue) | |
| # Gemini 3 Pro can return thinking-only responses during compaction, causing failures. | |
| # This routes summarization requests to Flash which handles them reliably. | |
| # See: https://discuss.ai.google.dev/t/gemini-3-0-pro-preview-with-empty-response-text/109818 | |
| cat > ~/.claude-code-router/custom-router.js << 'EOF' | |
| // Custom router for claude-code-router | |
| // Routes summarization/compaction requests to Gemini 2.5 Flash | |
| // Gemini 3 Pro can return thinking-only responses (no text output) during summarization, | |
| // causing Claude Code's compaction to fail. Flash handles these reliably. | |
| // See: https://discuss.ai.google.dev/t/gemini-3-0-pro-preview-with-empty-response-text/109818 | |
| module.exports = async function router(req, config) { | |
| const system = req.body.system || []; | |
| const systemText = Array.isArray(system) | |
| ? system.map(s => s.text || "").join(" ") | |
| : system; | |
| // Detect Claude Code's summarization/compaction requests | |
| // These prompts contain specific markers from the compact system prompt | |
| const summarizationMarkers = [ | |
| "create a detailed summary", | |
| "summarize this coding conversation", | |
| "Primary Request and Intent", | |
| "Key Technical Concepts", | |
| "conversation so far" | |
| ]; | |
| const isSummarization = summarizationMarkers.some(marker => | |
| systemText.toLowerCase().includes(marker.toLowerCase()) | |
| ); | |
| if (isSummarization) { | |
| console.log("[Custom Router] Detected summarization request, routing to Gemini 2.5 Flash"); | |
| return "gemini,gemini-2.5-flash"; | |
| } | |
| // Return null to use default routing | |
| return null; | |
| }; | |
| EOF | |
| echo "β Created ~/.claude-code-router/custom-router.js (routes summarization to Flash)" | |
| # 3. Gemini settings with sandbox permissions | |
| cat > ~/.claude-gemini/settings.json << EOF | |
| { | |
| "apiKeyHelper": "echo $APIKEY", | |
| "includeCoAuthoredBy": false, | |
| "statusLine": { | |
| "type": "command", | |
| "command": "~/.claude-gemini/statusline.sh" | |
| }, | |
| "permissions": { | |
| "allow": [], | |
| "deny": [ | |
| "Read(**/.env)", | |
| "Read(**/.env.*)", | |
| "Read(**/secrets/**)", | |
| "Read(**/*.pem)", | |
| "Read(**/*.key)", | |
| "Read(**/*credentials*)", | |
| "Read(**/*secret*)", | |
| "Read(**/apikey*)", | |
| "Read(~/.ssh/**)", | |
| "Read(~/.aws/**)", | |
| "Read(~/.gnupg/**)", | |
| "Read(~/.kube/**)", | |
| "Read(~/.netrc)", | |
| "Read(~/.git-credentials)", | |
| "Read(~/.pypirc)", | |
| "Read(~/.docker/**)", | |
| "Read(~/.cargo/credentials*)", | |
| "Read(~/.m2/**)", | |
| "Read(~/.claude/**)", | |
| "Read(~/Library/Keychains/**)", | |
| "Read(~/Library/Cookies/**)", | |
| "Read(~/Library/Accounts/**)", | |
| "Read(~/Library/Mail/**)", | |
| "Read(~/Library/Messages/**)", | |
| "Read(~/Library/Preferences/**)", | |
| "Read(~/Library/Safari/**)", | |
| "Read(~/Library/Application Support/Google/Chrome/**)", | |
| "Read(~/Library/Application Support/Firefox/**)", | |
| "Read(~/Library/Application Support/Microsoft/Edge/**)", | |
| "Read(~/Library/Application Support/1Password/**)", | |
| "Read(~/Library/Saved Application State/**)", | |
| "Bash(rm -rf:*)", | |
| "Bash(rm -r:*)", | |
| "Bash(sudo:*)", | |
| "Bash(su:*)", | |
| "Bash(chmod 777:*)", | |
| "Bash(curl|sh)", | |
| "Bash(wget|sh)", | |
| "Bash(> /dev:*)", | |
| "Bash(mkfs:*)", | |
| "Bash(dd:*)" | |
| ] | |
| }, | |
| "sandbox": { | |
| "enabled": true, | |
| "allowUnsandboxedCommands": true, | |
| "network": { | |
| "allowUnixSockets": ["/private/tmp/com.apple.launchd.*/Listeners"], | |
| "allowLocalBinding": true | |
| } | |
| } | |
| } | |
| EOF | |
| echo "β Created ~/.claude-gemini/settings.json" | |
| # 4. Status line script (visual indicator) | |
| cat > ~/.claude-gemini/statusline.sh << 'EOF' | |
| #!/bin/bash | |
| echo -e "\033[48;5;94m\033[97m π€ GEMINI MODE \033[0m" | |
| EOF | |
| chmod +x ~/.claude-gemini/statusline.sh | |
| echo "β Created ~/.claude-gemini/statusline.sh" | |
| # 5. Symlink commands and plugins (if they exist) | |
| if [ -d ~/.claude/commands ] && [ ! -e ~/.claude-gemini/commands ]; then | |
| ln -s ~/.claude/commands ~/.claude-gemini/commands | |
| echo "β Symlinked commands" | |
| fi | |
| if [ -d ~/.claude/plugins ] && [ ! -e ~/.claude-gemini/plugins ]; then | |
| ln -s ~/.claude/plugins ~/.claude-gemini/plugins | |
| echo "β Symlinked plugins" | |
| fi | |
| echo "" | |
| echo "β Setup complete!" | |
| echo "" | |
| echo "Next steps:" | |
| echo "" | |
| echo "1. Store your Gemini API key in macOS Keychain:" | |
| echo " security add-generic-password -a \"\$USER\" -s gemini-api-key -w 'your-api-key-here'" | |
| echo "" | |
| echo " Get your key at: https://aistudio.google.com/apikey" | |
| echo "" | |
| echo "2. Add these aliases to your ~/.zshrc:" | |
| echo "" | |
| cat << 'ALIASES' | |
| # Gemini via Claude Code Router | |
| gemini() { | |
| unset ANTHROPIC_AUTH_TOKEN | |
| CLAUDE_CONFIG_DIR=~/.claude-gemini ANTHROPIC_BASE_URL=http://127.0.0.1:3456 claude "$@" | |
| } | |
| alias gr='gemini --resume' | |
| alias gyolo='gemini --dangerously-skip-permissions' | |
| alias gyolop='gemini --dangerously-skip-permissions -p' | |
| alias gyolor='gemini --dangerously-skip-permissions --resume' | |
| ALIASES | |
| echo "" | |
| echo "3. Start the router: ccr start &" | |
| echo "" | |
| echo "4. Launch Gemini: gyolo" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment