From 4a27f06eae10fe0a04f03e305623b7ff5a35a512 Mon Sep 17 00:00:00 2001 From: Radu S Date: Thu, 5 Feb 2026 11:41:45 +0200 Subject: [PATCH 1/2] install-claude fix make setup make start and stop make claude-disable more models work in windows removed console special chars --- .vscode/settings.json | 5 +++ Makefile | 74 +++++++++++++++++++------------ README.md | 6 ++- copilot-config.yaml | 16 +++++++ scripts/claude_enable_wrapper.py | 49 ++++++++++++++++++++ scripts/setup.py | 76 ++++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 28 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 scripts/claude_enable_wrapper.py create mode 100644 scripts/setup.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..510513c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "chat.tools.terminal.autoApprove": { + "make": true + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index bb7acfb..12f8f13 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,12 @@ .PHONY: help setup install-claude start stop clean test verify claude-enable claude-disable claude-status list-models list-models-enabled +ifeq ($(OS),Windows_NT) +NPM_CHECK = where npm >nul 2>&1 +else +NPM_CHECK = command -v npm >/dev/null 2>&1 +endif + # Default target help: @echo "Available targets:" @@ -16,43 +22,55 @@ help: @echo " make list-models - List all GitHub Copilot models" @echo " make list-models-enabled - List only enabled GitHub Copilot models" -# Set up environment +# Set up environment (delegates to scripts/setup.py which uses venv) setup: @echo "Setting up environment..." - @mkdir -p scripts - @python3 -m venv venv - @./venv/bin/pip install -r requirements.txt - @if [ ! -f .env ]; then \ - echo "Generating .env file..."; \ - python3 generate_env.py; \ - else \ - echo "✓ .env file already exists, skipping generation"; \ - fi - @echo "✓ Setup complete" + @python3 scripts/setup.py || python scripts/setup.py + @echo "Setup complete OK" # Install Claude Code desktop application +ifeq ($(OS),Windows_NT) +install-claude: + @echo "Installing Claude Code desktop application..." + @where npm >nul 2>&1 && (echo "Installing Claude Code via npm..." & npm install -g @anthropic-ai/claude-code & echo "Claude Code installed successfully" & echo "Note: run 'make claude-enable' to configure it") || (echo "npm not found. Please install Node.js and npm first:" & echo " https://nodejs.org/" & echo " Then run: npm install -g @anthropic-ai/claude-code") +else install-claude: @echo "Installing Claude Code desktop application..." @if command -v npm >/dev/null 2>&1; then \ - echo "Installing Claude Code via npm..."; \ - npm install -g @anthropic-ai/claude-code && echo "✓ Claude Code installed successfully" && \ - echo "💡 You can now run 'make claude-enable' to configure it"; \ + echo "Installing Claude Code via npm..."; \ + npm install -g @anthropic-ai/claude-code && echo "Claude Code installed successfully" && \ + echo "Note: run 'make claude-enable' to configure it"; \ else \ echo "❌ npm not found. Please install Node.js and npm first:"; \ echo " https://nodejs.org/"; \ echo " Then run: npm install -g @anthropic-ai/claude-code"; \ fi +endif # Start LiteLLM proxy start: @echo "Starting LiteLLM proxy..." +ifeq ($(OS),Windows_NT) + @if exist venv\\Scripts\\litellm.exe ( \ + venv\\Scripts\\litellm.exe --config copilot-config.yaml --port 4444 \ + ) else if exist venv\\Scripts\\litellm ( \ + venv\\Scripts\\litellm --config copilot-config.yaml --port 4444 \ + ) else ( \ + venv\\Scripts\\python.exe -m litellm --config copilot-config.yaml --port 4444 \ + ) +else @source venv/bin/activate && litellm --config copilot-config.yaml --port 4444 +endif # Stop running processes stop: @echo "Stopping processes..." +ifeq ($(OS),Windows_NT) + @powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $$_.CommandLine -match 'litellm' } | ForEach-Object { Stop-Process -Id $$_.ProcessId -Force }" || echo "No matching processes found" +else @pkill -f litellm 2>/dev/null || true - @echo "✓ Processes stopped" +endif + @echo "Processes stopped" # Test proxy connection test: @@ -62,29 +80,26 @@ test: -H "Authorization: Bearer $$(grep LITELLM_MASTER_KEY .env | cut -d'=' -f2 | tr -d '\"')" \ -d '{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}' @echo "" - @echo "✅ Test completed successfully!" + @echo "Test completed successfully!" # Configure Claude Code to use local proxy claude-enable: @echo "Configuring Claude Code to use local proxy..." - @if [ ! -f .env ]; then echo "❌ .env file not found. Run 'make setup' first."; exit 1; fi - @MASTER_KEY=$$(grep LITELLM_MASTER_KEY .env | cut -d'=' -f2 | tr -d '"'); \ - if [ -z "$$MASTER_KEY" ]; then echo "❌ LITELLM_MASTER_KEY not found in .env"; exit 1; fi; \ - if [ -f ~/.claude/settings.json ]; then \ - cp ~/.claude/settings.json ~/.claude/settings.json.backup.$$(date +%Y%m%d_%H%M%S); \ - echo "📁 Backed up existing settings to ~/.claude/settings.json.backup.$$(date +%Y%m%d_%H%M%S)"; \ - fi; \ - python3 scripts/claude_enable.py "$$MASTER_KEY" - @echo "✅ Claude Code configured to use local proxy" - @echo "💡 Make sure to run 'make start' to start the LiteLLM proxy server" + @python3 scripts/claude_enable_wrapper.py || python scripts/claude_enable_wrapper.py + @echo "Claude Code configured to use local proxy" + @echo "Make sure to run 'make start' to start the LiteLLM proxy server" # Restore Claude Code to default settings claude-disable: @echo "Restoring Claude Code to default settings..." + +ifeq ($(OS),Windows_NT) + @powershell -NoProfile -Command "$$dir = Join-Path $$env:USERPROFILE '.claude'; if (Test-Path (Join-Path $$dir 'settings.json')) { Copy-Item (Join-Path $$dir 'settings.json') (Join-Path $$dir ('settings.json.proxy_backup.' + (Get-Date -Format 'yyyyMMdd_HHmmss'))); Write-Host 'Backed up proxy settings to' (Join-Path $$dir ('settings.json.proxy_backup.' + (Get-Date -Format 'yyyyMMdd_HHmmss')) ) } ; $$backups = Get-ChildItem $$dir -Filter 'settings.json.backup.*' -ErrorAction SilentlyContinue; if (-not $$backups) { $$backups = Get-ChildItem $$dir -Filter 'settings.json.proxy_backup.*' -ErrorAction SilentlyContinue }; if ($$backups) { $$latest = $$backups | Sort-Object LastWriteTime -Descending | Select-Object -First 1; Copy-Item $$latest.FullName (Join-Path $$dir 'settings.json'); Write-Host 'Restored settings from' $$latest.FullName } else { try { & python3 scripts/claude_disable.py } catch { & python scripts/claude_disable.py } }" +else @if [ -f ~/.claude/settings.json ]; then \ cp ~/.claude/settings.json ~/.claude/settings.json.proxy_backup.$$(date +%Y%m%d_%H%M%S); \ echo "📁 Backed up proxy settings to ~/.claude/settings.json.proxy_backup.$$(date +%Y%m%d_%H%M%S)"; \ - fi + fi; \ @if ls ~/.claude/settings.json.backup.* >/dev/null 2>&1; then \ LATEST_BACKUP=$$(ls -t ~/.claude/settings.json.backup.* | head -1); \ cp "$$LATEST_BACKUP" ~/.claude/settings.json; \ @@ -92,11 +107,15 @@ claude-disable: else \ python3 scripts/claude_disable.py; \ fi +endif # Show current Claude Code configuration claude-status: @echo "Current Claude Code configuration:" @echo "==================================" +ifeq ($(OS),Windows_NT) + @powershell -NoProfile -Command "if (Test-Path -Path (Join-Path $$env:USERPROFILE '.claude\\settings.json')) { Write-Host 'Settings file:' (Join-Path $$env:USERPROFILE '.claude\\settings.json'); Write-Host ''; try { Get-Content (Join-Path $$env:USERPROFILE '.claude\\settings.json') | ConvertFrom-Json | ConvertTo-Json -Depth 6 } catch { Get-Content (Join-Path $$env:USERPROFILE '.claude\\settings.json') }; Write-Host ''; $$content = Get-Content (Join-Path $$env:USERPROFILE '.claude\\settings.json') -Raw; if ($$content -match 'localhost:4444') { Write-Host 'Status: Using local proxy'; try { Invoke-WebRequest -Uri 'http://localhost:4444/health' -UseBasicParsing -TimeoutSec 2 | Out-Null; Write-Host 'Proxy server: Running' } catch { Write-Host 'Proxy server: Not running (run ''make start'')' } } else { Write-Host 'Status: Using default Anthropic servers' } } else { Write-Host 'No settings file found - using Claude Code defaults'; Write-Host 'Status: Using default Anthropic servers' }" +else @if [ -f ~/.claude/settings.json ]; then \ echo "📄 Settings file: ~/.claude/settings.json"; \ echo ""; \ @@ -116,6 +135,7 @@ claude-status: echo "📄 No settings file found - using Claude Code defaults"; \ echo "🌐 Status: Using default Anthropic servers"; \ fi +endif # List available GitHub Copilot models list-models: diff --git a/README.md b/README.md index 760044f..8d92cc9 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,11 @@ The proxy exposes these models to Claude Code: | Claude Code Model | Maps to GitHub Copilot | |-------------------|----------------------------------| | `claude-sonnet-4` | `github_copilot/claude-sonnet-4` | -| `gpt-4` | `github_copilot/gpt-4` | +| `gpt-5-mini` | `github_copilot/gpt-5-mini` | +| `gpt-4.1` | `github_copilot/gpt-4.1` | +| `gpt-4o` | `github_copilot/gpt-4o` | +| `grok-fast-1` | `github_copilot/grok-fast-1` | +| `gpt-4` | `github_copilot/gpt-4` | ## Additional Commands diff --git a/copilot-config.yaml b/copilot-config.yaml index 9b48b33..875e5c6 100644 --- a/copilot-config.yaml +++ b/copilot-config.yaml @@ -7,3 +7,19 @@ model_list: litellm_params: model: github_copilot/claude-sonnet-4 extra_headers: {"Editor-Version": "vscode/1.85.1", "Copilot-Integration-Id": "vscode-chat"} + - model_name: gpt-5-mini + litellm_params: + model: github_copilot/gpt-5-mini + extra_headers: {"Editor-Version": "vscode/1.85.1", "Copilot-Integration-Id": "vscode-chat"} + - model_name: gpt-4.1 + litellm_params: + model: github_copilot/gpt-4.1 + extra_headers: {"Editor-Version": "vscode/1.85.1", "Copilot-Integration-Id": "vscode-chat"} + - model_name: gpt-4o + litellm_params: + model: github_copilot/gpt-4o + extra_headers: {"Editor-Version": "vscode/1.85.1", "Copilot-Integration-Id": "vscode-chat"} + - model_name: grok-fast-1 + litellm_params: + model: github_copilot/grok-fast-1 + extra_headers: {"Editor-Version": "vscode/1.85.1", "Copilot-Integration-Id": "vscode-chat"} diff --git a/scripts/claude_enable_wrapper.py b/scripts/claude_enable_wrapper.py new file mode 100644 index 0000000..72e74f3 --- /dev/null +++ b/scripts/claude_enable_wrapper.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import os +import sys +import shutil +import time +from pathlib import Path +import subprocess + +def fail(msg, code=1): + print(msg, file=sys.stderr) + sys.exit(code) + +env_path = Path('.') / '.env' +if not env_path.exists(): + fail("❌ .env file not found. Run 'make setup' first.") + +master_key = None +for line in env_path.read_text().splitlines(): + if line.strip().startswith('LITELLM_MASTER_KEY'): + parts = line.split('=', 1) + if len(parts) > 1: + master_key = parts[1].strip().strip('"').strip("'") + break + +if not master_key: + fail("❌ LITELLM_MASTER_KEY not found in .env") + +claude_dir = Path.home() / '.claude' +settings = claude_dir / 'settings.json' +if settings.exists(): + timestamp = time.strftime("%Y%m%d_%H%M%S") + backup = claude_dir / f"settings.json.backup.{timestamp}" + try: + backup.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(str(settings), str(backup)) + print(f"📁 Backed up existing settings to {backup}") + except Exception as e: + fail(f"❌ Failed to backup settings: {e}") + +script = Path('scripts') / 'claude_enable.py' +if not script.exists(): + fail('❌ scripts/claude_enable.py not found') + +try: + subprocess.check_call([sys.executable, str(script), master_key]) +except subprocess.CalledProcessError as e: + fail(f"❌ scripts/claude_enable.py failed: {e}", code=e.returncode) + +print("✅ claude_enable completed") diff --git a/scripts/setup.py b/scripts/setup.py new file mode 100644 index 0000000..2f21a86 --- /dev/null +++ b/scripts/setup.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import sys +import shutil +import subprocess +import os +from pathlib import Path + +def fail(msg, code=1): + print(msg, file=sys.stderr) + sys.exit(code) + +def run(cmd): + print('> ' + ' '.join(cmd)) + subprocess.check_call(cmd) + +root = Path.cwd() + +# Ensure scripts directory exists +try: + (root / 'scripts').mkdir(parents=True, exist_ok=True) +except Exception as e: + fail(f"Failed to create scripts directory: {e}") + +# Find a Python executable +py = shutil.which('python3') or shutil.which('python') or sys.executable +if not py: + fail('Python not found on PATH') + +# Create virtual environment if needed +venv_dir = root / 'venv' +if venv_dir.exists(): + if os.name == 'nt': + venv_python = venv_dir / 'Scripts' / 'python.exe' + else: + venv_python = venv_dir / 'bin' / 'python' + if not venv_python.exists(): + print('Existing venv incomplete; attempting to recreate') + try: + shutil.rmtree(str(venv_dir)) + except Exception: + fail('Cannot remove existing venv; check permissions') + try: + run([py, '-m', 'venv', 'venv']) + except subprocess.CalledProcessError as e: + fail(f'Failed to create virtualenv: {e}') +else: + try: + run([py, '-m', 'venv', 'venv']) + except subprocess.CalledProcessError as e: + fail(f'Failed to create virtualenv: {e}') + +# Determine venv python +if os.name == 'nt': + venv_python = venv_dir / 'Scripts' / 'python.exe' +else: + venv_python = venv_dir / 'bin' / 'python' + +if not venv_python.exists(): + # fallback to provided python + venv_python = Path(py) + +# Install requirements +try: + run([str(venv_python), '-m', 'pip', 'install', '-r', 'requirements.txt']) +except subprocess.CalledProcessError as e: + fail(f'Failed to install requirements: {e}') + +# Generate .env if missing +env_file = root / '.env' +if not env_file.exists(): + try: + run([py, 'generate_env.py']) + except subprocess.CalledProcessError as e: + fail(f'Failed to generate .env: {e}') +else: + print('✓ .env file already exists, skipping generation') From 52e91d4ac929fe8e6ad80b00608365c520024370 Mon Sep 17 00:00:00 2001 From: Radu S Date: Sun, 8 Feb 2026 15:21:20 +0200 Subject: [PATCH 2/2] works on t5820 --- Makefile | 12 +------- copilot-config.yaml | 3 ++ scripts/start.py | 73 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 scripts/start.py diff --git a/Makefile b/Makefile index 12f8f13..4313876 100644 --- a/Makefile +++ b/Makefile @@ -50,17 +50,7 @@ endif # Start LiteLLM proxy start: @echo "Starting LiteLLM proxy..." -ifeq ($(OS),Windows_NT) - @if exist venv\\Scripts\\litellm.exe ( \ - venv\\Scripts\\litellm.exe --config copilot-config.yaml --port 4444 \ - ) else if exist venv\\Scripts\\litellm ( \ - venv\\Scripts\\litellm --config copilot-config.yaml --port 4444 \ - ) else ( \ - venv\\Scripts\\python.exe -m litellm --config copilot-config.yaml --port 4444 \ - ) -else - @source venv/bin/activate && litellm --config copilot-config.yaml --port 4444 -endif + @python3 scripts/start.py || python scripts/start.py # Stop running processes stop: diff --git a/copilot-config.yaml b/copilot-config.yaml index 875e5c6..f57789a 100644 --- a/copilot-config.yaml +++ b/copilot-config.yaml @@ -1,3 +1,6 @@ +litellm_settings: + drop_params: true + model_list: - model_name: gpt-4 litellm_params: diff --git a/scripts/start.py b/scripts/start.py new file mode 100644 index 0000000..3169337 --- /dev/null +++ b/scripts/start.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess + +VENV_DIR = os.path.join(os.getcwd(), 'venv') + +def venv_python(): + if os.name == 'nt': + return os.path.join(VENV_DIR, 'Scripts', 'python.exe') + return os.path.join(VENV_DIR, 'bin', 'python') + +def run_litellm(vpython): + args = ['--config', 'copilot-config.yaml', '--port', '4444'] + + # Try running the litellm console script in the venv (preferred) + candidates = [] + if os.name == 'nt': + candidates = [os.path.join(VENV_DIR, 'Scripts', 'litellm.exe'), + os.path.join(VENV_DIR, 'Scripts', 'litellm')] + else: + candidates = [os.path.join(VENV_DIR, 'bin', 'litellm')] + + for exe in candidates: + if os.path.exists(exe): + cmd = [exe] + args + print('Running:', ' '.join(cmd)) + try: + subprocess.check_call(cmd) + return + except subprocess.CalledProcessError as e: + print('litellm exited with', e.returncode) + sys.exit(e.returncode) + + # If no console script was found, try running via the venv python as a module + cmd = [vpython, '-m', 'litellm'] + args + print('Attempting fallback:', ' '.join(cmd)) + try: + subprocess.check_call(cmd) + return + except subprocess.CalledProcessError as e: + print('Fallback -m litellm failed with', e.returncode) + + # Try a secondary fallback: common submodule entrypoint + cmd2 = [vpython, '-m', 'litellm.cli'] + args + print('Attempting secondary fallback:', ' '.join(cmd2)) + try: + subprocess.check_call(cmd2) + return + except subprocess.CalledProcessError as e: + print('Secondary fallback litellm.cli failed with', e.returncode) + + # Final attempt: try system `litellm` on PATH + try: + print('Attempting to run `litellm` from PATH') + subprocess.check_call(['litellm'] + args) + return + except Exception: + print('Error: could not start litellm. Ensure it is installed in the venv or on PATH.') + sys.exit(3) + +if __name__ == '__main__': + if not os.path.isdir(VENV_DIR): + print('Error: virtual environment not found (expected at', VENV_DIR + '). Run "make setup" first.') + sys.exit(1) + + vpython = venv_python() + if not os.path.exists(vpython): + print('Warning: venv python not found at', vpython) + print('Falling back to system python. Ensure litellm is installed in your environment.') + vpython = sys.executable + + run_litellm(vpython)