├── test ├── mock-tool-calls │ ├── simple-read.json │ ├── multi-file-glob.json │ ├── grep-search.json │ └── task-search.json ├── manual-test.sh └── test-runner.sh ├── LICENSE ├── hooks ├── config │ └── debug.conf ├── lib │ ├── path-converter.sh │ ├── json-parser.sh │ ├── debug-helpers.sh │ └── gemini-wrapper.sh └── gemini-bridge.sh ├── .gitignore ├── CLAUDE.md ├── uninstall.sh ├── docs └── TROUBLESHOOTING.md ├── CONTRIBUTING.md ├── install.sh └── README.md /test/mock-tool-calls/simple-read.json: -------------------------------------------------------------------------------- 1 | { 2 | "tool_name": "Read", 3 | "tool_input": { 4 | "file_path": "@src/main.py" 5 | }, 6 | "session_id": "test-session", 7 | "transcript_path": "/tmp/test-transcript.jsonl" 8 | } -------------------------------------------------------------------------------- /test/mock-tool-calls/multi-file-glob.json: -------------------------------------------------------------------------------- 1 | { 2 | "tool_name": "Glob", 3 | "tool_input": { 4 | "pattern": "**/*.php", 5 | "path": "/Users/tim/Code/smartphones" 6 | }, 7 | "session_id": "test-session", 8 | "transcript_path": "/tmp/test-transcript.jsonl" 9 | } -------------------------------------------------------------------------------- /test/mock-tool-calls/grep-search.json: -------------------------------------------------------------------------------- 1 | { 2 | "tool_name": "Grep", 3 | "tool_input": { 4 | "pattern": "function.*config", 5 | "path": "/Users/tim/Code/smartphones/src/", 6 | "include": "*.php" 7 | }, 8 | "session_id": "test-session", 9 | "transcript_path": "/tmp/test-transcript.jsonl" 10 | } -------------------------------------------------------------------------------- /test/mock-tool-calls/task-search.json: -------------------------------------------------------------------------------- 1 | { 2 | "tool_name": "Task", 3 | "tool_input": { 4 | "prompt": "Search for all occurrences of 'config' in @./ and analyze the configuration patterns", 5 | "description": "Config pattern analysis" 6 | }, 7 | "session_id": "test-session", 8 | "transcript_path": "/tmp/test-transcript.jsonl" 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Claude-Gemini Bridge Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /hooks/config/debug.conf: -------------------------------------------------------------------------------- 1 | # Debug configuration for Claude-Gemini Bridge 2 | # Debug level: 0=off, 1=basic, 2=verbose, 3=trace 3 | DEBUG_LEVEL=2 4 | 5 | # Save all hook inputs for later analysis 6 | CAPTURE_INPUTS=true 7 | # CAPTURE_DIR will be set dynamically based on script location 8 | 9 | # Enable performance measurements 10 | MEASURE_PERFORMANCE=true 11 | 12 | # Dry-run mode (doesn't actually execute Gemini) 13 | DRY_RUN=false 14 | 15 | # Gemini-specific configuration 16 | GEMINI_CACHE_TTL=3600 # 1 hour cache 17 | GEMINI_TIMEOUT=30 # 30 seconds timeout 18 | GEMINI_RATE_LIMIT=1 # 1 second between calls 19 | GEMINI_MAX_FILES=20 # Max 20 files per call 20 | 21 | # Decision criteria for Gemini delegation 22 | MIN_FILES_FOR_GEMINI=3 # At least 3 files for Task operations 23 | MIN_FILE_SIZE_FOR_GEMINI=20480 # Minimum total size 20KB for delegation 24 | MAX_TOTAL_SIZE_FOR_GEMINI=10485760 # Max 10MB total size (10MB in bytes) 25 | CLAUDE_TOKEN_LIMIT=50000 # Token limit for Claude delegation (50000 == ~200KB) 26 | GEMINI_TOKEN_LIMIT=800000 # Max tokens Gemini can handle 27 | 28 | # Excluded file patterns (never sent to Gemini) 29 | GEMINI_EXCLUDE_PATTERNS="*.secret|*.key|*.env|*.password|*.token|*.pem|*.p12" 30 | 31 | # Automatic cache cleanup 32 | AUTO_CLEANUP_CACHE=true 33 | CACHE_MAX_AGE_HOURS=24 34 | 35 | # Log rotation 36 | AUTO_CLEANUP_LOGS=true 37 | LOG_MAX_AGE_DAYS=7 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Claude-Gemini Bridge .gitignore 2 | 3 | # Runtime files and directories 4 | cache/ 5 | logs/ 6 | debug/captured/ 7 | 8 | # Temporary files 9 | *.tmp 10 | *.temp 11 | *.swp 12 | *.swo 13 | *~ 14 | 15 | # OS generated files 16 | .DS_Store 17 | .DS_Store? 18 | ._* 19 | .Spotlight-V100 20 | .Trashes 21 | ehthumbs.db 22 | Thumbs.db 23 | 24 | # IDE and editor files 25 | .vscode/ 26 | .idea/ 27 | *.sublime-* 28 | .vim/ 29 | .nvim/ 30 | 31 | # Shell history and runtime 32 | .bash_history 33 | .zsh_history 34 | .history 35 | 36 | # Configuration files that might contain secrets 37 | *.local.conf 38 | *.secret 39 | *.key 40 | *.env 41 | config/*.local.* 42 | 43 | # Installation artifacts 44 | *.backup.* 45 | install.log 46 | *.tar.gz 47 | 48 | # Test artifacts 49 | test/temp/ 50 | test/mock-data/temp/ 51 | /tmp/claude_bridge_* 52 | 53 | # Claude Code specific 54 | .claude/ 55 | claude_cache/ 56 | 57 | # Gemini CLI artifacts 58 | .gemini/ 59 | 60 | # Node.js (if any JS tools are added later) 61 | node_modules/ 62 | npm-debug.log* 63 | yarn-debug.log* 64 | yarn-error.log* 65 | 66 | # Python (if any Python tools are added later) 67 | __pycache__/ 68 | *.py[cod] 69 | *$py.class 70 | *.so 71 | .Python 72 | env/ 73 | venv/ 74 | 75 | # Coverage reports 76 | *.lcov 77 | coverage/ 78 | 79 | # Logs 80 | *.log 81 | logs/ 82 | *.log.* 83 | 84 | # Runtime data 85 | pids/ 86 | *.pid 87 | *.seed 88 | *.pid.lock 89 | 90 | # Directory for instrumented libs generated by jscoverage/JSCover 91 | lib-cov/ 92 | 93 | # Coverage directory used by tools like istanbul 94 | coverage/ 95 | 96 | # Dependency directories 97 | vendor/ 98 | 99 | # Optional npm cache directory 100 | .npm 101 | 102 | # Optional REPL history 103 | .node_repl_history 104 | 105 | # Output of 'npm pack' 106 | *.tgz 107 | 108 | # Yarn Integrity file 109 | .yarn-integrity 110 | 111 | # dotenv environment variables file 112 | .env 113 | .env.local 114 | .env.development.local 115 | .env.test.local 116 | .env.production.local 117 | 118 | # Backup files 119 | *.bak 120 | *.backup 121 | *~ 122 | 123 | # Lock files 124 | package-lock.json 125 | yarn.lock 126 | 127 | # JetBrains IDEs 128 | .idea/ 129 | *.iml 130 | *.iws 131 | *.ipr 132 | 133 | # Visual Studio Code 134 | .vscode/ 135 | *.code-workspace 136 | 137 | # Sublime Text 138 | *.sublime-project 139 | *.sublime-workspace 140 | 141 | # Vim 142 | [._]*.s[a-v][a-z] 143 | [._]*.sw[a-p] 144 | [._]s[a-rt-v][a-z] 145 | [._]ss[a-gi-z] 146 | [._]sw[a-p] 147 | 148 | # Emacs 149 | *~ 150 | \#*\# 151 | /.emacs.desktop 152 | /.emacs.desktop.lock 153 | *.elc 154 | auto-save-list 155 | tramp 156 | .\#* 157 | 158 | # Local configuration 159 | local.conf 160 | debug.local.conf 161 | *.local 162 | 163 | # Performance measurement files 164 | *.perf 165 | *.prof 166 | 167 | # Test coverage 168 | .nyc_output/ 169 | coverage.lcov 170 | 171 | # Documentation build artifacts 172 | docs/_build/ 173 | docs/build/ 174 | 175 | # macOS 176 | .AppleDouble 177 | .LSOverride 178 | Icon 179 | .DocumentRevisions-V100 180 | .fseventsd 181 | .TemporaryItems 182 | .VolumeIcon.icns 183 | .com.apple.timemachine.donotpresent 184 | .AppleDB 185 | .AppleDesktop 186 | Network Trash Folder 187 | Temporary Items 188 | .apdisk 189 | 190 | # Windows 191 | Thumbs.db 192 | ehthumbs.db 193 | Desktop.ini 194 | $RECYCLE.BIN/ 195 | *.cab 196 | *.msi 197 | *.msm 198 | *.msp 199 | *.lnk 200 | 201 | # Linux 202 | *~ 203 | .fuse_hidden* 204 | .directory 205 | .Trash-* 206 | .nfs* -------------------------------------------------------------------------------- /hooks/lib/path-converter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Converts Claude's @ notation to absolute paths for Gemini integration 3 | 4 | # Main function for path conversion with security validation 5 | convert_claude_paths() { 6 | local input="$1" 7 | local base_dir="$2" 8 | 9 | # Input validation 10 | if [ -z "$input" ]; then 11 | echo "" 12 | return 0 13 | fi 14 | 15 | if [ -z "$base_dir" ]; then 16 | base_dir="$(pwd)" 17 | fi 18 | 19 | # Security: Block path traversal attempts 20 | if [[ "$input" =~ \.\./|\.\.\\ ]]; then 21 | echo "" # Return empty on path traversal attempt 22 | return 1 23 | fi 24 | 25 | # Security: Validate base_dir is safe 26 | case "$base_dir" in 27 | /etc/*|/usr/*|/bin/*|/sbin/*|/root/*|/home/*/\.ssh/*) 28 | echo "" # Block access to system directories 29 | return 1 30 | ;; 31 | esac 32 | 33 | # Remove trailing slash from base_dir 34 | base_dir="${base_dir%/}" 35 | 36 | # Perform conversions 37 | # @./ -> empty string (current directory) 38 | input="${input//@\.\//}" 39 | 40 | # @/ -> project root (base_dir) 41 | input="${input//@\//$base_dir/}" 42 | 43 | # @filename or @folder/ -> absolute path 44 | # Use sed for @ followed by file/folder name 45 | input=$(echo "$input" | sed -E "s|@([^/[:space:]]+)|$base_dir/\\1|g") 46 | 47 | # Clean up double slashes 48 | input=$(echo "$input" | sed 's|//|/|g') 49 | 50 | echo "$input" 51 | } 52 | 53 | # Extracts file paths from text with security validation 54 | extract_files_from_text() { 55 | local text="$1" 56 | 57 | # Security: Block path traversal attempts 58 | if [[ "$text" =~ \.\./|\.\.\\ ]]; then 59 | echo "" # Return empty on path traversal attempt 60 | return 1 61 | fi 62 | 63 | # Security: Block absolute paths outside working directory 64 | if [[ "$text" =~ /etc/|/usr/|/bin/|/sbin/|/root/|/home/ ]]; then 65 | echo "" # Return empty on system path access 66 | return 1 67 | fi 68 | 69 | # Finds all file paths with extensions 70 | echo "$text" | grep -oE '(/[^[:space:]]+|[^[:space:]]+/[^[:space:]]+)\.[a-zA-Z0-9]+' | sort -u 71 | } 72 | 73 | # Advanced path conversion for complex patterns 74 | convert_advanced_paths() { 75 | local input="$1" 76 | local base_dir="$2" 77 | 78 | # Basic conversion 79 | local result=$(convert_claude_paths "$input" "$base_dir") 80 | 81 | # Glob pattern support 82 | # **/*.py -> all Python files recursively 83 | if [[ "$result" == *"**/"* ]]; then 84 | result=$(echo "$result" | sed "s|\\*\\*/|**/|g") 85 | fi 86 | 87 | echo "$result" 88 | } 89 | 90 | # Test function for path conversion 91 | test_path_conversion() { 92 | local wd="/Users/tim/Code/project" 93 | local failed=0 94 | 95 | echo "Testing path conversion..." 96 | 97 | # Test 1: @./ -> empty string 98 | local result1=$(convert_claude_paths '@./' "$wd") 99 | if [ "$result1" != "" ]; then 100 | echo "❌ Test 1 failed: '@./' -> '$result1' (expected: '')" 101 | failed=1 102 | else 103 | echo "✅ Test 1 passed: '@./' -> ''" 104 | fi 105 | 106 | # Test 2: @src/main.py -> absolute path 107 | local result2=$(convert_claude_paths '@src/main.py' "$wd") 108 | local expected2="$wd/src/main.py" 109 | if [ "$result2" != "$expected2" ]; then 110 | echo "❌ Test 2 failed: '@src/main.py' -> '$result2' (expected: '$expected2')" 111 | failed=1 112 | else 113 | echo "✅ Test 2 passed: '@src/main.py' -> '$expected2'" 114 | fi 115 | 116 | # Test 3: Multiple @ paths 117 | local input3="Check @README.md and @src/*.py files" 118 | local result3=$(convert_claude_paths "$input3" "$wd") 119 | local expected3="Check $wd/README.md and $wd/src/*.py files" 120 | if [ "$result3" != "$expected3" ]; then 121 | echo "❌ Test 3 failed: '$input3' -> '$result3' (expected: '$expected3')" 122 | failed=1 123 | else 124 | echo "✅ Test 3 passed: Multiple @ paths" 125 | fi 126 | 127 | # Test 4: @/ -> base_dir 128 | local result4=$(convert_claude_paths '@/' "$wd") 129 | local expected4="$wd/" 130 | if [ "$result4" != "$expected4" ]; then 131 | echo "❌ Test 4 failed: '@/' -> '$result4' (expected: '$expected4')" 132 | failed=1 133 | else 134 | echo "✅ Test 4 passed: '@/' -> '$expected4'" 135 | fi 136 | 137 | if [ $failed -eq 0 ]; then 138 | echo "🎉 All path conversion tests passed!" 139 | return 0 140 | else 141 | echo "💥 Some tests failed!" 142 | return 1 143 | fi 144 | } 145 | 146 | # If the script is called directly, run tests 147 | if [ "${BASH_SOURCE[0]}" == "${0}" ]; then 148 | test_path_conversion 149 | fi -------------------------------------------------------------------------------- /test/manual-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Interactive test tool for Claude-Gemini Bridge 3 | 4 | echo "🧪 Claude-Gemini Bridge Test Tool" 5 | echo "==================================" 6 | echo "" 7 | 8 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | 10 | # Use current directory structure (git repo = installation) 11 | BRIDGE_DIR="$SCRIPT_DIR/.." 12 | echo "🔧 Running from: $BRIDGE_DIR" 13 | 14 | BRIDGE_SCRIPT="$BRIDGE_DIR/hooks/gemini-bridge.sh" 15 | 16 | # Check if bridge script exists 17 | if [ ! -f "$BRIDGE_SCRIPT" ]; then 18 | echo "❌ Bridge script not found: $BRIDGE_SCRIPT" 19 | exit 1 20 | fi 21 | 22 | # Test scenarios 23 | run_test() { 24 | local test_name="$1" 25 | local json_file="$2" 26 | 27 | echo "🔍 Running test: $test_name" 28 | echo "JSON file: $json_file" 29 | echo "" 30 | 31 | if [ ! -f "$json_file" ]; then 32 | echo "❌ Test file not found: $json_file" 33 | return 1 34 | fi 35 | 36 | echo "📋 Input JSON:" 37 | cat "$json_file" | jq '.' 2>/dev/null || cat "$json_file" 38 | echo "" 39 | 40 | echo "⚡ Bridge Response:" 41 | cat "$json_file" | "$BRIDGE_SCRIPT" | jq '.' 2>/dev/null || cat "$json_file" | "$BRIDGE_SCRIPT" 42 | echo "" 43 | 44 | echo "📊 Check logs for details:" 45 | echo " Debug: tail -f $BRIDGE_DIR/logs/debug/$(date +%Y%m%d).log" 46 | echo " Errors: tail -f $BRIDGE_DIR/logs/debug/errors.log" 47 | echo "" 48 | } 49 | 50 | # Interactive menu 51 | while true; do 52 | echo "Choose a test:" 53 | echo "1) Simple Read (@src/main.py)" 54 | echo "2) Task with Search (config analysis)" 55 | echo "3) Multi-File Glob (@**/*.php)" 56 | echo "4) Grep Search (function.*config)" 57 | echo "5) Custom JSON Input" 58 | echo "6) Replay Captured Call" 59 | echo "7) Test All Library Functions" 60 | echo "8) View Recent Logs" 61 | echo "9) Clear Cache and Logs" 62 | echo "0) Exit" 63 | echo "" 64 | read -p "Selection (0-9): " choice 65 | 66 | case $choice in 67 | 1) 68 | run_test "Simple Read" "$SCRIPT_DIR/mock-tool-calls/simple-read.json" 69 | ;; 70 | 2) 71 | run_test "Task Search" "$SCRIPT_DIR/mock-tool-calls/task-search.json" 72 | ;; 73 | 3) 74 | run_test "Multi-File Glob" "$SCRIPT_DIR/mock-tool-calls/multi-file-glob.json" 75 | ;; 76 | 4) 77 | run_test "Grep Search" "$SCRIPT_DIR/mock-tool-calls/grep-search.json" 78 | ;; 79 | 5) 80 | echo "Enter your JSON (Ctrl+D to finish):" 81 | echo "Example:" 82 | echo '{"tool":"Read","parameters":{"file_path":"@test.txt"},"context":{}}' 83 | echo "" 84 | CUSTOM_JSON=$(cat) 85 | echo "$CUSTOM_JSON" | "$BRIDGE_SCRIPT" | jq '.' 2>/dev/null || echo "$CUSTOM_JSON" | "$BRIDGE_SCRIPT" 86 | echo "" 87 | ;; 88 | 6) 89 | CAPTURE_DIR="$BRIDGE_DIR/debug/captured" 90 | if [ -d "$CAPTURE_DIR" ] && [ "$(ls -A "$CAPTURE_DIR" 2>/dev/null)" ]; then 91 | echo "Available captures:" 92 | ls -la "$CAPTURE_DIR/" 93 | echo "" 94 | read -p "Enter filename: " filename 95 | if [ -f "$CAPTURE_DIR/$filename" ]; then 96 | run_test "Replay: $filename" "$CAPTURE_DIR/$filename" 97 | else 98 | echo "❌ File not found: $filename" 99 | fi 100 | else 101 | echo "❌ No captures available in: $CAPTURE_DIR" 102 | fi 103 | echo "" 104 | ;; 105 | 7) 106 | echo "🧪 Testing all library functions..." 107 | echo "" 108 | echo "Path Converter:" 109 | "$BRIDGE_DIR/hooks/lib/path-converter.sh" 110 | echo "" 111 | echo "JSON Parser:" 112 | "$BRIDGE_DIR/hooks/lib/json-parser.sh" 113 | echo "" 114 | echo "Debug Helpers:" 115 | "$BRIDGE_DIR/hooks/lib/debug-helpers.sh" 116 | echo "" 117 | echo "Gemini Wrapper:" 118 | "$BRIDGE_DIR/hooks/lib/gemini-wrapper.sh" 119 | echo "" 120 | ;; 121 | 8) 122 | echo "📋 Recent Debug Logs (last 20 lines):" 123 | LOG_FILE="$BRIDGE_DIR/logs/debug/$(date +%Y%m%d).log" 124 | if [ -f "$LOG_FILE" ]; then 125 | tail -20 "$LOG_FILE" 126 | else 127 | echo "No logs found for today" 128 | fi 129 | echo "" 130 | 131 | echo "📋 Recent Error Logs:" 132 | ERROR_FILE="$BRIDGE_DIR/logs/debug/errors.log" 133 | if [ -f "$ERROR_FILE" ]; then 134 | tail -10 "$ERROR_FILE" 135 | else 136 | echo "No errors logged" 137 | fi 138 | echo "" 139 | ;; 140 | 9) 141 | echo "🧹 Clearing cache and logs..." 142 | rm -rf "$BRIDGE_DIR/cache/gemini/"* 143 | rm -rf "$BRIDGE_DIR/logs/debug/"* 144 | rm -rf "$BRIDGE_DIR/debug/captured/"* 145 | mkdir -p "$BRIDGE_DIR/cache/gemini" 146 | mkdir -p "$BRIDGE_DIR/logs/debug" 147 | mkdir -p "$BRIDGE_DIR/debug/captured" 148 | echo "✅ Cache and logs cleared" 149 | echo "" 150 | ;; 151 | 0) 152 | echo "👋 Goodbye!" 153 | break 154 | ;; 155 | *) 156 | echo "❌ Invalid selection" 157 | echo "" 158 | ;; 159 | esac 160 | 161 | read -p "Press Enter to continue..." dummy 162 | echo "" 163 | done -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with the Claude-Gemini Bridge repository. 4 | 5 | ## Project Overview 6 | 7 | The Claude-Gemini Bridge is an intelligent hook system that seamlessly integrates Claude Code with Google Gemini for large-scale code analysis tasks. When Claude Code encounters complex analysis requests, the bridge automatically delegates appropriate tasks to Gemini while maintaining Claude's control over the conversation flow. 8 | 9 | ## Architecture 10 | 11 | ### Core Components 12 | 13 | - **Hook System**: Uses Claude Code's PreToolUse hooks to intercept tool calls 14 | - **Path Converter**: Translates Claude's `@` notation to absolute file paths 15 | - **Decision Engine**: Intelligently determines when to delegate tasks to Gemini 16 | - **Caching Layer**: Avoids redundant API calls with content-aware caching 17 | - **Debug System**: Comprehensive logging and performance monitoring 18 | 19 | ### How It Works 20 | 21 | 1. **Interception**: Hook catches Claude's tool calls (Read, Glob, Grep, Task) 22 | 2. **Analysis**: Decision engine evaluates file count, size, and task complexity 23 | 3. **Delegation**: Large or complex tasks are sent to Gemini for processing 24 | 4. **Integration**: Gemini's analysis is seamlessly returned to Claude 25 | 5. **Fallback**: Failed delegations continue with normal Claude execution 26 | 27 | ## Usage Patterns 28 | 29 | ### Automatic Delegation Triggers 30 | 31 | The bridge delegates to Gemini when: 32 | - **Token Limit**: Content exceeds ~50k tokens (~200KB, optimized for Claude's 200k context) 33 | - **Multi-File Tasks**: ≥3 files for Task operations (configurable) 34 | - **Safety Limits**: Content must be ≤10MB and ≤800k tokens for Gemini processing 35 | - **File Exclusions**: Automatically excludes sensitive files (*.secret, *.key, *.env, etc.) 36 | 37 | ### Configuration 38 | 39 | Key settings in `hooks/config/debug.conf`: 40 | 41 | ```bash 42 | # Delegation thresholds (optimized for Claude 200k context) 43 | MIN_FILES_FOR_GEMINI=3 # Delegate Task operations with ≥3 files 44 | MIN_FILE_SIZE_FOR_GEMINI=5120 # Minimum 5KB total size 45 | MAX_TOTAL_SIZE_FOR_GEMINI=10485760 # Maximum 10MB total size 46 | 47 | # Debug and performance 48 | DEBUG_LEVEL=2 # 0=off, 1=basic, 2=verbose, 3=trace 49 | DRY_RUN=false # Test mode without Gemini calls 50 | ``` 51 | 52 | See README.md for complete configuration reference. 53 | 54 | ## Development Guidelines 55 | 56 | ### Code Style 57 | - All shell scripts use bash and follow POSIX compliance where possible 58 | - Comments are in English 59 | - Functions include single-line ABOUTME comments explaining purpose 60 | - Error handling with proper exit codes and logging 61 | 62 | ### Testing 63 | - Use `test/test-runner.sh` for automated testing 64 | - Use `test/manual-test.sh` for interactive debugging 65 | - All library functions include self-tests 66 | 67 | ### Debugging 68 | - Set `DEBUG_LEVEL=3` for maximum verbosity 69 | - Enable `CAPTURE_INPUTS=true` to save tool calls for replay 70 | - Use `DRY_RUN=true` to test delegation logic without calling Gemini 71 | 72 | ## Security Considerations 73 | 74 | ### File Exclusions 75 | The bridge automatically excludes sensitive files: 76 | - `*.secret`, `*.key`, `*.env` 77 | - `*.password`, `*.token`, `*.pem`, `*.p12` 78 | 79 | ### Rate Limiting 80 | - 1 second between Gemini API calls (configurable) 81 | - 100 requests/day quota monitoring 82 | - Automatic cache cleanup to prevent data accumulation 83 | 84 | ### Permissions 85 | - Scripts run with user permissions only 86 | - No elevated privileges required 87 | - Logs stored in user directory 88 | 89 | ## Performance Optimization 90 | 91 | ### Delegation Strategy (Optimized 2024) 92 | - **Early delegation**: Content >50k tokens (~200KB) goes to Gemini 93 | - **Multi-file threshold**: Task operations with ≥3 files delegate automatically 94 | - **Efficient context usage**: Claude reserves capacity for response generation 95 | - **Gemini utilization**: Leverages 1M token capacity for large analysis tasks 96 | 97 | ### Caching Strategy 98 | - Content-aware cache keys based on file contents and metadata 99 | - 1-hour default TTL with automatic cleanup 100 | - Cache invalidation on file modifications 101 | 102 | ### Resource Management 103 | - Automatic memory cleanup after processing 104 | - Background cache and log rotation 105 | - Configurable file size limits 106 | 107 | ### Performance Impact (2024 Optimization) 108 | - **Early delegation**: Content >50k tokens (~200KB) → Gemini 109 | - **Multi-file threshold**: ≥3 files for Task operations → Gemini 110 | - **Better coverage**: Minimum size reduced to 5KB 111 | 112 | ## Integration Points 113 | 114 | ### Claude Code Integration 115 | - Seamless hook integration via `settings.json` 116 | - No modification of Claude Code required 117 | - Preserves all existing Claude functionality 118 | 119 | ### Gemini API Integration 120 | - Direct CLI integration (no custom API wrappers) 121 | - Automatic error handling and fallbacks 122 | - Structured prompt generation based on task type 123 | 124 | ## Troubleshooting 125 | 126 | ### Common Issues 127 | - **Hook not executing**: Check `~/.claude/settings.json` configuration 128 | - **Gemini not found**: Verify `gemini` CLI is in PATH 129 | - **Cache issues**: Clear cache with `rm -rf cache/gemini/*` 130 | - **Permission errors**: Ensure scripts are executable 131 | 132 | ### Debug Commands 133 | ```bash 134 | # View recent logs 135 | tail -f logs/debug/$(date +%Y%m%d).log 136 | 137 | # Test individual components 138 | hooks/lib/path-converter.sh 139 | hooks/lib/json-parser.sh 140 | hooks/lib/gemini-wrapper.sh 141 | 142 | # Run full test suite 143 | test/test-runner.sh 144 | 145 | # Interactive testing 146 | test/manual-test.sh 147 | ``` 148 | 149 | ## Monitoring 150 | 151 | ### Performance Metrics 152 | - Hook execution time 153 | - Gemini processing duration 154 | - Cache hit/miss ratios 155 | - File processing statistics 156 | 157 | ### Health Checks 158 | - Automated testing in CI/CD 159 | - Component-level validation 160 | - API connectivity verification 161 | - Resource usage monitoring 162 | 163 | ## Contribution Guidelines 164 | 165 | ### Pull Requests 166 | - Include test coverage for new features 167 | - Update documentation for API changes 168 | - Follow existing code style conventions 169 | - Add debug logging for new components 170 | 171 | ### Issue Reporting 172 | - Include debug logs and reproduction steps 173 | - Specify Claude Code and Gemini CLI versions 174 | - Provide sample inputs when possible 175 | - Test with latest version before reporting -------------------------------------------------------------------------------- /test/test-runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Automated test suite for Claude-Gemini Bridge 3 | 4 | echo "🚀 Claude-Gemini Bridge Test Suite" 5 | echo "===================================" 6 | echo "" 7 | 8 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | 10 | # Use current directory structure (git repo = installation) 11 | BRIDGE_DIR="$SCRIPT_DIR/.." 12 | echo "🔧 Running from: $BRIDGE_DIR" 13 | 14 | BRIDGE_SCRIPT="$BRIDGE_DIR/hooks/gemini-bridge.sh" 15 | MOCK_DIR="$SCRIPT_DIR/mock-tool-calls" 16 | 17 | # Statistics 18 | TOTAL_TESTS=0 19 | PASSED_TESTS=0 20 | FAILED_TESTS=0 21 | 22 | # Test function 23 | run_test() { 24 | local test_name="$1" 25 | local json_file="$2" 26 | local expected_action="$3" # "continue" or "replace" 27 | 28 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 29 | 30 | echo "🧪 Test $TOTAL_TESTS: $test_name" 31 | 32 | if [ ! -f "$json_file" ]; then 33 | echo "❌ FAILED: Test file not found: $json_file" 34 | FAILED_TESTS=$((FAILED_TESTS + 1)) 35 | return 1 36 | fi 37 | 38 | # Execute test 39 | local result=$(cat "$json_file" | "$BRIDGE_SCRIPT" 2>/dev/null) 40 | local exit_code=$? 41 | 42 | # Check exit code 43 | if [ $exit_code -ne 0 ]; then 44 | echo "❌ FAILED: Bridge script exit code: $exit_code" 45 | FAILED_TESTS=$((FAILED_TESTS + 1)) 46 | return 1 47 | fi 48 | 49 | # Check JSON validity 50 | if ! echo "$result" | jq empty 2>/dev/null; then 51 | echo "❌ FAILED: Invalid JSON response" 52 | echo " Response: $result" 53 | FAILED_TESTS=$((FAILED_TESTS + 1)) 54 | return 1 55 | fi 56 | 57 | # Check decision (new Claude Code hook format) 58 | local decision=$(echo "$result" | jq -r '.decision // empty') 59 | 60 | # Map expected actions to decisions 61 | local expected_decision 62 | case "$expected_action" in 63 | "continue") expected_decision="approve" ;; 64 | "replace") expected_decision="block" ;; 65 | *) expected_decision="$expected_action" ;; 66 | esac 67 | 68 | if [ "$decision" != "$expected_decision" ]; then 69 | echo "❌ FAILED: Expected decision '$expected_decision', got '$decision'" 70 | echo " Response: $result" 71 | FAILED_TESTS=$((FAILED_TESTS + 1)) 72 | return 1 73 | fi 74 | 75 | # Additional validation for "block" decision (delegation to Gemini) 76 | if [ "$decision" = "block" ]; then 77 | local has_result=$(echo "$result" | jq -r '.result // empty') 78 | if [ -z "$has_result" ]; then 79 | echo "❌ FAILED: Block decision without result" 80 | FAILED_TESTS=$((FAILED_TESTS + 1)) 81 | return 1 82 | fi 83 | fi 84 | 85 | echo "✅ PASSED: $test_name" 86 | PASSED_TESTS=$((PASSED_TESTS + 1)) 87 | return 0 88 | } 89 | 90 | # Test preparation 91 | echo "📋 Preparing test environment..." 92 | 93 | # Create test files if they don't exist 94 | mkdir -p /tmp/test-project/src 95 | echo "print('Hello World')" > /tmp/test-project/src/main.py 96 | echo "# Test config file" > /tmp/test-project/config.txt 97 | 98 | echo "✅ Test environment ready" 99 | echo "" 100 | 101 | # Execute tests 102 | echo "🏃 Running tests..." 103 | echo "" 104 | 105 | # Test 1: Simple Read (should be "continue" since only 1 file) 106 | run_test "Simple Read (continue)" "$MOCK_DIR/simple-read.json" "continue" 107 | 108 | # Test 2: Task with Search (could be "replace" depending on files) 109 | run_test "Task Search" "$MOCK_DIR/task-search.json" "continue" 110 | 111 | # Test 3: Multi-File Glob (could be "replace" with many files) 112 | run_test "Multi-File Glob" "$MOCK_DIR/multi-file-glob.json" "continue" 113 | 114 | # Test 4: Grep Search 115 | run_test "Grep Search" "$MOCK_DIR/grep-search.json" "continue" 116 | 117 | # Test 5: Invalid JSON 118 | echo "🧪 Test $((TOTAL_TESTS + 1)): Invalid JSON" 119 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 120 | INVALID_RESULT=$(echo '{"invalid": json}' | "$BRIDGE_SCRIPT" 2>/dev/null) 121 | INVALID_EXIT=$? 122 | if [ $INVALID_EXIT -eq 1 ]; then 123 | echo "✅ PASSED: Invalid JSON handled correctly" 124 | PASSED_TESTS=$((PASSED_TESTS + 1)) 125 | else 126 | echo "❌ FAILED: Invalid JSON not handled properly" 127 | FAILED_TESTS=$((FAILED_TESTS + 1)) 128 | fi 129 | 130 | # Test 6: Empty Input 131 | echo "🧪 Test $((TOTAL_TESTS + 1)): Empty Input" 132 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 133 | EMPTY_RESULT=$(echo '' | "$BRIDGE_SCRIPT" 2>/dev/null) 134 | EMPTY_EXIT=$? 135 | if [ $EMPTY_EXIT -eq 0 ] && [[ "$EMPTY_RESULT" == *"approve"* ]]; then 136 | echo "✅ PASSED: Empty input handled correctly" 137 | PASSED_TESTS=$((PASSED_TESTS + 1)) 138 | else 139 | echo "❌ FAILED: Empty input not handled properly (exit: $EMPTY_EXIT, result: $EMPTY_RESULT)" 140 | FAILED_TESTS=$((FAILED_TESTS + 1)) 141 | fi 142 | 143 | # Library tests 144 | echo "" 145 | echo "📚 Testing library functions..." 146 | 147 | # Path Converter Test 148 | echo "🧪 Testing path-converter.sh..." 149 | if "$BRIDGE_DIR/hooks/lib/path-converter.sh" >/dev/null 2>&1; then 150 | echo "✅ PASSED: Path converter" 151 | PASSED_TESTS=$((PASSED_TESTS + 1)) 152 | else 153 | echo "❌ FAILED: Path converter" 154 | FAILED_TESTS=$((FAILED_TESTS + 1)) 155 | fi 156 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 157 | 158 | # JSON Parser Test 159 | echo "🧪 Testing json-parser.sh..." 160 | if "$BRIDGE_DIR/hooks/lib/json-parser.sh" >/dev/null 2>&1; then 161 | echo "✅ PASSED: JSON parser" 162 | PASSED_TESTS=$((PASSED_TESTS + 1)) 163 | else 164 | echo "❌ FAILED: JSON parser" 165 | FAILED_TESTS=$((FAILED_TESTS + 1)) 166 | fi 167 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 168 | 169 | # Debug Helpers Test 170 | echo "🧪 Testing debug-helpers.sh..." 171 | if "$BRIDGE_DIR/hooks/lib/debug-helpers.sh" >/dev/null 2>&1; then 172 | echo "✅ PASSED: Debug helpers" 173 | PASSED_TESTS=$((PASSED_TESTS + 1)) 174 | else 175 | echo "❌ FAILED: Debug helpers" 176 | FAILED_TESTS=$((FAILED_TESTS + 1)) 177 | fi 178 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 179 | 180 | # Gemini Wrapper Test 181 | echo "🧪 Testing gemini-wrapper.sh..." 182 | if "$BRIDGE_DIR/hooks/lib/gemini-wrapper.sh" >/dev/null 2>&1; then 183 | echo "✅ PASSED: Gemini wrapper" 184 | PASSED_TESTS=$((PASSED_TESTS + 1)) 185 | else 186 | echo "❌ FAILED: Gemini wrapper" 187 | FAILED_TESTS=$((FAILED_TESTS + 1)) 188 | fi 189 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 190 | 191 | # Test summary 192 | echo "" 193 | echo "📊 Test Summary" 194 | echo "===============" 195 | echo "Total Tests: $TOTAL_TESTS" 196 | echo "Passed: $PASSED_TESTS" 197 | echo "Failed: $FAILED_TESTS" 198 | echo "Success Rate: $(( PASSED_TESTS * 100 / TOTAL_TESTS ))%" 199 | echo "" 200 | 201 | if [ $FAILED_TESTS -eq 0 ]; then 202 | echo "🎉 All tests passed! Bridge is ready for use." 203 | exit 0 204 | else 205 | echo "💥 Some tests failed. Check the logs for details:" 206 | echo " Debug: $BRIDGE_DIR/logs/debug/$(date +%Y%m%d).log" 207 | echo " Errors: $BRIDGE_DIR/logs/debug/errors.log" 208 | echo "" 209 | echo "Run manual tests for more details:" 210 | echo " $SCRIPT_DIR/manual-test.sh" 211 | exit 1 212 | fi -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Uninstaller for Claude-Gemini Bridge - removes hooks and optionally cleans up data 3 | 4 | echo "🗑️ Claude-Gemini Bridge Uninstaller" 5 | echo "====================================" 6 | echo "" 7 | 8 | # Colors for output 9 | GREEN='\033[0;32m' 10 | RED='\033[0;31m' 11 | YELLOW='\033[1;33m' 12 | BLUE='\033[0;34m' 13 | NC='\033[0m' 14 | 15 | # Global variables 16 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 17 | CLAUDE_SETTINGS_FILE="$HOME/.claude/settings.json" 18 | BACKUP_SUFFIX=$(date +%Y%m%d_%H%M%S) 19 | 20 | # Log function 21 | log() { 22 | local level="$1" 23 | local message="$2" 24 | 25 | case $level in 26 | "info") echo -e "${GREEN}✅${NC} $message" ;; 27 | "warn") echo -e "${YELLOW}⚠️${NC} $message" ;; 28 | "error") echo -e "${RED}❌${NC} $message" ;; 29 | "debug") echo -e "${BLUE}🔍${NC} $message" ;; 30 | esac 31 | } 32 | 33 | # Error handling 34 | error_exit() { 35 | log "error" "$1" 36 | echo "" 37 | echo "💥 Uninstallation failed!" 38 | exit 1 39 | } 40 | 41 | # Check if jq is available 42 | check_jq() { 43 | if ! command -v jq &> /dev/null; then 44 | log "warn" "jq not found - will use fallback method for settings removal" 45 | return 1 46 | fi 47 | return 0 48 | } 49 | 50 | # Remove hook from Claude settings 51 | remove_claude_hooks() { 52 | log "info" "Removing Claude Code Hooks..." 53 | 54 | if [ ! -f "$CLAUDE_SETTINGS_FILE" ]; then 55 | log "info" "No Claude settings file found - nothing to remove" 56 | return 0 57 | fi 58 | 59 | # Backup existing settings 60 | cp "$CLAUDE_SETTINGS_FILE" "${CLAUDE_SETTINGS_FILE}.backup.${BACKUP_SUFFIX}" 61 | log "info" "Backup created: ${CLAUDE_SETTINGS_FILE}.backup.${BACKUP_SUFFIX}" 62 | 63 | # Check if our hook exists 64 | local hook_command="$SCRIPT_DIR/hooks/gemini-bridge.sh" 65 | if ! grep -q "gemini-bridge.sh" "$CLAUDE_SETTINGS_FILE" 2>/dev/null; then 66 | log "info" "No Claude-Gemini Bridge hooks found in settings" 67 | return 0 68 | fi 69 | 70 | if check_jq; then 71 | # Use jq to remove our hook 72 | local updated_config=$(jq ' 73 | .hooks.PreToolUse = (.hooks.PreToolUse // []) | 74 | .hooks.PreToolUse |= map( 75 | select(.hooks[]?.command? and (.hooks[]?.command | contains("gemini-bridge.sh")) | not) 76 | ) | 77 | if (.hooks.PreToolUse | length) == 0 then 78 | del(.hooks.PreToolUse) 79 | else . end | 80 | if (.hooks | length) == 0 then 81 | del(.hooks) 82 | else . end 83 | ' "$CLAUDE_SETTINGS_FILE" 2>/dev/null) 84 | 85 | if [ $? -eq 0 ] && [ -n "$updated_config" ]; then 86 | echo "$updated_config" > "$CLAUDE_SETTINGS_FILE" 87 | log "info" "Hook removed from Claude settings" 88 | else 89 | log "warn" "Could not remove hook with jq - using fallback method" 90 | remove_hook_fallback 91 | fi 92 | else 93 | remove_hook_fallback 94 | fi 95 | } 96 | 97 | # Fallback method to remove hook (without jq) 98 | remove_hook_fallback() { 99 | log "debug" "Using fallback method to remove hook..." 100 | 101 | # Create a temporary file without our hook 102 | local temp_file=$(mktemp) 103 | local in_our_hook=false 104 | local brace_count=0 105 | 106 | while IFS= read -r line; do 107 | # Check if we're entering our hook section 108 | if [[ "$line" =~ gemini-bridge\.sh ]]; then 109 | in_our_hook=true 110 | # Skip this entire hook object 111 | continue 112 | fi 113 | 114 | # If we're in our hook, count braces to know when we're out 115 | if [ "$in_our_hook" = true ]; then 116 | # Count opening and closing braces 117 | local open_braces=$(echo "$line" | tr -cd '{' | wc -c) 118 | local close_braces=$(echo "$line" | tr -cd '}' | wc -c) 119 | brace_count=$((brace_count + open_braces - close_braces)) 120 | 121 | # If brace count is back to 0, we're out of our hook 122 | if [ $brace_count -le 0 ]; then 123 | in_our_hook=false 124 | fi 125 | continue 126 | fi 127 | 128 | # If we're not in our hook, keep the line 129 | echo "$line" >> "$temp_file" 130 | done < "$CLAUDE_SETTINGS_FILE" 131 | 132 | # Replace the original file 133 | mv "$temp_file" "$CLAUDE_SETTINGS_FILE" 134 | log "info" "Hook removed using fallback method" 135 | } 136 | 137 | # Clean up data 138 | cleanup_data() { 139 | local cleanup_choice="$1" 140 | 141 | case $cleanup_choice in 142 | "all") 143 | log "info" "Removing all data (cache, logs, captured inputs)..." 144 | rm -rf "$SCRIPT_DIR/cache" "$SCRIPT_DIR/logs" "$SCRIPT_DIR/debug" 2>/dev/null 145 | log "info" "All data removed" 146 | ;; 147 | "cache") 148 | log "info" "Removing cache only..." 149 | rm -rf "$SCRIPT_DIR/cache" 2>/dev/null 150 | log "info" "Cache removed" 151 | ;; 152 | "logs") 153 | log "info" "Removing logs only..." 154 | rm -rf "$SCRIPT_DIR/logs" "$SCRIPT_DIR/debug" 2>/dev/null 155 | log "info" "Logs removed" 156 | ;; 157 | "none") 158 | log "info" "Keeping all data files" 159 | ;; 160 | esac 161 | } 162 | 163 | # Show uninstallation summary 164 | show_summary() { 165 | echo "" 166 | echo "🎉 Uninstallation completed!" 167 | echo "============================" 168 | echo "" 169 | echo "✅ Claude-Gemini Bridge hooks removed from: $CLAUDE_SETTINGS_FILE" 170 | echo "📁 Bridge directory remains: $SCRIPT_DIR" 171 | echo "" 172 | echo "📚 Next steps:" 173 | echo "" 174 | echo " 1. **RESTART Claude Code** to apply hook changes" 175 | echo "" 176 | echo " 2. Optional: Remove the bridge directory manually:" 177 | echo " rm -rf $SCRIPT_DIR" 178 | echo "" 179 | echo " 3. If needed, restore settings backup:" 180 | echo " cp ${CLAUDE_SETTINGS_FILE}.backup.${BACKUP_SUFFIX} $CLAUDE_SETTINGS_FILE" 181 | echo "" 182 | echo "💡 The Claude-Gemini Bridge can be reinstalled anytime by running:" 183 | echo " git clone [repository] && cd claude-gemini-bridge && ./install.sh" 184 | } 185 | 186 | # Main uninstallation 187 | main() { 188 | echo "This script removes Claude-Gemini Bridge hooks from Claude Code settings." 189 | echo "Bridge directory: $SCRIPT_DIR" 190 | echo "" 191 | 192 | # Ask what to clean up 193 | echo "What would you like to clean up?" 194 | echo "" 195 | echo "1) Remove hooks only (keep cache and logs)" 196 | echo "2) Remove hooks + cache (keep logs)" 197 | echo "3) Remove hooks + logs (keep cache)" 198 | echo "4) Remove hooks + all data (cache, logs, captured inputs)" 199 | echo "5) Cancel" 200 | echo "" 201 | read -p "Choose option (1-5): " choice 202 | 203 | case $choice in 204 | 1) 205 | cleanup_option="none" 206 | ;; 207 | 2) 208 | cleanup_option="cache" 209 | ;; 210 | 3) 211 | cleanup_option="logs" 212 | ;; 213 | 4) 214 | cleanup_option="all" 215 | ;; 216 | 5) 217 | log "info" "Uninstallation cancelled" 218 | exit 0 219 | ;; 220 | *) 221 | log "warn" "Invalid choice. Using option 1 (hooks only)" 222 | cleanup_option="none" 223 | ;; 224 | esac 225 | 226 | echo "" 227 | read -p "Continue with uninstallation? (y/N): " confirm 228 | 229 | if [[ ! "$confirm" =~ ^[Yy]$ ]]; then 230 | log "info" "Uninstallation cancelled" 231 | exit 0 232 | fi 233 | 234 | echo "" 235 | 236 | # Uninstallation steps 237 | remove_claude_hooks 238 | cleanup_data "$cleanup_option" 239 | 240 | show_summary 241 | } 242 | 243 | # Execute script 244 | main "$@" -------------------------------------------------------------------------------- /hooks/lib/json-parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: JSON parsing functions for Claude-Gemini Bridge 3 | 4 | # Extracts tool name from JSON 5 | extract_tool_name() { 6 | local json="$1" 7 | echo "$json" | jq -r '.tool_name // .tool // empty' 8 | } 9 | 10 | # Extracts parameters as JSON object 11 | extract_parameters() { 12 | local json="$1" 13 | echo "$json" | jq -r '.tool_input // .parameters // {}' 14 | } 15 | 16 | # Extracts working directory from context 17 | extract_working_directory() { 18 | local json="$1" 19 | echo "$json" | jq -r '.context.working_directory // empty' 20 | } 21 | 22 | # Extracts file paths based on tool type 23 | extract_file_paths() { 24 | local params="$1" 25 | local tool="$2" 26 | 27 | case "$tool" in 28 | "Read") 29 | echo "$params" | jq -r '.file_path // empty' 30 | ;; 31 | "Glob") 32 | echo "$params" | jq -r '.pattern // empty' 33 | ;; 34 | "Grep") 35 | local path=$(echo "$params" | jq -r '.path // "."') 36 | local pattern=$(echo "$params" | jq -r '.pattern // empty') 37 | echo "$path (pattern: $pattern)" 38 | ;; 39 | "Task") 40 | # Simple Task file extraction - just extract @ paths and file references 41 | local prompt=$(echo "$params" | jq -r '.prompt // empty') 42 | 43 | # Extract @ paths and explicit file patterns 44 | echo "$prompt" | grep -oE '(@[^[:space:]]+|/[^[:space:]]*\.[a-zA-Z0-9]+|[^[:space:]]*\.[a-zA-Z0-9]+)' 45 | ;; 46 | *) 47 | echo "" 48 | ;; 49 | esac 50 | } 51 | 52 | # Extracts prompt from task parameters 53 | extract_task_prompt() { 54 | local params="$1" 55 | echo "$params" | jq -r '.prompt // empty' 56 | } 57 | 58 | # Extracts description from task parameters 59 | extract_task_description() { 60 | local params="$1" 61 | echo "$params" | jq -r '.description // empty' 62 | } 63 | 64 | # Validates if JSON is valid 65 | validate_json() { 66 | local json="$1" 67 | if echo "$json" | jq empty 2>/dev/null; then 68 | return 0 69 | else 70 | return 1 71 | fi 72 | } 73 | 74 | # Creates JSON response for hook response 75 | create_hook_response() { 76 | local action="$1" 77 | local result="$2" 78 | local reason="$3" 79 | 80 | case "$action" in 81 | "continue"|"approve") 82 | # Normal tool execution continues 83 | echo '{"decision": "approve"}' 84 | ;; 85 | "replace"|"block_with_result") 86 | # Block tool execution and provide Gemini result 87 | if [ -n "$result" ]; then 88 | # Extract the actual content from the Gemini response 89 | local gemini_content=$(echo "$result" | jq -r '.content // empty' 2>/dev/null) 90 | if [ -n "$gemini_content" ]; then 91 | # Extract metadata for better context 92 | local tool_name=$(echo "$result" | jq -r '.metadata.original_tool // "unknown"' 2>/dev/null) 93 | local file_count=$(echo "$result" | jq -r '.metadata.file_count // 0' 2>/dev/null) 94 | 95 | # Create a friendly introduction 96 | local intro_text="🤖 Gemini Assistant here! I've analyzed this content for you since it exceeded your optimal processing size. 97 | 98 | Tool: ${tool_name} 99 | Files analyzed: ${file_count} 100 | ─────────────────────────────────────── 101 | 102 | " 103 | 104 | # Put Gemini's response with friendly intro in the reason field 105 | jq -n --arg reason "${intro_text}${gemini_content}" \ 106 | '{"decision": "block", "reason": $reason}' 107 | else 108 | # Fallback if content extraction fails 109 | jq -n --arg reason "${reason:-Delegated to Gemini for large-scale analysis}" \ 110 | '{"decision": "block", "reason": $reason}' 111 | fi 112 | else 113 | jq -n --arg reason "${reason:-No result provided}" \ 114 | '{"decision": "block", "reason": $reason}' 115 | fi 116 | ;; 117 | "block") 118 | # Block tool execution with error 119 | jq -n --arg reason "${reason:-Blocked by hook}" \ 120 | '{"decision": "block", "reason": $reason}' 121 | ;; 122 | *) 123 | echo '{"decision": "approve"}' 124 | ;; 125 | esac 126 | } 127 | 128 | # Creates structured Gemini response 129 | create_gemini_response() { 130 | local content="$1" 131 | local original_tool="$2" 132 | local file_count="$3" 133 | local processing_time="$4" 134 | 135 | jq -n \ 136 | --arg content "$content" \ 137 | --arg original_tool "$original_tool" \ 138 | --arg file_count "$file_count" \ 139 | --arg processing_time "$processing_time" \ 140 | '{ 141 | type: "gemini_analysis", 142 | content: $content, 143 | metadata: { 144 | original_tool: $original_tool, 145 | file_count: ($file_count | tonumber), 146 | processing_time: $processing_time, 147 | timestamp: now 148 | } 149 | }' 150 | } 151 | 152 | # Counts number of files in a string 153 | count_files() { 154 | local files="$1" 155 | if [ -z "$files" ]; then 156 | echo "0" 157 | else 158 | echo "$files" | wc -w | tr -d ' ' 159 | fi 160 | } 161 | 162 | # Test function for JSON parser 163 | test_json_parser() { 164 | echo "Testing JSON parser..." 165 | local failed=0 166 | 167 | # Test JSON 168 | local test_json='{ 169 | "tool": "Read", 170 | "parameters": { 171 | "file_path": "@src/main.py" 172 | }, 173 | "context": { 174 | "working_directory": "/Users/tim/Code/project" 175 | } 176 | }' 177 | 178 | # Test 1: Tool-Name extrahieren 179 | local tool_name=$(extract_tool_name "$test_json") 180 | if [ "$tool_name" != "Read" ]; then 181 | echo "❌ Test 1 failed: Tool name '$tool_name' != 'Read'" 182 | failed=1 183 | else 184 | echo "✅ Test 1 passed: Tool name extraction" 185 | fi 186 | 187 | # Test 2: Working Directory extrahieren 188 | local wd=$(extract_working_directory "$test_json") 189 | if [ "$wd" != "/Users/tim/Code/project" ]; then 190 | echo "❌ Test 2 failed: Working directory '$wd' != '/Users/tim/Code/project'" 191 | failed=1 192 | else 193 | echo "✅ Test 2 passed: Working directory extraction" 194 | fi 195 | 196 | # Test 3: Dateipfade extrahieren 197 | local params=$(extract_parameters "$test_json") 198 | local file_path=$(extract_file_paths "$params" "Read") 199 | if [ "$file_path" != "@src/main.py" ]; then 200 | echo "❌ Test 3 failed: File path '$file_path' != '@src/main.py'" 201 | failed=1 202 | else 203 | echo "✅ Test 3 passed: File path extraction" 204 | fi 205 | 206 | # Test 4: Hook-Response erstellen 207 | local response=$(create_hook_response "continue") 208 | if ! echo "$response" | jq empty 2>/dev/null; then 209 | echo "❌ Test 4 failed: Invalid JSON response" 210 | failed=1 211 | else 212 | echo "✅ Test 4 passed: Hook response creation" 213 | fi 214 | 215 | # Test 5: Task-JSON 216 | local task_json='{ 217 | "tool": "Task", 218 | "parameters": { 219 | "prompt": "Search for config files in @src/ and @config/", 220 | "description": "Config search" 221 | }, 222 | "context": {} 223 | }' 224 | 225 | local task_params=$(extract_parameters "$task_json") 226 | local task_prompt=$(extract_task_prompt "$task_params") 227 | if [[ "$task_prompt" != *"@src/"* ]]; then 228 | echo "❌ Test 5 failed: Task prompt extraction" 229 | failed=1 230 | else 231 | echo "✅ Test 5 passed: Task prompt extraction" 232 | fi 233 | 234 | if [ $failed -eq 0 ]; then 235 | echo "🎉 All JSON parser tests passed!" 236 | return 0 237 | else 238 | echo "💥 Some tests failed!" 239 | return 1 240 | fi 241 | } 242 | 243 | # Wenn das Script direkt aufgerufen wird, führe Tests aus 244 | if [ "${BASH_SOURCE[0]}" == "${0}" ]; then 245 | test_json_parser 246 | fi -------------------------------------------------------------------------------- /docs/TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting Guide for Claude-Gemini Bridge 2 | 3 | ## 🔧 Common Problems and Solutions 4 | 5 | ### Installation & Setup 6 | 7 | #### Hook not executing 8 | **Symptom:** Claude behaves normally, but Gemini is never called 9 | 10 | **Solution steps:** 11 | 1. Check Claude Settings: 12 | ```bash 13 | cat ~/.claude/settings.json 14 | ``` 15 | 16 | 2. Test hook manually: 17 | ```bash 18 | echo '{"tool_name":"Read","tool_input":{"file_path":"test.txt"},"session_id":"test"}' | ./hooks/gemini-bridge.sh 19 | ``` 20 | 21 | 3. Check permissions: 22 | ```bash 23 | ls -la hooks/gemini-bridge.sh 24 | # Should be executable (x-flag) 25 | ``` 26 | 27 | 4. Check hook configuration: 28 | ```bash 29 | jq '.hooks' ~/.claude/settings.json 30 | ``` 31 | 32 | **Solution:** Run re-installation: 33 | ```bash 34 | ./install.sh 35 | ``` 36 | 37 | --- 38 | 39 | #### "command not found: jq" 40 | **Symptom:** Error when running scripts 41 | 42 | **Solution:** 43 | - **macOS:** `brew install jq` 44 | - **Linux:** `sudo apt-get install jq` 45 | - **Alternative:** Use the installer, which checks jq dependencies 46 | 47 | --- 48 | 49 | #### "command not found: gemini" 50 | **Symptom:** Bridge cannot find Gemini 51 | 52 | **Solution steps:** 53 | 1. Check Gemini installation: 54 | ```bash 55 | which gemini 56 | gemini --version 57 | ``` 58 | 59 | 2. Test Gemini manually: 60 | ```bash 61 | echo "Test" | gemini -p "Say hello" 62 | ``` 63 | 64 | 3. Check PATH: 65 | ```bash 66 | echo $PATH 67 | ``` 68 | 69 | **Solution:** Install Gemini CLI or add to PATH 70 | 71 | --- 72 | 73 | ### Gemini Integration 74 | 75 | #### Gemini not responding 76 | **Symptom:** Hook runs, but Gemini doesn't return responses 77 | 78 | **Debug steps:** 79 | 1. Enable verbose logging: 80 | ```bash 81 | # In hooks/config/debug.conf 82 | DEBUG_LEVEL=3 83 | ``` 84 | 85 | 2. Check Gemini logs: 86 | ```bash 87 | tail -f logs/debug/$(date +%Y%m%d).log | grep -i gemini 88 | ``` 89 | 90 | 3. Test Gemini API key: 91 | ```bash 92 | gemini "test" -p "Hello" 93 | ``` 94 | 95 | **Common causes:** 96 | - Missing or invalid API key 97 | - Rate limiting reached 98 | - Network problems 99 | - Gemini service unavailable 100 | 101 | --- 102 | 103 | #### "Rate limiting: sleeping Xs" 104 | **Symptom:** Bridge waits between calls 105 | 106 | **Explanation:** Normal! Prevents API overload. 107 | 108 | **Adjust:** 109 | ```bash 110 | # In debug.conf 111 | GEMINI_RATE_LIMIT=0.5 # Reduce to 0.5 seconds 112 | ``` 113 | 114 | --- 115 | 116 | #### Cache problems 117 | **Symptom:** Outdated responses from Gemini 118 | 119 | **Solution:** 120 | ```bash 121 | # Clear cache completely 122 | rm -rf cache/gemini/* 123 | 124 | # Or reduce cache TTL (in debug.conf) 125 | GEMINI_CACHE_TTL=1800 # 30 minutes instead of 1 hour 126 | ``` 127 | 128 | --- 129 | 130 | ### Path Conversion 131 | 132 | #### @ paths not converted 133 | **Symptom:** Gemini cannot find files 134 | 135 | **Debug:** 136 | 1. Test path conversion in isolation: 137 | ```bash 138 | cd hooks/lib 139 | source path-converter.sh 140 | convert_claude_paths "@src/main.py" "/Users/tim/project" 141 | ``` 142 | 143 | 2. Check working directory in logs: 144 | ```bash 145 | grep "Working directory" logs/debug/$(date +%Y%m%d).log 146 | ``` 147 | 148 | **Common causes:** 149 | - Missing working_directory in tool call 150 | - Relative paths without @ prefix 151 | - Incorrect directory structures 152 | 153 | --- 154 | 155 | ### Performance & Behavior 156 | 157 | #### Gemini called too often 158 | **Symptom:** Every small Read command goes to Gemini 159 | 160 | **Adjustments in debug.conf:** 161 | ```bash 162 | MIN_FILES_FOR_GEMINI=5 # Increase minimum file count 163 | CLAUDE_TOKEN_LIMIT=100000 # Increase token threshold 164 | ``` 165 | 166 | --- 167 | 168 | #### Gemini never called 169 | **Symptom:** Even large analyses don't go to Gemini 170 | 171 | **Debug:** 172 | 1. Enable DRY_RUN mode: 173 | ```bash 174 | # In debug.conf 175 | DRY_RUN=true 176 | ``` 177 | 178 | 2. Check decision logic: 179 | ```bash 180 | grep "should_delegate_to_gemini" logs/debug/$(date +%Y%m%d).log 181 | ``` 182 | 183 | **Adjustments:** 184 | ```bash 185 | MIN_FILES_FOR_GEMINI=1 # Reduce thresholds 186 | CLAUDE_TOKEN_LIMIT=10000 # Lower token limit 187 | ``` 188 | 189 | --- 190 | 191 | ## 🔍 Debug Workflow 192 | 193 | ### 1. Reproduce problem 194 | ```bash 195 | # Enable input capturing 196 | # In debug.conf: CAPTURE_INPUTS=true 197 | 198 | # Run problematic Claude command 199 | # Input will be automatically saved 200 | ``` 201 | 202 | ### 2. Analyze logs 203 | ```bash 204 | # Current debug logs 205 | tail -f logs/debug/$(date +%Y%m%d).log 206 | 207 | # Error logs 208 | tail -f logs/debug/errors.log 209 | 210 | # All logs of the day 211 | less logs/debug/$(date +%Y%m%d).log 212 | ``` 213 | 214 | ### 3. Test in isolation 215 | ```bash 216 | # Interactive tests 217 | ./test/manual-test.sh 218 | 219 | # Automated tests 220 | ./test/test-runner.sh 221 | 222 | # Replay saved inputs 223 | ls debug/captured/ 224 | cat debug/captured/FILENAME.json | ./hooks/gemini-bridge.sh 225 | ``` 226 | 227 | ### 4. Step-by-step debugging 228 | ```bash 229 | # Highest debug level 230 | # In debug.conf: DEBUG_LEVEL=3 231 | 232 | # Dry-run mode (no actual Gemini call) 233 | # In debug.conf: DRY_RUN=true 234 | 235 | # Test individual library functions 236 | ./hooks/lib/path-converter.sh 237 | ./hooks/lib/json-parser.sh 238 | ./hooks/lib/gemini-wrapper.sh 239 | ``` 240 | 241 | --- 242 | 243 | ## ⚙️ Configuration 244 | 245 | ### Debug levels 246 | ```bash 247 | # In hooks/config/debug.conf 248 | 249 | DEBUG_LEVEL=0 # No debug output 250 | DEBUG_LEVEL=1 # Basic information (default) 251 | DEBUG_LEVEL=2 # Detailed information 252 | DEBUG_LEVEL=3 # Complete tracing 253 | ``` 254 | 255 | ### Gemini settings 256 | ```bash 257 | GEMINI_CACHE_TTL=3600 # Cache time in seconds 258 | GEMINI_TIMEOUT=30 # Timeout per call 259 | GEMINI_RATE_LIMIT=1 # Seconds between calls 260 | GEMINI_MAX_FILES=20 # Max files per call 261 | ``` 262 | 263 | ### Decision criteria 264 | ```bash 265 | MIN_FILES_FOR_GEMINI=3 # Minimum file count 266 | CLAUDE_TOKEN_LIMIT=50000 # Token threshold (~200KB) 267 | GEMINI_TOKEN_LIMIT=800000 # Max tokens for Gemini 268 | MAX_TOTAL_SIZE_FOR_GEMINI=10485760 # Max total size (10MB) 269 | 270 | # Excluded files 271 | GEMINI_EXCLUDE_PATTERNS="*.secret|*.key|*.env|*.password" 272 | ``` 273 | 274 | --- 275 | 276 | ## 🧹 Maintenance 277 | 278 | ### Clear cache 279 | ```bash 280 | # Manually 281 | rm -rf cache/gemini/* 282 | 283 | # Automatically (via debug.conf) 284 | AUTO_CLEANUP_CACHE=true 285 | CACHE_MAX_AGE_HOURS=24 286 | ``` 287 | 288 | ### Clear logs 289 | ```bash 290 | # Manually 291 | rm -rf logs/debug/* 292 | 293 | # Automatically (via debug.conf) 294 | AUTO_CLEANUP_LOGS=true 295 | LOG_MAX_AGE_DAYS=7 296 | ``` 297 | 298 | ### Clear captured inputs 299 | ```bash 300 | rm -rf debug/captured/* 301 | ``` 302 | 303 | --- 304 | 305 | ## 🆘 Emergency Deactivation 306 | 307 | ### Temporarily disable hook 308 | ```bash 309 | # Backup settings 310 | cp ~/.claude/settings.json ~/.claude/settings.json.backup 311 | 312 | # Remove hook 313 | jq 'del(.hooks)' ~/.claude/settings.json > /tmp/claude_settings 314 | mv /tmp/claude_settings ~/.claude/settings.json 315 | ``` 316 | 317 | ### Re-enable hook 318 | ```bash 319 | # Restore settings 320 | cp ~/.claude/settings.json.backup ~/.claude/settings.json 321 | 322 | # Or reinstall 323 | ./install.sh 324 | ``` 325 | 326 | ### Complete uninstallation 327 | ```bash 328 | # Remove hook 329 | jq 'del(.hooks)' ~/.claude/settings.json > /tmp/claude_settings 330 | mv /tmp/claude_settings ~/.claude/settings.json 331 | 332 | # Remove bridge 333 | rm -rf ~/claude-gemini-bridge 334 | ``` 335 | 336 | --- 337 | 338 | ## 📞 Support & Reporting 339 | 340 | ### Collect logs for support 341 | ```bash 342 | # Create debug package 343 | tar -czf claude-gemini-debug-$(date +%Y%m%d).tar.gz \ 344 | ~/.claude/settings.json \ 345 | logs/debug/ \ 346 | hooks/config/debug.conf 347 | ``` 348 | 349 | ### Helpful information 350 | - Claude Version: `claude --version` 351 | - Gemini Version: `gemini --version` 352 | - Operating System: `uname -a` 353 | - Shell: `echo $SHELL` 354 | - PATH: `echo $PATH` 355 | 356 | ### Common error messages 357 | - **"Invalid JSON received"**: Input validation failed 358 | - **"Gemini initialization failed"**: Gemini CLI not available 359 | - **"Files too large/small"**: Thresholds not met 360 | - **"Rate limiting"**: Normal, shows correct function 361 | - **"Cache expired"**: Normal, cache being renewed -------------------------------------------------------------------------------- /hooks/lib/debug-helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Debug helper functions for Claude-Gemini Bridge 3 | 4 | # Global variables 5 | DEBUG_LOG_DIR="" 6 | DEBUG_LEVEL=${DEBUG_LEVEL:-1} 7 | DEBUG_COMPONENT="" 8 | 9 | # Helper to get bridge directory 10 | get_bridge_dir() { 11 | echo "${CLAUDE_GEMINI_BRIDGE_DIR:-$HOME/.claude-gemini-bridge}" 12 | } 13 | 14 | # Color codes for terminal output 15 | RED='\033[0;31m' 16 | GREEN='\033[0;32m' 17 | YELLOW='\033[1;33m' 18 | BLUE='\033[0;34m' 19 | PURPLE='\033[0;35m' 20 | CYAN='\033[0;36m' 21 | NC='\033[0m' # No Color 22 | 23 | # Initializes the debug system 24 | init_debug() { 25 | DEBUG_COMPONENT="$1" 26 | DEBUG_LOG_DIR="$2" 27 | 28 | # Fallback to default directory 29 | if [ -z "$DEBUG_LOG_DIR" ]; then 30 | DEBUG_LOG_DIR="${CLAUDE_GEMINI_BRIDGE_DIR:-$HOME/.claude-gemini-bridge}/logs/debug" 31 | fi 32 | 33 | # Create log directory 34 | mkdir -p "$DEBUG_LOG_DIR" 35 | 36 | debug_log 1 "Debug system initialized for component: $DEBUG_COMPONENT" 37 | } 38 | 39 | # Main function for debug logging 40 | debug_log() { 41 | local level=$1 42 | local message="$2" 43 | local timestamp=$(date '+%Y-%m-%d %H:%M:%S.%3N') 44 | local component_prefix="" 45 | 46 | # Add component prefix 47 | if [ -n "$DEBUG_COMPONENT" ]; then 48 | component_prefix="[$DEBUG_COMPONENT] " 49 | fi 50 | 51 | # Only log if level is activated 52 | if [ "$level" -le "$DEBUG_LEVEL" ]; then 53 | local log_entry="[$timestamp] $component_prefix$message" 54 | 55 | # Level-specific handling 56 | case $level in 57 | 1) 58 | prefix="${GREEN}[INFO]${NC}" 59 | log_file="$DEBUG_LOG_DIR/$(date +%Y%m%d).log" 60 | ;; 61 | 2) 62 | prefix="${YELLOW}[DEBUG]${NC}" 63 | log_file="$DEBUG_LOG_DIR/$(date +%Y%m%d).log" 64 | ;; 65 | 3) 66 | prefix="${BLUE}[TRACE]${NC}" 67 | log_file="$DEBUG_LOG_DIR/$(date +%Y%m%d)_trace.log" 68 | ;; 69 | esac 70 | 71 | # Write to file 72 | echo "$log_entry" >> "$log_file" 73 | 74 | # Also output to stderr for higher debug levels 75 | if [ "$DEBUG_LEVEL" -ge 2 ]; then 76 | echo -e "$prefix $log_entry" >&2 77 | fi 78 | fi 79 | } 80 | 81 | # Error logging (always active) 82 | error_log() { 83 | local message="$1" 84 | local timestamp=$(date '+%Y-%m-%d %H:%M:%S') 85 | local component_prefix="" 86 | 87 | if [ -n "$DEBUG_COMPONENT" ]; then 88 | component_prefix="[$DEBUG_COMPONENT] " 89 | fi 90 | 91 | local log_entry="[$timestamp] $component_prefix$message" 92 | 93 | # Both stderr and error log 94 | echo -e "${RED}[ERROR]${NC} $log_entry" >&2 95 | echo "$log_entry" >> "$DEBUG_LOG_DIR/errors.log" 96 | } 97 | 98 | # Start performance measurement 99 | start_timer() { 100 | local timer_name="$1" 101 | local start_time=$(date +%s.%N) 102 | echo "$start_time" > "/tmp/claude_bridge_timer_$timer_name" 103 | debug_log 3 "Timer started: $timer_name" 104 | } 105 | 106 | # End performance measurement 107 | end_timer() { 108 | local timer_name="$1" 109 | local timer_file="/tmp/claude_bridge_timer_$timer_name" 110 | 111 | if [ -f "$timer_file" ]; then 112 | local start_time=$(cat "$timer_file") 113 | local end_time=$(date +%s.%N) 114 | # Use awk instead of bc for better portability 115 | local duration=$(awk -v e="$end_time" -v s="$start_time" 'BEGIN {printf "%.3f", e-s}' 2>/dev/null || echo "0") 116 | 117 | debug_log 2 "Timer finished: $timer_name took ${duration}s" 118 | rm -f "$timer_file" 119 | echo "$duration" 120 | else 121 | debug_log 1 "Timer not found: $timer_name" 122 | echo "0" 123 | fi 124 | } 125 | 126 | # Pretty-print for JSON with syntax highlighting 127 | debug_json() { 128 | local label="$1" 129 | local json="$2" 130 | 131 | debug_log 3 "$label:" 132 | 133 | if [ "$DEBUG_LEVEL" -ge 3 ]; then 134 | if command -v jq >/dev/null 2>&1; then 135 | echo -e "${CYAN}JSON:${NC}" >&2 136 | echo "$json" | jq '.' 2>/dev/null >&2 || echo "$json" >&2 137 | else 138 | echo -e "${CYAN}JSON (raw):${NC}" >&2 139 | echo "$json" >&2 140 | fi 141 | fi 142 | } 143 | 144 | # Variable dump for debugging 145 | debug_vars() { 146 | local prefix="$1" 147 | shift 148 | 149 | debug_log 3 "Variables dump ($prefix):" 150 | 151 | if [ "$DEBUG_LEVEL" -ge 3 ]; then 152 | for var in "$@"; do 153 | if [ -n "${!var}" ]; then 154 | echo -e "${PURPLE} $var=${NC}${!var}" >&2 155 | else 156 | echo -e "${PURPLE} $var=${NC}(empty)" >&2 157 | fi 158 | done 159 | fi 160 | } 161 | 162 | # System info for debug context 163 | debug_system_info() { 164 | debug_log 3 "System info:" 165 | if [ "$DEBUG_LEVEL" -ge 3 ]; then 166 | echo -e "${CYAN}System Information:${NC}" >&2 167 | echo " OS: $(uname -s)" >&2 168 | echo " PWD: $(pwd)" >&2 169 | echo " USER: ${USER:-unknown}" >&2 170 | echo " PID: $$" >&2 171 | echo " Bash version: $BASH_VERSION" >&2 172 | fi 173 | } 174 | 175 | # File size for debug output 176 | debug_file_size() { 177 | local file="$1" 178 | if [ -f "$file" ]; then 179 | local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "unknown") 180 | debug_log 3 "File size: $file = $size bytes" 181 | echo "$size" 182 | else 183 | debug_log 3 "File not found: $file" 184 | echo "0" 185 | fi 186 | } 187 | 188 | # Capture input for later analysis 189 | capture_input() { 190 | local input="$1" 191 | local capture_dir="$2" 192 | 193 | if [ -z "$capture_dir" ]; then 194 | capture_dir="$(get_bridge_dir)/debug/captured" 195 | fi 196 | 197 | mkdir -p "$capture_dir" 198 | 199 | local capture_file="$capture_dir/$(date +%Y%m%d_%H%M%S)_$(uuidgen 2>/dev/null || echo $$).json" 200 | echo "$input" > "$capture_file" 201 | 202 | debug_log 2 "Input captured to: $capture_file" 203 | echo "$capture_file" 204 | } 205 | 206 | # Clean up old debug files 207 | cleanup_debug_files() { 208 | local days_to_keep=${1:-7} 209 | 210 | debug_log 2 "Cleaning up debug files older than $days_to_keep days" 211 | 212 | # Delete old log files 213 | find "$DEBUG_LOG_DIR" -name "*.log" -mtime +$days_to_keep -delete 2>/dev/null 214 | 215 | # Delete old capture files 216 | find "$(get_bridge_dir)/debug/captured" -name "*.json" -mtime +$days_to_keep -delete 2>/dev/null 217 | 218 | # Delete old timer files 219 | find "/tmp" -name "claude_bridge_timer_*" -mtime +1 -delete 2>/dev/null 220 | } 221 | 222 | # Test function for debug helpers 223 | test_debug_helpers() { 224 | echo "Testing debug helpers..." 225 | local failed=0 226 | 227 | # Test directory 228 | local test_dir="/tmp/claude_bridge_debug_test" 229 | mkdir -p "$test_dir" 230 | 231 | # Test 1: Initialization 232 | init_debug "test_component" "$test_dir" 233 | if [ ! -d "$test_dir" ]; then 234 | echo "❌ Test 1 failed: Debug directory not created" 235 | failed=1 236 | else 237 | echo "✅ Test 1 passed: Debug initialization" 238 | fi 239 | 240 | # Test 2: Logging 241 | debug_log 1 "Test message" 242 | local log_file="$test_dir/$(date +%Y%m%d).log" 243 | if [ ! -f "$log_file" ]; then 244 | echo "❌ Test 2 failed: Log file not created" 245 | failed=1 246 | else 247 | echo "✅ Test 2 passed: Debug logging" 248 | fi 249 | 250 | # Test 3: Timer 251 | start_timer "test_timer" 252 | sleep 0.1 253 | local duration=$(end_timer "test_timer") 254 | if [ "$duration" = "0" ]; then 255 | echo "❌ Test 3 failed: Timer not working" 256 | failed=1 257 | else 258 | echo "✅ Test 3 passed: Timer functionality" 259 | fi 260 | 261 | # Test 4: JSON Debug 262 | local test_json='{"test": "value"}' 263 | debug_json "Test JSON" "$test_json" 264 | echo "✅ Test 4 passed: JSON debug (check manually)" 265 | 266 | # Cleanup 267 | rm -rf "$test_dir" 268 | 269 | if [ $failed -eq 0 ]; then 270 | echo "🎉 All debug helper tests passed!" 271 | return 0 272 | else 273 | echo "💥 Some tests failed!" 274 | return 1 275 | fi 276 | } 277 | 278 | # If script is called directly, run tests 279 | if [ "${BASH_SOURCE[0]}" == "${0}" ]; then 280 | test_debug_helpers 281 | fi -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Claude-Gemini Bridge 2 | 3 | Thank you for your interest in contributing to the Claude-Gemini Bridge! This document provides guidelines and information for contributors. 4 | 5 | ## 🚀 Getting Started 6 | 7 | ### Prerequisites 8 | 9 | - Bash 4.0+ 10 | - Claude Code CLI 11 | - Google Gemini CLI 12 | - `jq` for JSON processing 13 | - `git` for version control 14 | 15 | ### Development Setup 16 | 17 | 1. **Fork the repository** 18 | ```bash 19 | # Fork on GitHub, then clone your fork 20 | git clone https://github.com/your-username/claude-gemini-bridge.git 21 | cd claude-gemini-bridge 22 | ``` 23 | 24 | 2. **Set up development environment** 25 | ```bash 26 | # Install development tools (macOS) 27 | brew install shellcheck shfmt 28 | 29 | # Make scripts executable 30 | chmod +x hooks/*.sh test/*.sh 31 | 32 | # Run initial tests 33 | ./test/test-runner.sh 34 | ``` 35 | 36 | 3. **Create a development branch** 37 | ```bash 38 | git checkout -b feature/your-feature-name 39 | ``` 40 | 41 | ## 📝 Code Standards 42 | 43 | ### Shell Script Guidelines 44 | 45 | - **Shebang**: Always use `#!/bin/bash` 46 | - **ABOUTME**: Include single-line comment explaining file purpose 47 | - **Functions**: Document with inline comments 48 | - **Variables**: Use `local` for function variables 49 | - **Error Handling**: Always check exit codes and handle errors 50 | 51 | #### Example: 52 | ```bash 53 | #!/bin/bash 54 | # ABOUTME: Example script demonstrating code standards 55 | 56 | # Global configuration 57 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 58 | 59 | # Example function with proper documentation 60 | process_files() { 61 | local input_dir="$1" 62 | local output_file="$2" 63 | 64 | # Validate inputs 65 | if [ ! -d "$input_dir" ]; then 66 | echo "Error: Directory not found: $input_dir" >&2 67 | return 1 68 | fi 69 | 70 | # Process files 71 | find "$input_dir" -name "*.txt" > "$output_file" 72 | 73 | return 0 74 | } 75 | ``` 76 | 77 | ### Code Style 78 | 79 | - **Indentation**: 4 spaces (no tabs) 80 | - **Line Length**: Maximum 100 characters 81 | - **Comments**: English only 82 | - **Naming**: Use snake_case for variables and functions 83 | - **Constants**: Use UPPER_CASE for constants 84 | 85 | ### Testing Requirements 86 | 87 | Every new feature must include: 88 | 89 | 1. **Unit Tests**: Test individual functions 90 | 2. **Integration Tests**: Test component interactions 91 | 3. **Mock Data**: Provide test inputs when needed 92 | 93 | Example test structure: 94 | ```bash 95 | test_new_feature() { 96 | echo "Testing new feature..." 97 | 98 | # Test 1: Normal case 99 | local result=$(your_function "normal_input") 100 | if [ "$result" != "expected_output" ]; then 101 | echo "❌ Test 1 failed" 102 | return 1 103 | fi 104 | 105 | # Test 2: Error case 106 | your_function "invalid_input" 2>/dev/null 107 | if [ $? -eq 0 ]; then 108 | echo "❌ Test 2 failed: Should have returned error" 109 | return 1 110 | fi 111 | 112 | echo "✅ All tests passed" 113 | return 0 114 | } 115 | ``` 116 | 117 | ## 🔧 Development Workflow 118 | 119 | ### 1. Issue Identification 120 | 121 | - Check existing issues before creating new ones 122 | - Use issue templates when available 123 | - Include minimal reproduction steps 124 | - Specify environment details (OS, Claude version, etc.) 125 | 126 | ### 2. Feature Development 127 | 128 | - Create feature branch from `main` 129 | - Implement changes with tests 130 | - Update documentation as needed 131 | - Ensure all tests pass 132 | 133 | ### 3. Testing 134 | 135 | ```bash 136 | # Run all tests 137 | ./test/test-runner.sh 138 | 139 | # Test specific components 140 | ./hooks/lib/path-converter.sh 141 | ./hooks/lib/json-parser.sh 142 | 143 | # Interactive testing 144 | ./test/manual-test.sh 145 | 146 | # Check shell script quality 147 | shellcheck hooks/*.sh hooks/lib/*.sh 148 | ``` 149 | 150 | ### 4. Documentation 151 | 152 | Update documentation for: 153 | - New configuration options 154 | - API changes 155 | - New features 156 | - Breaking changes 157 | 158 | ### 5. Pull Request 159 | 160 | - Use descriptive PR titles 161 | - Include detailed description 162 | - Reference related issues 163 | - Ensure CI passes 164 | 165 | ## 🐛 Bug Reports 166 | 167 | ### Before Reporting 168 | 169 | 1. **Search existing issues** for duplicates 170 | 2. **Test with latest version** 171 | 3. **Check troubleshooting guide** 172 | 4. **Enable debug logging** (`DEBUG_LEVEL=3`) 173 | 174 | ### Bug Report Template 175 | 176 | ```markdown 177 | **Bug Description** 178 | Clear description of the issue 179 | 180 | **Environment** 181 | - OS: macOS 14.5 / Ubuntu 20.04 / etc. 182 | - Claude Code Version: 1.0.40 183 | - Gemini CLI Version: 1.2.3 184 | - Bridge Version: commit hash 185 | 186 | **Reproduction Steps** 187 | 1. Step one 188 | 2. Step two 189 | 3. ... 190 | 191 | **Expected Behavior** 192 | What should happen 193 | 194 | **Actual Behavior** 195 | What actually happens 196 | 197 | **Debug Logs** 198 | ``` 199 | Paste relevant logs here 200 | ``` 201 | 202 | **Additional Context** 203 | Any other relevant information 204 | ``` 205 | 206 | ## 🚀 Feature Requests 207 | 208 | ### Feature Request Template 209 | 210 | ```markdown 211 | **Feature Description** 212 | Clear description of the proposed feature 213 | 214 | **Use Case** 215 | Why is this feature needed? What problem does it solve? 216 | 217 | **Proposed Implementation** 218 | High-level approach to implementing the feature 219 | 220 | **Alternatives Considered** 221 | Other approaches that were considered 222 | 223 | **Additional Context** 224 | Screenshots, examples, references, etc. 225 | ``` 226 | 227 | ## 📋 Component Overview 228 | 229 | Understanding the codebase structure: 230 | 231 | ``` 232 | claude-gemini-bridge/ 233 | ├── hooks/ 234 | │ ├── gemini-bridge.sh # Main hook script 235 | │ ├── lib/ 236 | │ │ ├── path-converter.sh # @ path conversion 237 | │ │ ├── json-parser.sh # JSON handling 238 | │ │ ├── debug-helpers.sh # Logging/debugging 239 | │ │ └── gemini-wrapper.sh # Gemini API interface 240 | │ └── config/ 241 | │ └── debug.conf # Configuration 242 | ├── test/ 243 | │ ├── test-runner.sh # Automated tests 244 | │ ├── manual-test.sh # Interactive testing 245 | │ └── mock-tool-calls/ # Test data 246 | ├── docs/ 247 | │ └── TROUBLESHOOTING.md # Debug guide 248 | └── install.sh # Installation script 249 | ``` 250 | 251 | ## 🔍 Debugging Guidelines 252 | 253 | ### Debug Levels 254 | 255 | - **Level 0**: No debug output 256 | - **Level 1**: Basic information (default) 257 | - **Level 2**: Detailed information 258 | - **Level 3**: Full tracing 259 | 260 | ### Debugging Tools 261 | 262 | ```bash 263 | # Enable maximum debugging 264 | echo "DEBUG_LEVEL=3" >> hooks/config/debug.conf 265 | 266 | # Capture all inputs 267 | echo "CAPTURE_INPUTS=true" >> hooks/config/debug.conf 268 | 269 | # Test mode (no actual Gemini calls) 270 | echo "DRY_RUN=true" >> hooks/config/debug.conf 271 | 272 | # View logs in real-time 273 | tail -f logs/debug/$(date +%Y%m%d).log 274 | ``` 275 | 276 | ### Common Debug Scenarios 277 | 278 | 1. **Hook not executing**: Check Claude settings and permissions 279 | 2. **Path conversion issues**: Test path-converter.sh directly 280 | 3. **Gemini API problems**: Verify CLI setup and credentials 281 | 4. **Cache problems**: Clear cache and check file permissions 282 | 283 | ## 🎯 Pull Request Guidelines 284 | 285 | ### PR Checklist 286 | 287 | - [ ] Tests pass (`./test/test-runner.sh`) 288 | - [ ] Code follows style guidelines 289 | - [ ] Documentation updated 290 | - [ ] CHANGELOG.md updated (if applicable) 291 | - [ ] Commit messages are descriptive 292 | - [ ] No unnecessary files included 293 | 294 | ### Commit Message Format 295 | 296 | ``` 297 | type(scope): description 298 | 299 | Body explaining the change in detail. 300 | 301 | Fixes #123 302 | ``` 303 | 304 | Types: 305 | - `feat`: New feature 306 | - `fix`: Bug fix 307 | - `docs`: Documentation changes 308 | - `style`: Code style changes 309 | - `refactor`: Code refactoring 310 | - `test`: Test changes 311 | - `chore`: Maintenance tasks 312 | 313 | Examples: 314 | ``` 315 | feat(cache): add content-aware cache invalidation 316 | 317 | Implement cache key generation based on file contents and metadata 318 | to ensure cache invalidation when files are modified. 319 | 320 | Fixes #45 321 | ``` 322 | 323 | ## 🏆 Recognition 324 | 325 | Contributors will be recognized in: 326 | - README.md acknowledgments 327 | - Release notes 328 | - GitHub contributor graphs 329 | - Optional Twitter mentions (@claude_bridge) 330 | 331 | ## 📞 Getting Help 332 | 333 | - **GitHub Issues**: For bugs and feature requests 334 | - **GitHub Discussions**: For questions and community chat 335 | - **Documentation**: Check TROUBLESHOOTING.md first 336 | - **Code Review**: Tag maintainers for review assistance 337 | 338 | ## 🎨 Code of Conduct 339 | 340 | We are committed to providing a welcoming and inclusive environment. Please: 341 | 342 | - Be respectful and constructive 343 | - Focus on the technical aspects 344 | - Help others learn and grow 345 | - Report any inappropriate behavior 346 | 347 | ## 📈 Release Process 348 | 349 | ### Versioning 350 | 351 | We use [Semantic Versioning](https://semver.org/): 352 | - `MAJOR.MINOR.PATCH` 353 | - MAJOR: Breaking changes 354 | - MINOR: New features 355 | - PATCH: Bug fixes 356 | 357 | ### Release Checklist 358 | 359 | 1. Update version numbers 360 | 2. Update CHANGELOG.md 361 | 3. Create release PR 362 | 4. Tag release after merge 363 | 5. Update installation documentation 364 | 365 | Thank you for contributing to the Claude-Gemini Bridge! 🎉 -------------------------------------------------------------------------------- /hooks/gemini-bridge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Main hook script for Claude-Gemini Bridge - intercepts tool calls and delegates to Gemini when appropriate 3 | 4 | # Determine script directory 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 6 | 7 | # Load configuration 8 | source "$SCRIPT_DIR/config/debug.conf" 9 | 10 | # Set dynamic paths that aren't set in config 11 | if [ -z "$CAPTURE_DIR" ]; then 12 | export CAPTURE_DIR="$SCRIPT_DIR/../debug/captured" 13 | fi 14 | source "$SCRIPT_DIR/lib/debug-helpers.sh" 15 | source "$SCRIPT_DIR/lib/path-converter.sh" 16 | source "$SCRIPT_DIR/lib/json-parser.sh" 17 | source "$SCRIPT_DIR/lib/gemini-wrapper.sh" 18 | 19 | # Initialize debug system 20 | init_debug "gemini-bridge" "$SCRIPT_DIR/../logs/debug" 21 | 22 | # Start performance measurement 23 | start_timer "hook_execution" 24 | 25 | debug_log 1 "Hook execution started" 26 | debug_system_info 27 | 28 | # Read tool call JSON from stdin 29 | TOOL_CALL_JSON=$(cat) 30 | 31 | # Check for empty input 32 | if [ -z "$TOOL_CALL_JSON" ]; then 33 | debug_log 1 "Empty input received, continuing with normal execution" 34 | create_hook_response "continue" "" "Empty input" 35 | exit 0 36 | fi 37 | 38 | debug_log 2 "Received tool call of size: $(echo "$TOOL_CALL_JSON" | wc -c) bytes" 39 | 40 | # Save input for later analysis 41 | if [ "$CAPTURE_INPUTS" = "true" ]; then 42 | CAPTURE_FILE=$(capture_input "$TOOL_CALL_JSON" "$CAPTURE_DIR") 43 | debug_log 1 "Input captured to: $CAPTURE_FILE" 44 | fi 45 | 46 | # Validate JSON 47 | if ! validate_json "$TOOL_CALL_JSON"; then 48 | error_log "Invalid JSON received from Claude" 49 | create_hook_response "continue" "" "Invalid JSON input" 50 | exit 1 51 | fi 52 | 53 | # Determine working directory 54 | WORKING_DIR=$(extract_working_directory "$TOOL_CALL_JSON") 55 | if [ -z "$WORKING_DIR" ]; then 56 | WORKING_DIR=$(pwd) 57 | debug_log 1 "No working_directory in context, using: $WORKING_DIR" 58 | else 59 | debug_log 2 "Working directory from context: $WORKING_DIR" 60 | fi 61 | 62 | # Extract tool type and parameters 63 | TOOL_NAME=$(extract_tool_name "$TOOL_CALL_JSON") 64 | TOOL_PARAMS=$(extract_parameters "$TOOL_CALL_JSON") 65 | 66 | debug_log 1 "Processing tool: $TOOL_NAME" 67 | debug_json "Tool parameters" "$TOOL_PARAMS" 68 | 69 | # Extract file paths based on tool type 70 | case "$TOOL_NAME" in 71 | "Read") 72 | FILE_PATH_RAW=$(extract_file_paths "$TOOL_PARAMS" "$TOOL_NAME") 73 | ABSOLUTE_PATH=$(convert_claude_paths "$FILE_PATH_RAW" "$WORKING_DIR") 74 | FILES="$ABSOLUTE_PATH" 75 | ORIGINAL_PROMPT="Read file: $FILE_PATH_RAW" 76 | ;; 77 | "Glob") 78 | PATTERN_RAW=$(extract_file_paths "$TOOL_PARAMS" "$TOOL_NAME") 79 | ABSOLUTE_PATTERN=$(convert_claude_paths "$PATTERN_RAW" "$WORKING_DIR") 80 | # Get search path from parameters 81 | SEARCH_PATH=$(echo "$TOOL_PARAMS" | jq -r '.path // empty') 82 | if [ -z "$SEARCH_PATH" ]; then 83 | SEARCH_PATH="$WORKING_DIR" 84 | fi 85 | # Expand glob pattern properly and safely 86 | cd "$SEARCH_PATH" 2>/dev/null || cd "$WORKING_DIR" 2>/dev/null || cd /tmp 87 | # Use find for safe glob expansion 88 | if [[ "$PATTERN_RAW" == "**/*"* ]]; then 89 | # Handle recursive patterns 90 | EXTENSION=$(echo "$PATTERN_RAW" | sed 's/.*\*\*\/\*\.\([^*]*\)$/\1/') 91 | if [ "$EXTENSION" != "$PATTERN_RAW" ]; then 92 | FILES=$(find . -name "*.${EXTENSION}" -type f 2>/dev/null | sed 's|^\./||' | head -$GEMINI_MAX_FILES) 93 | else 94 | FILES=$(find . -type f 2>/dev/null | sed 's|^\./||' | head -$GEMINI_MAX_FILES) 95 | fi 96 | else 97 | # Simple glob patterns 98 | FILES=$(ls $PATTERN_RAW 2>/dev/null | head -$GEMINI_MAX_FILES) 99 | fi 100 | # Convert to absolute paths 101 | ABSOLUTE_FILES="" 102 | for file in $FILES; do 103 | ABSOLUTE_FILES="$ABSOLUTE_FILES $(cd "$SEARCH_PATH" && pwd)/$file" 104 | done 105 | FILES="$ABSOLUTE_FILES" 106 | ORIGINAL_PROMPT="Find files matching: $PATTERN_RAW in $SEARCH_PATH" 107 | ;; 108 | "Grep") 109 | GREP_INFO=$(extract_file_paths "$TOOL_PARAMS" "$TOOL_NAME") 110 | GREP_PATH=$(echo "$GREP_INFO" | cut -d' ' -f1) 111 | ABSOLUTE_GREP_PATH=$(convert_claude_paths "$GREP_PATH" "$WORKING_DIR") 112 | # For Grep we use the search path as basis 113 | FILES="$ABSOLUTE_GREP_PATH" 114 | ORIGINAL_PROMPT="Search in: $GREP_INFO" 115 | ;; 116 | "Task") 117 | TASK_PROMPT=$(extract_task_prompt "$TOOL_PARAMS") 118 | CONVERTED_PROMPT=$(convert_claude_paths "$TASK_PROMPT" "$WORKING_DIR") 119 | # Extract file paths from prompt 120 | FILES=$(extract_files_from_text "$CONVERTED_PROMPT") 121 | ORIGINAL_PROMPT="$TASK_PROMPT" 122 | ;; 123 | *) 124 | debug_log 1 "Unknown tool type: $TOOL_NAME, continuing normally" 125 | create_hook_response "continue" 126 | exit 0 127 | ;; 128 | esac 129 | 130 | debug_vars "extracted" TOOL_NAME FILES WORKING_DIR ORIGINAL_PROMPT 131 | 132 | # Decision: Should Gemini be used? Based on Claude's 200k vs Gemini's 1M token limit 133 | should_delegate_to_gemini() { 134 | local tool="$1" 135 | local files="$2" 136 | local prompt="$3" 137 | 138 | # Dry-run mode - always delegate for tests 139 | if [ "$DRY_RUN" = "true" ]; then 140 | debug_log 1 "DRY_RUN mode: would delegate to Gemini" 141 | return 0 142 | fi 143 | 144 | # Calculate estimated token count (rough estimate: 4 chars = 1 token) 145 | local total_size=0 146 | local file_count=0 147 | 148 | if [ -n "$files" ]; then 149 | file_count=$(count_files "$files") 150 | for file in $files; do 151 | if [ -f "$file" ]; then 152 | local file_size=$(debug_file_size "$file") 153 | total_size=$((total_size + file_size)) 154 | fi 155 | done 156 | fi 157 | 158 | # Rough token estimation: 4 characters ≈ 1 token 159 | local estimated_tokens=$((total_size / 4)) 160 | 161 | debug_log 2 "File count: $file_count, Total size: $total_size bytes, Estimated tokens: $estimated_tokens" 162 | 163 | # Use configurable token limits 164 | local claude_token_limit=${CLAUDE_TOKEN_LIMIT:-50000} 165 | local gemini_token_limit=${GEMINI_TOKEN_LIMIT:-800000} 166 | local min_files_threshold=${MIN_FILES_FOR_GEMINI:-3} 167 | local max_total_size=${MAX_TOTAL_SIZE_FOR_GEMINI:-10485760} 168 | 169 | # Check if total size exceeds maximum limit 170 | if [ "$total_size" -gt "$max_total_size" ]; then 171 | debug_log 1 "Content too large ($total_size bytes > $max_total_size) - exceeds maximum size limit" 172 | return 1 173 | fi 174 | 175 | # If estimated tokens exceed Claude's comfortable limit, use Gemini 176 | if [ "$estimated_tokens" -gt "$claude_token_limit" ]; then 177 | if [ "$estimated_tokens" -le "$gemini_token_limit" ]; then 178 | debug_log 1 "Large content ($estimated_tokens tokens > $claude_token_limit) - delegating to Gemini" 179 | return 0 180 | else 181 | debug_log 1 "Content too large even for Gemini ($estimated_tokens tokens > $gemini_token_limit) - splitting needed" 182 | return 1 183 | fi 184 | fi 185 | 186 | # For smaller content, check if it's a multi-file analysis task that benefits from Gemini 187 | if [ "$file_count" -ge "$min_files_threshold" ] && [[ "$tool" == "Task" ]]; then 188 | debug_log 1 "Multi-file Task ($file_count files >= $min_files_threshold) - delegating to Gemini for better analysis" 189 | return 0 190 | fi 191 | 192 | # Check for excluded file patterns 193 | for file in $files; do 194 | local filename=$(basename "$file") 195 | if [[ "$filename" =~ $GEMINI_EXCLUDE_PATTERNS ]]; then 196 | debug_log 2 "Excluded file pattern detected: $filename" 197 | return 1 198 | fi 199 | done 200 | 201 | debug_log 2 "Content size manageable for Claude - no delegation needed" 202 | return 1 203 | } 204 | 205 | # Main decision 206 | if should_delegate_to_gemini "$TOOL_NAME" "$FILES" "$ORIGINAL_PROMPT"; then 207 | debug_log 1 "Delegating to Gemini for tool: $TOOL_NAME" 208 | 209 | # Initialize Gemini wrapper 210 | if ! init_gemini_wrapper; then 211 | error_log "Failed to initialize Gemini wrapper" 212 | create_hook_response "continue" "" "Gemini initialization failed" 213 | exit 1 214 | fi 215 | 216 | # Call Gemini 217 | start_timer "gemini_processing" 218 | GEMINI_RESULT=$(call_gemini "$TOOL_NAME" "$FILES" "$WORKING_DIR" "$ORIGINAL_PROMPT") 219 | GEMINI_EXIT_CODE=$? 220 | GEMINI_DURATION=$(end_timer "gemini_processing") 221 | 222 | if [ "$GEMINI_EXIT_CODE" -eq 0 ] && [ -n "$GEMINI_RESULT" ]; then 223 | # Successful Gemini response 224 | debug_log 1 "Gemini processing successful (${GEMINI_DURATION}s)" 225 | 226 | # Create structured response 227 | FILE_COUNT=$(count_files "$FILES") 228 | STRUCTURED_RESPONSE=$(create_gemini_response "$GEMINI_RESULT" "$TOOL_NAME" "$FILE_COUNT" "$GEMINI_DURATION") 229 | 230 | # Hook response with Gemini result 231 | create_hook_response "replace" "$STRUCTURED_RESPONSE" 232 | else 233 | # Gemini error - continue normally 234 | error_log "Gemini processing failed, continuing with normal tool execution" 235 | create_hook_response "continue" "" "Gemini processing failed" 236 | fi 237 | else 238 | # Continue normally without Gemini 239 | debug_log 1 "Continuing with normal tool execution" 240 | create_hook_response "continue" 241 | fi 242 | 243 | # End performance measurement 244 | TOTAL_DURATION=$(end_timer "hook_execution") 245 | debug_log 1 "Hook execution completed in ${TOTAL_DURATION}s" 246 | 247 | # Automatic cleanup 248 | if [ "$AUTO_CLEANUP_CACHE" = "true" ]; then 249 | # Only clean occasionally (about 1 in 10 times) 250 | if [ $((RANDOM % 10)) -eq 0 ]; then 251 | cleanup_gemini_cache "$CACHE_MAX_AGE_HOURS" & 252 | fi 253 | fi 254 | 255 | if [ "$AUTO_CLEANUP_LOGS" = "true" ]; then 256 | # Only clean occasionally (about 1 in 20 times) 257 | if [ $((RANDOM % 20)) -eq 0 ]; then 258 | cleanup_debug_files "$LOG_MAX_AGE_DAYS" & 259 | fi 260 | fi 261 | 262 | exit 0 -------------------------------------------------------------------------------- /hooks/lib/gemini-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Wrapper for Gemini CLI with caching and rate limiting 3 | 4 | # Configuration 5 | GEMINI_CACHE_DIR="${GEMINI_CACHE_DIR:-${CLAUDE_GEMINI_BRIDGE_DIR:-$HOME/.claude-gemini-bridge}/cache/gemini}" 6 | GEMINI_CACHE_TTL="${GEMINI_CACHE_TTL:-3600}" # 1 hour 7 | GEMINI_TIMEOUT="${GEMINI_TIMEOUT:-30}" # 30 seconds 8 | GEMINI_RATE_LIMIT="${GEMINI_RATE_LIMIT:-1}" # 1 second between calls 9 | GEMINI_MAX_FILES="${GEMINI_MAX_FILES:-20}" # Max 20 files per call 10 | 11 | # Rate limiting file 12 | RATE_LIMIT_FILE="/tmp/claude_bridge_gemini_last_call" 13 | 14 | # Initialize Gemini wrapper 15 | init_gemini_wrapper() { 16 | mkdir -p "$GEMINI_CACHE_DIR" 17 | 18 | # Test if Gemini is available 19 | if ! command -v gemini >/dev/null 2>&1; then 20 | error_log "Gemini CLI not found in PATH" 21 | return 1 22 | fi 23 | 24 | debug_log 1 "Gemini wrapper initialized" 25 | debug_log 2 "Cache dir: $GEMINI_CACHE_DIR" 26 | debug_log 2 "Cache TTL: $GEMINI_CACHE_TTL seconds" 27 | 28 | return 0 29 | } 30 | 31 | # Implement rate limiting 32 | enforce_rate_limit() { 33 | if [ -f "$RATE_LIMIT_FILE" ]; then 34 | local last_call=$(cat "$RATE_LIMIT_FILE" 2>/dev/null || echo "0") 35 | local current_time=$(date +%s) 36 | local time_diff=$((current_time - last_call)) 37 | 38 | if [ "$time_diff" -lt "$GEMINI_RATE_LIMIT" ]; then 39 | local sleep_time=$((GEMINI_RATE_LIMIT - time_diff)) 40 | debug_log 2 "Rate limiting: sleeping ${sleep_time}s" 41 | sleep "$sleep_time" 42 | fi 43 | fi 44 | 45 | # Save current time 46 | date +%s > "$RATE_LIMIT_FILE" 47 | } 48 | 49 | # Generate cache key from input 50 | generate_cache_key() { 51 | local prompt="$1" 52 | local files="$2" 53 | local working_dir="$3" 54 | 55 | # Create hash from file contents + metadata 56 | local content_hash="" 57 | local file_array=($files) 58 | 59 | for file in "${file_array[@]}"; do 60 | if [ -f "$file" ] && [ -r "$file" ]; then 61 | # Combine filename, size, modification time and first 1KB of content 62 | local file_info=$(stat -f "%N|%z|%m" "$file" 2>/dev/null || stat -c "%n|%s|%Y" "$file" 2>/dev/null) 63 | local file_sample=$(head -c 1024 "$file" 2>/dev/null | shasum -a 256 | cut -d' ' -f1) 64 | content_hash="${content_hash}${file_info}|${file_sample}|" 65 | fi 66 | done 67 | 68 | # SHA256 hash from all parameters + file contents 69 | local input_string="$prompt|$files|$working_dir|$content_hash" 70 | echo "$input_string" | shasum -a 256 | cut -d' ' -f1 71 | } 72 | 73 | # Check if cache entry is still valid 74 | is_cache_valid() { 75 | local cache_file="$1" 76 | 77 | if [ ! -f "$cache_file" ]; then 78 | return 1 79 | fi 80 | 81 | local cache_age=$(( $(date +%s) - $(stat -f %m "$cache_file" 2>/dev/null || stat -c %Y "$cache_file") )) 82 | 83 | if [ "$cache_age" -lt "$GEMINI_CACHE_TTL" ]; then 84 | debug_log 2 "Cache hit: age ${cache_age}s (TTL: ${GEMINI_CACHE_TTL}s)" 85 | return 0 86 | else 87 | debug_log 2 "Cache expired: age ${cache_age}s" 88 | return 1 89 | fi 90 | } 91 | 92 | # Create Gemini prompt based on tool type 93 | create_gemini_prompt() { 94 | local tool_type="$1" 95 | local original_prompt="$2" 96 | local file_count="$3" 97 | 98 | case "$tool_type" in 99 | "Read") 100 | echo "You are assisting another LLM with file analysis. The user requested to read this file, but it's large enough that I'm helping out. Please analyze this file and provide a concise summary focusing on purpose, main functions, and important details:" 101 | ;; 102 | "Glob"|"Grep") 103 | echo "You are assisting another LLM with multi-file analysis. The user is searching across $file_count files. Please analyze these files and create a structured overview, grouping similar files and explaining the purpose of each group:" 104 | ;; 105 | "Task") 106 | if [[ "$original_prompt" =~ (search|find|suche|finde) ]]; then 107 | echo "You are assisting another LLM with a complex search task. The original request was: $original_prompt 108 | 109 | Please search the provided files for the specified criteria and provide a structured list of findings with context:" 110 | elif [[ "$original_prompt" =~ (analyze|analysiere|verstehe) ]]; then 111 | echo "You are assisting another LLM with a complex analysis task. The original request was: $original_prompt 112 | 113 | Please perform a detailed analysis of the provided files:" 114 | else 115 | echo "You are assisting another LLM with a complex analysis task. The original request was: $original_prompt 116 | 117 | Please process this task with the provided files and give a comprehensive response:" 118 | fi 119 | ;; 120 | *) 121 | echo "You are assisting another LLM with file analysis. Please analyze the provided files and provide a helpful summary." 122 | ;; 123 | esac 124 | } 125 | 126 | # Prepare files for Gemini 127 | prepare_files_for_gemini() { 128 | local files="$1" 129 | local working_dir="$2" 130 | local processed_files="" 131 | local file_count=0 132 | 133 | # Convert to array 134 | local file_array=($files) 135 | 136 | for file in "${file_array[@]}"; do 137 | # Skip if too many files 138 | if [ "$file_count" -ge "$GEMINI_MAX_FILES" ]; then 139 | debug_log 1 "File limit reached: $GEMINI_MAX_FILES" 140 | break 141 | fi 142 | 143 | # Make path absolute if necessary 144 | if [[ "$file" != /* ]]; then 145 | file="$working_dir/$file" 146 | fi 147 | 148 | # Check if file exists and is readable 149 | if [ -f "$file" ] && [ -r "$file" ]; then 150 | # Check file size (max 1MB per file) 151 | local file_size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0") 152 | if [ "$file_size" -lt 1048576 ]; then 153 | processed_files="$processed_files $file" 154 | file_count=$((file_count + 1)) 155 | debug_log 3 "Added file: $file (${file_size} bytes)" 156 | else 157 | debug_log 2 "Skipping large file: $file (${file_size} bytes)" 158 | fi 159 | else 160 | debug_log 2 "Skipping non-existent/unreadable file: $file" 161 | fi 162 | done 163 | 164 | echo "$processed_files" 165 | } 166 | 167 | # Main function: Call Gemini with caching 168 | call_gemini() { 169 | local tool_type="$1" 170 | local files="$2" 171 | local working_dir="$3" 172 | local original_prompt="$4" 173 | 174 | debug_log 1 "Calling Gemini for tool: $tool_type" 175 | start_timer "gemini_call" 176 | 177 | # Generate cache key 178 | local cache_key=$(generate_cache_key "$tool_type|$original_prompt" "$files" "$working_dir") 179 | local cache_file="$GEMINI_CACHE_DIR/$cache_key" 180 | 181 | # Check cache 182 | if is_cache_valid "$cache_file"; then 183 | debug_log 1 "Using cached result" 184 | cat "$cache_file" 185 | end_timer "gemini_call" >/dev/null 186 | return 0 187 | fi 188 | 189 | # Prepare files 190 | local processed_files=$(prepare_files_for_gemini "$files" "$working_dir") 191 | local file_count=$(echo "$processed_files" | wc -w | tr -d ' ') 192 | 193 | if [ "$file_count" -eq 0 ]; then 194 | debug_log 1 "No valid files found for Gemini" 195 | echo "No valid files found for analysis." 196 | end_timer "gemini_call" >/dev/null 197 | return 1 198 | fi 199 | 200 | # Create prompt 201 | local gemini_prompt=$(create_gemini_prompt "$tool_type" "$original_prompt" "$file_count") 202 | 203 | debug_log 2 "Processing $file_count files with Gemini" 204 | debug_log 3 "Prompt: $gemini_prompt" 205 | 206 | # Rate limiting 207 | enforce_rate_limit 208 | 209 | # Call Gemini 210 | local gemini_result="" 211 | local gemini_exit_code=0 212 | 213 | # Timeout with GNU timeout or gtimeout (macOS) 214 | local timeout_cmd="timeout" 215 | if command -v gtimeout >/dev/null 2>&1; then 216 | timeout_cmd="gtimeout" 217 | fi 218 | 219 | # Prepare file contents for STDIN 220 | local file_contents="" 221 | for file in $processed_files; do 222 | if [ -f "$file" ]; then 223 | file_contents="${file_contents}=== File: $file ===\n\n" 224 | file_contents="${file_contents}$(cat "$file" 2>/dev/null)\n\n" 225 | fi 226 | done 227 | 228 | # Debug: Show exact command being executed 229 | debug_log 3 "Executing: echo [file contents] | gemini -p \"$gemini_prompt\"" 230 | 231 | if command -v "$timeout_cmd" >/dev/null 2>&1; then 232 | gemini_result=$(echo -e "$file_contents" | "$timeout_cmd" "$GEMINI_TIMEOUT" gemini -p "$gemini_prompt" 2>&1) 233 | gemini_exit_code=$? 234 | else 235 | # Fallback without timeout 236 | gemini_result=$(echo -e "$file_contents" | gemini -p "$gemini_prompt" 2>&1) 237 | gemini_exit_code=$? 238 | fi 239 | 240 | local duration=$(end_timer "gemini_call") 241 | 242 | # Check result 243 | if [ "$gemini_exit_code" -eq 0 ] && [ -n "$gemini_result" ]; then 244 | # Cache successful response 245 | echo "$gemini_result" > "$cache_file" 246 | debug_log 1 "Gemini call successful (${duration}s, $file_count files)" 247 | echo "$gemini_result" 248 | return 0 249 | else 250 | error_log "Gemini call failed (exit code: $gemini_exit_code)" 251 | debug_log 2 "Gemini error output: $gemini_result" 252 | echo "Gemini analysis failed. Please check the logs." 253 | return 1 254 | fi 255 | } 256 | 257 | # Clean up old cache 258 | cleanup_gemini_cache() { 259 | local max_age_hours=${1:-24} # Default: 24 hours 260 | 261 | debug_log 2 "Cleaning up Gemini cache older than $max_age_hours hours" 262 | 263 | find "$GEMINI_CACHE_DIR" -type f -mtime +$(echo "$max_age_hours/24" | bc) -delete 2>/dev/null 264 | 265 | # Cache statistics 266 | local cache_files=$(find "$GEMINI_CACHE_DIR" -type f | wc -l | tr -d ' ') 267 | local cache_size=$(du -sh "$GEMINI_CACHE_DIR" 2>/dev/null | cut -f1) 268 | 269 | debug_log 1 "Cache stats: $cache_files files, $cache_size total size" 270 | } 271 | 272 | # Test function for Gemini wrapper 273 | test_gemini_wrapper() { 274 | echo "Testing Gemini wrapper..." 275 | local failed=0 276 | 277 | # Test 1: Initialization 278 | if ! init_gemini_wrapper; then 279 | echo "❌ Test 1 failed: Gemini wrapper initialization" 280 | failed=1 281 | else 282 | echo "✅ Test 1 passed: Gemini wrapper initialization" 283 | fi 284 | 285 | # Test 2: Cache-Key Generierung 286 | local key1=$(generate_cache_key "test" "file1.txt" "/tmp") 287 | local key2=$(generate_cache_key "test" "file1.txt" "/tmp") 288 | local key3=$(generate_cache_key "test2" "file1.txt" "/tmp") 289 | 290 | if [ "$key1" != "$key2" ]; then 291 | echo "❌ Test 2a failed: Cache keys should be identical" 292 | failed=1 293 | elif [ "$key1" = "$key3" ]; then 294 | echo "❌ Test 2b failed: Cache keys should be different" 295 | failed=1 296 | else 297 | echo "✅ Test 2 passed: Cache key generation" 298 | fi 299 | 300 | # Test 3: Prompt creation 301 | local prompt=$(create_gemini_prompt "Read" "analyze this file" 1) 302 | local prompt_lower=$(echo "$prompt" | tr '[:upper:]' '[:lower:]') 303 | if [[ "$prompt_lower" != *"analyze"* ]]; then 304 | echo "❌ Test 3 failed: Prompt creation" 305 | failed=1 306 | else 307 | echo "✅ Test 3 passed: Prompt creation" 308 | fi 309 | 310 | # Test 4: Rate limiting (simulated) 311 | echo $(date +%s) > "$RATE_LIMIT_FILE" 312 | local start_time=$(date +%s) 313 | enforce_rate_limit 314 | local end_time=$(date +%s) 315 | local time_diff=$((end_time - start_time)) 316 | 317 | if [ "$time_diff" -ge 1 ]; then 318 | echo "✅ Test 4 passed: Rate limiting works" 319 | else 320 | echo "✅ Test 4 passed: Rate limiting (no delay needed)" 321 | fi 322 | 323 | # Cleanup 324 | rm -f "$RATE_LIMIT_FILE" 325 | 326 | if [ $failed -eq 0 ]; then 327 | echo "🎉 All Gemini wrapper tests passed!" 328 | return 0 329 | else 330 | echo "💥 Some tests failed!" 331 | return 1 332 | fi 333 | } 334 | 335 | # If script is called directly, run tests 336 | if [ "${BASH_SOURCE[0]}" == "${0}" ]; then 337 | # Initialize debug system for tests 338 | if [ -f "$(dirname "$0")/debug-helpers.sh" ]; then 339 | source "$(dirname "$0")/debug-helpers.sh" 340 | init_debug "gemini-wrapper-test" 341 | fi 342 | 343 | test_gemini_wrapper 344 | fi -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ABOUTME: Simplified installer for Claude-Gemini Bridge that works in current directory 3 | 4 | echo "🚀 Claude-Gemini Bridge Installer" 5 | echo "==================================" 6 | echo "" 7 | 8 | # Colors for output 9 | GREEN='\033[0;32m' 10 | RED='\033[0;31m' 11 | YELLOW='\033[1;33m' 12 | BLUE='\033[0;34m' 13 | NC='\033[0m' 14 | 15 | # Global variables 16 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 17 | CLAUDE_SETTINGS_FILE="$HOME/.claude/settings.json" 18 | BACKUP_SUFFIX=$(date +%Y%m%d_%H%M%S) 19 | 20 | # Log function 21 | log() { 22 | local level="$1" 23 | local message="$2" 24 | 25 | case $level in 26 | "info") echo -e "${GREEN}✅${NC} $message" ;; 27 | "warn") echo -e "${YELLOW}⚠️${NC} $message" ;; 28 | "error") echo -e "${RED}❌${NC} $message" ;; 29 | "debug") echo -e "${BLUE}🔍${NC} $message" ;; 30 | esac 31 | } 32 | 33 | # Error handling 34 | error_exit() { 35 | log "error" "$1" 36 | echo "" 37 | echo "💥 Installation aborted!" 38 | echo "For help see: $SCRIPT_DIR/docs/TROUBLESHOOTING.md" 39 | exit 1 40 | } 41 | 42 | # Check prerequisites 43 | check_requirements() { 44 | log "info" "Checking prerequisites..." 45 | 46 | # Claude CLI 47 | if ! command -v claude &> /dev/null; then 48 | error_exit "Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code" 49 | fi 50 | log "debug" "Claude CLI found: $(which claude)" 51 | 52 | # Gemini CLI 53 | if ! command -v gemini &> /dev/null; then 54 | error_exit "Gemini CLI not found. Visit: https://github.com/google-gemini/gemini-cli" 55 | fi 56 | log "debug" "Gemini CLI found: $(which gemini)" 57 | 58 | # jq for JSON processing 59 | if ! command -v jq &> /dev/null; then 60 | log "warn" "jq not found. Install with:" 61 | echo " macOS: brew install jq" 62 | echo " Linux: sudo apt-get install jq" 63 | error_exit "jq is required for JSON processing" 64 | fi 65 | log "debug" "jq found: $(which jq)" 66 | 67 | log "info" "All prerequisites met" 68 | } 69 | 70 | # Create directory structure if needed 71 | create_directories() { 72 | log "info" "Creating directory structure..." 73 | 74 | mkdir -p "$SCRIPT_DIR"/{cache/gemini,logs/debug,debug/captured} 75 | 76 | if [ $? -eq 0 ]; then 77 | log "info" "Directory structure ready" 78 | else 79 | error_exit "Error creating directory structure" 80 | fi 81 | } 82 | 83 | # Test Gemini connection 84 | test_gemini_connection() { 85 | log "info" "Testing Gemini connection..." 86 | 87 | # Simple test call - just check if Gemini CLI works at all 88 | local test_result=$(echo "1+1" | gemini -p "What is the result?" 2>&1) 89 | local exit_code=$? 90 | 91 | # Only check exit code and that we got SOME response 92 | if [ $exit_code -eq 0 ] && [ -n "$test_result" ] && [ ${#test_result} -gt 0 ]; then 93 | log "info" "Gemini connection tested successfully" 94 | log "debug" "Gemini CLI is working and responding" 95 | else 96 | log "warn" "Gemini test failed. API key configured?" 97 | log "debug" "Gemini exit code: $exit_code" 98 | log "debug" "Gemini output: $test_result" 99 | echo "" 100 | echo "Common issues:" 101 | echo " - Missing API key: export GEMINI_API_KEY=your_key" 102 | echo " - Authentication problem with Gemini CLI" 103 | echo " - Network connectivity issues" 104 | echo "" 105 | read -p "Continue anyway? (y/N): " continue_anyway 106 | if [[ ! "$continue_anyway" =~ ^[Yy]$ ]]; then 107 | error_exit "Gemini configuration required. See Gemini CLI documentation." 108 | fi 109 | fi 110 | } 111 | 112 | # Intelligent hook merging for settings.json 113 | configure_claude_hooks() { 114 | log "info" "Configuring Claude Code Hooks..." 115 | 116 | # Create .claude directory if not exists 117 | mkdir -p "$(dirname "$CLAUDE_SETTINGS_FILE")" 118 | 119 | # Backup existing settings 120 | if [ -f "$CLAUDE_SETTINGS_FILE" ]; then 121 | cp "$CLAUDE_SETTINGS_FILE" "${CLAUDE_SETTINGS_FILE}.backup.${BACKUP_SUFFIX}" 122 | log "info" "Backup created: ${CLAUDE_SETTINGS_FILE}.backup.${BACKUP_SUFFIX}" 123 | fi 124 | 125 | # Our hook configuration 126 | local hook_command="$SCRIPT_DIR/hooks/gemini-bridge.sh" 127 | 128 | # Ask user which tools to intercept 129 | echo "" 130 | echo "Which tools should delegate to Gemini?" 131 | echo "Available options:" 132 | echo " Read - File reading operations" 133 | echo " Grep - Search operations" 134 | echo " Glob - File pattern matching" 135 | echo " Task - Complex analysis tasks (recommended)" 136 | echo "" 137 | echo "Enter tools separated by '|' (e.g., 'Task|Grep' or 'Read|Grep|Glob|Task')" 138 | read -p "Tools to intercept [Read|Grep|Glob|Task]: " user_tools 139 | 140 | local hook_matcher="${user_tools:-Read|Grep|Glob|Task}" 141 | log "info" "Configuring hooks for tools: $hook_matcher" 142 | 143 | # Check for any existing Claude-Gemini Bridge installation 144 | if [ -f "$CLAUDE_SETTINGS_FILE" ]; then 145 | if grep -q "gemini-bridge.sh" "$CLAUDE_SETTINGS_FILE" 2>/dev/null; then 146 | log "warn" "Existing Claude-Gemini Bridge installation detected!" 147 | 148 | # Show current hook path 149 | local current_path=$(grep -o '[^"]*gemini-bridge.sh' "$CLAUDE_SETTINGS_FILE" 2>/dev/null | head -1) 150 | if [ -n "$current_path" ]; then 151 | log "debug" "Current hook path: $current_path" 152 | log "debug" "New hook path: $hook_command" 153 | fi 154 | 155 | # Ask user what to do 156 | echo "" 157 | echo "Options:" 158 | echo "1) Update hook path to current location (recommended)" 159 | echo "2) Remove old hook and add new one" 160 | echo "3) Cancel installation" 161 | echo "" 162 | read -p "Choose option (1-3): " update_choice 163 | 164 | case $update_choice in 165 | 1) 166 | log "info" "Updating hook path to current location..." 167 | update_existing_hook "$hook_command" "$hook_matcher" 168 | return 0 169 | ;; 170 | 2) 171 | log "info" "Removing old hook and installing new one..." 172 | remove_existing_hooks 173 | # Continue with normal installation below 174 | ;; 175 | 3|*) 176 | log "info" "Installation cancelled" 177 | exit 0 178 | ;; 179 | esac 180 | fi 181 | fi 182 | 183 | # Merge with existing configuration or create new 184 | if [ -f "$CLAUDE_SETTINGS_FILE" ]; then 185 | log "debug" "Merging with existing settings..." 186 | 187 | # Add our hook to existing PreToolUse array 188 | local merged_config=$(jq --arg cmd "$hook_command" --arg matcher "$hook_matcher" ' 189 | .hooks.PreToolUse = (.hooks.PreToolUse // []) + [{ 190 | "matcher": $matcher, 191 | "hooks": [{ 192 | "type": "command", 193 | "command": $cmd 194 | }] 195 | }]' "$CLAUDE_SETTINGS_FILE" 2>/dev/null) 196 | 197 | if [ $? -eq 0 ] && [ -n "$merged_config" ]; then 198 | echo "$merged_config" > "$CLAUDE_SETTINGS_FILE" 199 | log "info" "Hook added to existing settings" 200 | else 201 | log "warn" "Error merging configuration. Creating new settings file." 202 | create_new_settings_file "$hook_command" "$hook_matcher" 203 | fi 204 | else 205 | log "debug" "Creating new settings file..." 206 | create_new_settings_file "$hook_command" "$hook_matcher" 207 | fi 208 | 209 | log "debug" "Hook configured: $hook_command" 210 | } 211 | 212 | # Update existing hook path 213 | update_existing_hook() { 214 | local hook_command="$1" 215 | local hook_matcher="$2" 216 | 217 | local updated_config=$(jq --arg cmd "$hook_command" --arg matcher "$hook_matcher" ' 218 | .hooks.PreToolUse = (.hooks.PreToolUse // []) | 219 | .hooks.PreToolUse |= map( 220 | if .hooks[]?.command? and (.hooks[]?.command | contains("gemini-bridge.sh")) 221 | then (.hooks[0].command = $cmd | .matcher = $matcher) 222 | else . end 223 | )' "$CLAUDE_SETTINGS_FILE" 2>/dev/null) 224 | 225 | if [ $? -eq 0 ] && [ -n "$updated_config" ]; then 226 | echo "$updated_config" > "$CLAUDE_SETTINGS_FILE" 227 | log "info" "Hook path updated successfully" 228 | else 229 | log "error" "Failed to update hook path" 230 | exit 1 231 | fi 232 | } 233 | 234 | # Remove existing gemini-bridge hooks 235 | remove_existing_hooks() { 236 | log "debug" "Removing existing Claude-Gemini Bridge hooks..." 237 | 238 | local cleaned_config=$(jq ' 239 | .hooks.PreToolUse = (.hooks.PreToolUse // []) | 240 | .hooks.PreToolUse |= map( 241 | select(.hooks[]?.command? and (.hooks[]?.command | contains("gemini-bridge.sh")) | not) 242 | ) | 243 | if (.hooks.PreToolUse | length) == 0 then 244 | del(.hooks.PreToolUse) 245 | else . end | 246 | if (.hooks | length) == 0 then 247 | del(.hooks) 248 | else . end 249 | ' "$CLAUDE_SETTINGS_FILE" 2>/dev/null) 250 | 251 | if [ $? -eq 0 ] && [ -n "$cleaned_config" ]; then 252 | echo "$cleaned_config" > "$CLAUDE_SETTINGS_FILE" 253 | log "info" "Existing hooks removed" 254 | else 255 | log "warn" "Could not remove existing hooks automatically" 256 | fi 257 | } 258 | 259 | # Create new settings file 260 | create_new_settings_file() { 261 | local hook_command="$1" 262 | local hook_matcher="$2" 263 | 264 | cat > "$CLAUDE_SETTINGS_FILE" << EOF 265 | { 266 | "hooks": { 267 | "PreToolUse": [ 268 | { 269 | "matcher": "$hook_matcher", 270 | "hooks": [ 271 | { 272 | "type": "command", 273 | "command": "$hook_command" 274 | } 275 | ] 276 | } 277 | ] 278 | } 279 | } 280 | EOF 281 | log "info" "New Claude settings created" 282 | } 283 | 284 | # Set permissions 285 | set_permissions() { 286 | log "info" "Setting file permissions..." 287 | 288 | # Make all shell scripts executable 289 | find "$SCRIPT_DIR" -name "*.sh" -exec chmod +x {} \; 290 | 291 | log "info" "Permissions set" 292 | } 293 | 294 | # Run basic tests 295 | run_basic_tests() { 296 | log "info" "Running basic tests..." 297 | 298 | # Test library functions 299 | local lib_tests=("path-converter.sh" "json-parser.sh" "debug-helpers.sh" "gemini-wrapper.sh") 300 | local test_failures=0 301 | 302 | for test in "${lib_tests[@]}"; do 303 | if [ -f "$SCRIPT_DIR/hooks/lib/$test" ]; then 304 | if "$SCRIPT_DIR/hooks/lib/$test" >/dev/null 2>&1; then 305 | log "debug" "$test: OK" 306 | else 307 | log "warn" "$test: Tests failed" 308 | test_failures=$((test_failures + 1)) 309 | fi 310 | else 311 | log "warn" "File not found: $SCRIPT_DIR/hooks/lib/$test" 312 | test_failures=$((test_failures + 1)) 313 | fi 314 | done 315 | 316 | # Test hook script with mock input 317 | local test_json='{"tool_name":"Read","tool_input":{"file_path":"test.txt"},"session_id":"test","transcript_path":"/tmp/test"}' 318 | local hook_result=$(echo "$test_json" | "$SCRIPT_DIR/hooks/gemini-bridge.sh" 2>/dev/null) 319 | 320 | if echo "$hook_result" | jq empty 2>/dev/null; then 321 | log "info" "Hook script test successful" 322 | else 323 | log "warn" "Hook script test failed, but installation continued" 324 | log "debug" "Hook output: $hook_result" 325 | test_failures=$((test_failures + 1)) 326 | fi 327 | 328 | if [ $test_failures -eq 0 ]; then 329 | log "info" "All tests passed" 330 | else 331 | log "warn" "$test_failures test(s) failed - installation may need troubleshooting" 332 | fi 333 | } 334 | 335 | # Show installation summary 336 | show_summary() { 337 | echo "" 338 | echo "🎉 Installation completed successfully!" 339 | echo "=======================================" 340 | echo "" 341 | echo "📁 Installation Directory: $SCRIPT_DIR" 342 | echo "⚙️ Claude Settings: $CLAUDE_SETTINGS_FILE" 343 | echo "" 344 | echo "🧪 Next steps:" 345 | echo "" 346 | echo " 1. **RESTART Claude Code** (hooks are loaded at startup)" 347 | echo " Exit Claude Code completely and restart it" 348 | echo "" 349 | echo " 2. Test the installation:" 350 | echo " $SCRIPT_DIR/test/test-runner.sh" 351 | echo "" 352 | echo " 3. Use Claude Code normally:" 353 | echo " Large file analyses will automatically use Gemini!" 354 | echo "" 355 | echo "📚 Documentation:" 356 | echo " - README: $SCRIPT_DIR/README.md" 357 | echo " - Troubleshooting: $SCRIPT_DIR/docs/TROUBLESHOOTING.md" 358 | echo "" 359 | echo "🔧 Configuration:" 360 | echo " - Debug level: $SCRIPT_DIR/hooks/config/debug.conf" 361 | echo " - Logs: $SCRIPT_DIR/logs/debug/" 362 | echo "" 363 | echo "💡 Debug commands:" 364 | echo " - View logs: tail -f $SCRIPT_DIR/logs/debug/\$(date +%Y%m%d).log" 365 | echo " - Clear cache: rm -rf $SCRIPT_DIR/cache/gemini/*" 366 | echo " - Uninstall: $SCRIPT_DIR/uninstall.sh" 367 | echo "" 368 | echo "🚨 IMPORTANT: You must restart Claude Code for the hooks to take effect!" 369 | } 370 | 371 | # Main installation 372 | main() { 373 | echo "This script configures the Claude-Gemini Bridge in the current directory." 374 | echo "Installation directory: $SCRIPT_DIR" 375 | echo "" 376 | read -p "Continue with installation? (y/N): " confirm 377 | 378 | if [[ ! "$confirm" =~ ^[Yy]$ ]]; then 379 | log "info" "Installation cancelled" 380 | exit 0 381 | fi 382 | 383 | echo "" 384 | 385 | # Installation steps 386 | check_requirements 387 | create_directories 388 | test_gemini_connection 389 | configure_claude_hooks 390 | set_permissions 391 | run_basic_tests 392 | 393 | show_summary 394 | } 395 | 396 | # Execute script 397 | main "$@" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Claude-Gemini Bridge 2 | 3 | 🤖 **Intelligent integration between Claude Code and Google Gemini for large-scale code analysis** 4 | 5 | The Claude-Gemini Bridge automatically delegates complex code analysis tasks from Claude Code to Google Gemini, combining Claude's reasoning capabilities with Gemini's large context processing power. 6 | 7 | [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)](#testing) 8 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](#license) 9 | [![Shell](https://img.shields.io/badge/shell-bash-orange.svg)](#requirements) 10 | 11 | ## 🚀 Quick Start 12 | 13 | ```bash 14 | # Clone and install for all projects in one simple process 15 | git clone https://github.com/your-username/claude-gemini-bridge.git 16 | cd claude-gemini-bridge 17 | ./install.sh 18 | 19 | # IMPORTANT: Restart Claude Code to load the new hooks 20 | # (Hooks are only loaded once at startup) 21 | 22 | # Test the installation 23 | ./test/test-runner.sh 24 | 25 | # Use Claude Code normally - large analyses will automatically use Gemini! 26 | claude "analyze all Python files in this project" 27 | ``` 28 | 29 | ### Installation Details 30 | 31 | The installer: 32 | - ✅ Works in the current directory (no separate installation location) 33 | - ✅ Automatically merges with existing Claude hooks in `~/.claude/settings.json` 34 | - ✅ Creates backups before making changes 35 | - ✅ Tests all components during installation 36 | - ✅ Provides uninstallation via `./uninstall.sh` 37 | 38 | ## 📋 Table of Contents 39 | 40 | - [Architecture](#-architecture) 41 | - [How It Works](#-how-it-works) 42 | - [Installation](#-installation) 43 | - [Configuration](#-configuration) 44 | - [Usage Examples](#-usage-examples) 45 | - [Testing](#-testing) 46 | - [Troubleshooting](#-troubleshooting) 47 | - [Contributing](#-contributing) 48 | 49 | ## 🏗️ Architecture 50 | 51 | ```mermaid 52 | graph TB 53 | subgraph "Claude Code" 54 | CC[Claude Code CLI] 55 | TC[Tool Call] 56 | end 57 | 58 | subgraph "Claude-Gemini Bridge" 59 | HS[Hook System] 60 | DE[Decision Engine] 61 | PC[Path Converter] 62 | CH[Cache Layer] 63 | end 64 | 65 | subgraph "External APIs" 66 | GC[Gemini CLI] 67 | GA[Gemini API] 68 | end 69 | 70 | CC -->|PreToolUse Hook| HS 71 | HS --> PC 72 | PC --> DE 73 | DE -->|Delegate?| CH 74 | CH -->|Yes| GC 75 | GC --> GA 76 | GA -->|Analysis| CH 77 | CH -->|Response| HS 78 | HS -->|Result| CC 79 | DE -->|No| CC 80 | 81 | style CC fill:#e1f5fe 82 | style HS fill:#f3e5f5 83 | style GC fill:#e8f5e8 84 | style DE fill:#fff3e0 85 | ``` 86 | 87 | ## 🔄 How It Works 88 | 89 | The bridge operates through Claude Code's hook system, intelligently deciding when to delegate tasks to Gemini: 90 | 91 | ```mermaid 92 | sequenceDiagram 93 | participant User 94 | participant Claude as Claude Code 95 | participant Bridge as Gemini Bridge 96 | participant Gemini as Gemini API 97 | 98 | User->>Claude: "Analyze these 20 Python files" 99 | Claude->>Bridge: PreToolUse Hook (Glob *.py) 100 | 101 | Bridge->>Bridge: Convert @ paths to absolute 102 | Bridge->>Bridge: Count files (20 files) 103 | Bridge->>Bridge: Calculate total size (500KB) 104 | Bridge->>Bridge: Estimate tokens (~125k) 105 | 106 | alt Token limit exceeded (>50k) 107 | Bridge->>Bridge: Check if within Gemini limits (<800k) 108 | Bridge->>Gemini: Analyze 20 files with context 109 | Gemini->>Bridge: Structured analysis result 110 | Bridge->>Claude: Replace tool call with Gemini result 111 | Claude->>User: Comprehensive analysis from Gemini 112 | else Multi-file Task (≥3 files) 113 | Bridge->>Bridge: Check if Task operation 114 | Bridge->>Gemini: Delegate multi-file analysis 115 | Gemini->>Bridge: Analysis result 116 | Bridge->>Claude: Replace with Gemini result 117 | Claude->>User: Multi-file analysis from Gemini 118 | else Small content (<50k tokens, <3 files) 119 | Bridge->>Claude: Continue with normal execution 120 | Claude->>Claude: Process files normally 121 | Claude->>User: Standard Claude response 122 | end 123 | ``` 124 | 125 | ### Delegation Criteria 126 | 127 | The bridge delegates to Gemini when: 128 | 129 | - **Token Limit**: Content exceeds ~50k tokens (~200KB, optimized for Claude's 200k context) 130 | - **Multi-File Tasks**: ≥3 files for Task operations (configurable) 131 | - **Safety Limits**: Content must be ≤10MB and ≤800k tokens for Gemini processing 132 | - **File Exclusions**: Automatically excludes sensitive files (*.secret, *.key, *.env, etc.) 133 | 134 | ## 📦 Installation 135 | 136 | ### Prerequisites 137 | 138 | - [Claude Code CLI](https://claude.ai/code) installed and configured 139 | - [Google Gemini CLI](https://github.com/google/generative-ai-cli) installed 140 | - `jq` for JSON processing 141 | - `bash` 4.0+ 142 | 143 | ### Installation Options 144 | 145 | The Claude-Gemini Bridge supports two deployment models: 146 | 147 | #### 🌍 Global Installation (Recommended) 148 | 149 | For system-wide use across all projects: 150 | 151 | ```bash 152 | # Clone to a permanent location (one-time setup) 153 | git clone https://github.com/your-username/claude-gemini-bridge.git ~/claude-gemini-bridge 154 | cd ~/claude-gemini-bridge 155 | ./install.sh 156 | 157 | # IMPORTANT: Restart Claude Code after installation! 158 | ``` 159 | 160 | **Benefits:** 161 | - ✅ Works with all projects automatically 162 | - ✅ Single bridge installation to maintain 163 | - ✅ Easier updates via git pull 164 | - ✅ Global hooks in `~/.claude/settings.json` 165 | 166 | #### 📁 Project-Specific Configuration 167 | 168 | For project-specific settings without separate installation: 169 | 170 | ```bash 171 | # Use the same global bridge installation 172 | # No additional cloning needed! 173 | 174 | # Create project-specific Claude settings 175 | mkdir -p .claude 176 | cat > .claude/settings.json << 'EOF' 177 | { 178 | "hooks": { 179 | "PreToolUse": [{ 180 | "matcher": "Read|Grep|Glob|Task", 181 | "hooks": [{ 182 | "type": "command", 183 | "command": "/Users/yourname/claude-gemini-bridge/hooks/gemini-bridge.sh" 184 | }] 185 | }] 186 | } 187 | } 188 | EOF 189 | 190 | # Ensure script is executable (usually not needed after git clone) 191 | chmod +x /Users/yourname/claude-gemini-bridge/hooks/gemini-bridge.sh 192 | 193 | # Create project-specific environment setup (optional) 194 | cat > project-claude-setup.sh << 'EOF' 195 | export DEBUG_LEVEL=1 196 | export GEMINI_TIMEOUT=60 197 | export DRY_RUN=false 198 | EOF 199 | # Note: No chmod needed - script will be sourced, not executed 200 | ``` 201 | 202 | **Benefits:** 203 | - ✅ Project-specific settings via environment variables 204 | - ✅ Different thresholds per project 205 | - ✅ Team-shareable setup scripts 206 | - ✅ No duplicate bridge installations 207 | - ✅ Project-local Claude settings override global ones 208 | 209 | The installer automatically: 210 | - ✅ Checks all prerequisites 211 | - ✅ Tests Gemini connectivity 212 | - ✅ Backs up existing Claude settings 213 | - ✅ Intelligently merges hooks into `~/.claude/settings.json` 214 | - ✅ Sets up directory structure and permissions 215 | - ✅ Runs validation tests 216 | 217 | ### Manual Installation 218 | 219 |
220 | Click to expand manual installation steps 221 | 222 | 1. **Clone the repository:** 223 | ```bash 224 | git clone https://github.com/your-username/claude-gemini-bridge.git 225 | cd claude-gemini-bridge 226 | ``` 227 | 228 | 2. **Set up directory structure:** 229 | ```bash 230 | # Files are already in the right place after git clone 231 | chmod +x hooks/*.sh 232 | mkdir -p cache/gemini logs/debug debug/captured 233 | ``` 234 | 235 | 3. **Configure Claude Code hooks:** 236 | ```bash 237 | # Add to ~/.claude/settings.json 238 | { 239 | "hooks": { 240 | "PreToolUse": [{ 241 | "matcher": "Read|Grep|Glob|Task", 242 | "hooks": [{ 243 | "type": "command", 244 | "command": "/full/path/to/claude-gemini-bridge/hooks/gemini-bridge.sh" 245 | }] 246 | }] 247 | } 248 | } 249 | ``` 250 | 251 |
252 | 253 | ## ⚙️ Configuration 254 | 255 | ### Configuration Reference 256 | 257 | **Currently Configurable** (edit `hooks/config/debug.conf`): 258 | 259 | ```bash 260 | # Debug configuration (WORKING) 261 | DEBUG_LEVEL=2 # 0=off, 1=basic, 2=verbose, 3=trace 262 | CAPTURE_INPUTS=true # Save hook inputs for analysis 263 | MEASURE_PERFORMANCE=true # Enable timing measurements 264 | DRY_RUN=false # Test mode without Gemini calls 265 | 266 | # Gemini API settings (WORKING) 267 | GEMINI_CACHE_TTL=3600 # Cache responses for 1 hour 268 | GEMINI_RATE_LIMIT=1 # 1 second between API calls 269 | GEMINI_TIMEOUT=30 # 30 second API timeout 270 | GEMINI_MAX_FILES=20 # Maximum files per Gemini call 271 | 272 | # File security (WORKING) 273 | GEMINI_EXCLUDE_PATTERNS="*.secret|*.key|*.env|*.password|*.token|*.pem|*.p12" 274 | 275 | # Automatic maintenance (WORKING) 276 | AUTO_CLEANUP_CACHE=true # Enable cache cleanup 277 | CACHE_MAX_AGE_HOURS=24 # Clean cache older than 24h 278 | AUTO_CLEANUP_LOGS=true # Enable log rotation 279 | LOG_MAX_AGE_DAYS=7 # Keep logs for 7 days 280 | ``` 281 | 282 | **Delegation Settings** (configurable via debug.conf): 283 | 284 | ```bash 285 | # Delegation thresholds (CONFIGURABLE) 286 | MIN_FILES_FOR_GEMINI=3 # At least 3 files for Task operations 287 | CLAUDE_TOKEN_LIMIT=50000 # Token limit for Claude delegation (~200KB) 288 | GEMINI_TOKEN_LIMIT=800000 # Max tokens Gemini can handle 289 | MAX_TOTAL_SIZE_FOR_GEMINI=10485760 # Max 10MB total size 290 | ``` 291 | 292 | ### Advanced Configuration 293 | 294 | **Current Implementation:** 295 | - Single global configuration file: `hooks/config/debug.conf` 296 | - All settings configurable via environment variables 297 | - Full project-specific configuration support 298 | 299 | ## 💡 Usage Examples 300 | 301 | ### Basic Usage 302 | 303 | Simply use Claude Code normally - the bridge works transparently: 304 | 305 | ```bash 306 | # These commands will automatically use Gemini for large analyses: 307 | claude -p "analyze all TypeScript files and identify patterns" 308 | claude -p "find security issues in @src/ directory" 309 | claude -p "summarize the architecture of this codebase" 310 | ``` 311 | 312 | ### Project-Specific Configuration 313 | 314 | #### Configuration Sources 315 | 316 | The bridge currently uses a single configuration source: 317 | 318 | - **Global Configuration**: `hooks/config/debug.conf` (in bridge installation directory) 319 | - **Runtime Overrides**: Environment variables can override all config values including delegation thresholds 320 | 321 | #### Project-Specific Settings 322 | 323 | All delegation thresholds can now be overridden via environment variables. 324 | 325 | **What you CAN configure per project:** 326 | 327 | ```bash 328 | # project-claude-setup.sh 329 | export DEBUG_LEVEL=3 # Increase logging for this project 330 | export DRY_RUN=true # Disable Gemini calls (testing) 331 | export GEMINI_TIMEOUT=60 # Longer timeout for complex analysis 332 | export CAPTURE_INPUTS=true # Save inputs for debugging 333 | 334 | # Override delegation thresholds 335 | export MIN_FILES_FOR_GEMINI=5 # Require more files before delegating 336 | export CLAUDE_TOKEN_LIMIT=30000 # Delegate earlier (smaller limit) 337 | export MAX_TOTAL_SIZE_FOR_GEMINI=5242880 # 5MB limit for this project 338 | 339 | # Source before using Claude (important: use 'source', not './') 340 | source ./project-claude-setup.sh 341 | claude "analyze this project" 342 | ``` 343 | 344 | ### Debug Mode 345 | 346 | ```bash 347 | # Enable verbose debugging 348 | echo "DEBUG_LEVEL=3" >> ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/config/debug.conf 349 | 350 | # Test without calling Gemini 351 | echo "DRY_RUN=true" >> ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/config/debug.conf 352 | 353 | # View live logs 354 | tail -f ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/logs/debug/$(date +%Y%m%d).log 355 | ``` 356 | 357 | ## 🔍 Verifying Gemini Integration 358 | 359 | ### How to See if Gemini is Actually Called 360 | 361 | #### 1. Real-time Log Monitoring 362 | 363 | ```bash 364 | # Monitor live logs while using Claude Code 365 | tail -f ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/logs/debug/$(date +%Y%m%d).log 366 | 367 | # Filter for Gemini-specific entries 368 | tail -f ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/logs/debug/$(date +%Y%m%d).log | grep -i gemini 369 | ``` 370 | 371 | #### 2. Enable Verbose Debug Output 372 | 373 | ```bash 374 | # Set maximum debug level 375 | echo "DEBUG_LEVEL=3" >> ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/config/debug.conf 376 | 377 | # Now run a Claude command that should trigger Gemini 378 | claude "analyze all Python files in @src/ directory" 379 | ``` 380 | 381 | #### 3. Look for These Log Indicators 382 | 383 | **Gemini WILL be called when you see:** 384 | ``` 385 | [INFO] Processing tool: Task 386 | [DEBUG] File count: 5 (minimum: 3) 387 | [DEBUG] Total file size: 25KB (minimum: 10KB) 388 | [INFO] Delegating to Gemini: files meet criteria 389 | [INFO] Calling Gemini for tool: Task 390 | [INFO] Gemini call successful (2.3s, 5 files) 391 | ``` 392 | 393 | **Gemini will NOT be called when you see:** 394 | ``` 395 | [INFO] Processing tool: Read 396 | [DEBUG] File count: 1 (minimum: 3) 397 | [DEBUG] Not enough files for Gemini delegation 398 | [INFO] Continuing with normal tool execution 399 | ``` 400 | 401 | #### 4. Force Gemini Delegation for Testing 402 | 403 | ```bash 404 | # Temporarily lower thresholds to force delegation 405 | echo "MIN_FILES_FOR_GEMINI=1" >> hooks/config/debug.conf 406 | echo "MIN_FILE_SIZE_FOR_GEMINI=1" >> hooks/config/debug.conf 407 | 408 | # Test with a simple file 409 | claude "analyze @README.md" 410 | 411 | # Reset thresholds afterwards 412 | echo "MIN_FILES_FOR_GEMINI=3" >> hooks/config/debug.conf 413 | echo "MIN_FILE_SIZE_FOR_GEMINI=10240" >> hooks/config/debug.conf 414 | ``` 415 | 416 | #### 5. Check Cache for Gemini Responses 417 | 418 | ```bash 419 | # List cached Gemini responses 420 | ls -la cache/gemini/ 421 | 422 | # View a cached response 423 | find cache/gemini/ -name "*" -type f -exec echo "=== {} ===" \; -exec cat {} \; -exec echo \; 424 | ``` 425 | 426 | #### 6. Performance Indicators 427 | 428 | Gemini calls typically show: 429 | - **Execution time**: 2-10 seconds (vs. <1s for Claude) 430 | - **Rate limiting**: "Rate limiting: sleeping 1s" messages 431 | - **Cache hits**: "Using cached result" for repeated queries 432 | 433 | #### 7. Test with Interactive Tool 434 | 435 | ```bash 436 | # Use the interactive tester to simulate Gemini calls 437 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/test/manual-test.sh 438 | 439 | # Choose option 3 (Multi-File Glob) or 2 (Task Search) to trigger Gemini 440 | ``` 441 | 442 | #### 8. Dry Run Mode for Debugging 443 | 444 | ```bash 445 | # Enable dry run to see decision logic without calling Gemini 446 | echo "DRY_RUN=true" >> ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/config/debug.conf 447 | 448 | # Run Claude command - you'll see "DRY RUN: Would call Gemini" instead of actual calls 449 | claude "analyze @src/ @docs/ @test/" 450 | 451 | # Disable dry run 452 | sed -i '' '/DRY_RUN=true/d' ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/config/debug.conf 453 | ``` 454 | 455 | ## 🧪 Testing 456 | 457 | ### Automated Testing 458 | 459 | ```bash 460 | # Run full test suite 461 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/test/test-runner.sh 462 | 463 | # Test individual components 464 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/lib/path-converter.sh 465 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/lib/json-parser.sh 466 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/lib/gemini-wrapper.sh 467 | ``` 468 | 469 | ### Interactive Testing 470 | 471 | ```bash 472 | # Interactive test tool 473 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/test/manual-test.sh 474 | ``` 475 | 476 | The interactive tester provides: 477 | - 🔍 Mock tool call testing 478 | - 📝 Custom JSON input testing 479 | - 🔄 Replay captured calls 480 | - 📊 Log analysis 481 | - 🧹 Cache management 482 | 483 | ### Test Architecture 484 | 485 | ```mermaid 486 | graph TD 487 | subgraph "Test Suite" 488 | TR[Test Runner
test-runner.sh] 489 | MT[Manual Tester
manual-test.sh] 490 | 491 | subgraph "Component Tests" 492 | PC[Path Converter] 493 | JP[JSON Parser] 494 | DH[Debug Helpers] 495 | GW[Gemini Wrapper] 496 | end 497 | 498 | subgraph "Integration Tests" 499 | HT[Hook Tests] 500 | ET[End-to-End] 501 | CT[Cache Tests] 502 | end 503 | 504 | subgraph "Mock Data" 505 | MTC[Mock Tool Calls] 506 | TF[Test Files] 507 | end 508 | end 509 | 510 | TR --> PC 511 | TR --> JP 512 | TR --> DH 513 | TR --> GW 514 | TR --> HT 515 | TR --> ET 516 | TR --> CT 517 | 518 | MT --> MTC 519 | MT --> TF 520 | 521 | style TR fill:#e1f5fe 522 | style MT fill:#f3e5f5 523 | ``` 524 | 525 | ## 🐛 Troubleshooting 526 | 527 | ### Common Issues 528 | 529 |
530 | Hook not executing 531 | 532 | **Symptoms:** Claude behaves normally, Gemini never called 533 | 534 | **Solutions:** 535 | ```bash 536 | # Check hook configuration 537 | cat ~/.claude/settings.local.json | jq '.hooks' 538 | 539 | # Test hook manually 540 | echo '{"tool":"Read","parameters":{"file_path":"test.txt"},"context":{}}' | \ 541 | ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/gemini-bridge.sh 542 | 543 | # Verify file permissions 544 | ls -la ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/gemini-bridge.sh 545 | ``` 546 |
547 | 548 |
549 | Gemini API errors 550 | 551 | **Symptoms:** "Gemini initialization failed" errors 552 | 553 | **Solutions:** 554 | ```bash 555 | # Test Gemini CLI directly 556 | echo "test" | gemini -p "Say hello" 557 | 558 | # Check API key 559 | echo $GEMINI_API_KEY 560 | 561 | # Verify rate limits 562 | grep -i "rate limit" ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/logs/debug/*.log 563 | ``` 564 |
565 | 566 |
567 | Cache issues 568 | 569 | **Symptoms:** Outdated responses, cache errors 570 | 571 | **Solutions:** 572 | ```bash 573 | # Clear cache 574 | rm -rf ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/cache/gemini/* 575 | 576 | # Check cache settings 577 | grep CACHE ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/hooks/config/debug.conf 578 | 579 | # Monitor cache usage 580 | du -sh ${CLAUDE_GEMINI_BRIDGE_DIR:-~/.claude-gemini-bridge}/cache/ 581 | ``` 582 |
583 | 584 | ### Debug Workflow 585 | 586 | ```mermaid 587 | flowchart TD 588 | Start([Issue Reported]) --> Reproduce{Can Reproduce?} 589 | 590 | Reproduce -->|Yes| Logs[Check Logs] 591 | Reproduce -->|No| More[Request More Info] 592 | 593 | Logs --> Level{Debug Level} 594 | Level -->|Low| Increase[Set DEBUG_LEVEL=3] 595 | Level -->|High| Analyze[Analyze Logs] 596 | 597 | Increase --> Reproduce 598 | Analyze --> Component{Component Issue?} 599 | 600 | Component -->|Yes| Unit[Run Unit Tests] 601 | Component -->|No| Integration[Run Integration Tests] 602 | 603 | Unit --> Fix[Fix Component] 604 | Integration --> System[Check System State] 605 | 606 | Fix --> Test[Test Fix] 607 | System --> Config[Check Configuration] 608 | 609 | Test --> PR[Submit PR] 610 | Config --> Fix 611 | 612 | More --> Start 613 | 614 | style Start fill:#e8f5e8 615 | style PR fill:#e1f5fe 616 | style Fix fill:#fff3e0 617 | ``` 618 | 619 | ## 🤝 Contributing 620 | 621 | We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details. 622 | 623 | ### Development Setup 624 | 625 | ```bash 626 | # Fork and clone 627 | git clone https://github.com/your-username/claude-gemini-bridge.git 628 | cd claude-gemini-bridge 629 | 630 | # Run tests before committing 631 | ./test/test-runner.sh 632 | ``` 633 | 634 | ### Code Standards 635 | 636 | - **Shell Scripts**: Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html) 637 | - **Comments**: English only, include ABOUTME headers 638 | - **Testing**: All functions must have unit tests 639 | - **Documentation**: Update README for any API changes 640 | 641 | ## 📄 License 642 | 643 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 644 | 645 | ## 🙏 Acknowledgments 646 | 647 | - **Inspired by**: Reddit user's implementation of Claude-Gemini integration 648 | - **Claude Code Team**: For the excellent hook system 649 | - **Google**: For the Gemini API and CLI tools 650 | - **Community**: For testing and feedback 651 | 652 | ## 📊 Project Stats 653 | 654 | ```mermaid 655 | pie title Component Distribution 656 | "Hook System" : 35 657 | "Path Processing" : 20 658 | "Caching" : 15 659 | "Debug/Logging" : 15 660 | "Testing" : 10 661 | "Documentation" : 5 662 | ``` 663 | 664 | --- 665 | 666 |
667 | 668 | **Made with ❤️ for the Claude Code community** 669 | 670 | [Report Bug](https://github.com/your-username/claude-gemini-bridge/issues) • 671 | [Request Feature](https://github.com/your-username/claude-gemini-bridge/issues) • 672 | [View Documentation](./docs/) 673 | 674 |
--------------------------------------------------------------------------------