Last active
August 30, 2025 18:21
-
-
Save subframe7536/c708236c7e5c01fbc7f34dcf7c5ab937 to your computer and use it in GitHub Desktop.
Claude Code Env Manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Claude Configuration Manager - Simplified and Optimized | |
| Manages API configurations for Claude with multiple presets support. | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from typing import Dict, Optional, Tuple | |
| class ConfigManager: | |
| """Handles all configuration operations for Claude API settings.""" | |
| def __init__(self): | |
| self.config_path = self._get_config_path() | |
| self.default_base_url = "https://api.anthropic.com" | |
| def _get_config_path(self) -> Path: | |
| """Get configuration file path from environment or default location.""" | |
| if "CC_CONFIG" in os.environ: | |
| return Path(os.environ["CC_CONFIG"]) | |
| return Path.home() / ".config" / "cc.config.json" | |
| def load_config(self) -> Dict: | |
| """Load configuration from file, return empty config if file doesn't exist.""" | |
| if not self.config_path.exists(): | |
| return {"presets": {}, "active": ""} | |
| try: | |
| with open(self.config_path, "r") as f: | |
| return json.load(f) | |
| except (json.JSONDecodeError, IOError) as e: | |
| print(f"Error reading config file: {e}") | |
| return {"presets": {}, "active": ""} | |
| def save_config(self, config: Dict) -> bool: | |
| """Save configuration to file.""" | |
| try: | |
| self.config_path.parent.mkdir(parents=True, exist_ok=True) | |
| with open(self.config_path, "w") as f: | |
| json.dump(config, f, indent=2) | |
| return True | |
| except IOError as e: | |
| print(f"Error saving config: {e}") | |
| return False | |
| def get_presets(self) -> Dict: | |
| """Get all presets from configuration.""" | |
| return self.load_config().get("presets", {}) | |
| def get_active_name(self) -> str: | |
| """Get the name of the active preset.""" | |
| return self.load_config().get("active", "") | |
| def get_active_preset(self) -> Optional[Dict]: | |
| """Get the currently active preset configuration.""" | |
| config = self.load_config() | |
| presets = self.get_presets() | |
| active_name = self.get_active_name() | |
| if not active_name or active_name not in presets: | |
| # Auto-select first available preset | |
| if presets: | |
| active_name = next(iter(presets.keys())) | |
| config["active"] = active_name | |
| self.save_config(config) | |
| print(f"Auto-selected preset: {active_name}") | |
| return presets.get(active_name) if active_name else None | |
| def set_preset(self, name: str, base_url: str, api_key: str) -> bool: | |
| """Create or update a preset.""" | |
| config = self.load_config() | |
| # Use defaults if not provided | |
| if not base_url: | |
| base_url = self.default_base_url | |
| if not api_key: | |
| print("Error: API Key is required") | |
| return False | |
| config.setdefault("presets", {})[name] = { | |
| "base_url": base_url, | |
| "api_key": api_key, | |
| } | |
| config["active"] = name | |
| return self.save_config(config) | |
| def switch_preset(self, name: str) -> bool: | |
| """Switch to a different preset.""" | |
| config = self.load_config() | |
| presets = self.get_presets() | |
| if name not in presets: | |
| print(f"Preset '{name}' not found") | |
| return False | |
| config["active"] = name | |
| return self.save_config(config) | |
| def delete_preset(self, name: str) -> bool: | |
| """Delete a preset.""" | |
| config = self.load_config() | |
| presets = self.get_presets() | |
| if name not in presets: | |
| print(f"Preset '{name}' not found") | |
| return False | |
| del presets[name] | |
| # If we deleted the active preset, switch to another one | |
| if self.get_active_name() == name: | |
| config["active"] = next(iter(presets.keys())) if presets else "" | |
| return self.save_config(config) | |
| def apply_to_environment(self) -> bool: | |
| """Apply the active preset to environment variables.""" | |
| preset = self.get_active_preset() | |
| if not preset: | |
| print("No active preset available") | |
| return False | |
| os.environ["ANTHROPIC_BASE_URL"] = preset["base_url"] | |
| os.environ["ANTHROPIC_AUTH_TOKEN"] = preset["api_key"] | |
| return True | |
| def parse_env(self) -> Tuple[str | None, str | None, bool]: | |
| """Check if environment variables match the active preset.""" | |
| env_base_url = os.environ.get("ANTHROPIC_BASE_URL") | |
| env_api_key = os.environ.get("ANTHROPIC_AUTH_TOKEN") | |
| preset = self.get_active_preset() | |
| is_applied = False | |
| if preset: | |
| is_applied = ( | |
| env_base_url == preset["base_url"] | |
| and env_api_key == preset["api_key"] | |
| ) | |
| return (env_base_url, env_api_key, is_applied) | |
| def mask_api_key(api_key: str) -> str: | |
| """Mask API key for display purposes.""" | |
| if not api_key or len(api_key) < 8: | |
| return "<INVALID>" | |
| return f"{api_key[:4]}...{api_key[-4:]}" | |
| def show_status(config_manager: ConfigManager, verbose: bool = False): | |
| """Display current configuration status.""" | |
| presets = config_manager.get_presets() | |
| active_name = config_manager.get_active_name() | |
| # Show environment variables | |
| env_base_url, env_api_key, is_synced = config_manager.parse_env() | |
| env_base_url_display = env_base_url or "<EMPTY>" | |
| env_api_key_display = ( | |
| mask_api_key(env_api_key) if env_api_key else "<EMPTY>" | |
| ) | |
| print("Current Environment:") | |
| print(f" ANTHROPIC_BASE_URL: {env_base_url_display}") | |
| print(f" ANTHROPIC_AUTH_TOKEN: {env_api_key_display}") | |
| print() | |
| if not presets: | |
| print("No presets configured.") | |
| return | |
| # Check if environment is synced with active preset | |
| sync_status = " ✓" if is_synced else " ✗" | |
| print( | |
| f"Active Preset: {active_name or '<NONE>'}{sync_status if active_name else ''}" | |
| ) | |
| if verbose: | |
| print(f"Configuration file: {config_manager.config_path}") | |
| for name, preset in presets.items(): | |
| status = "*" if name == active_name else " " | |
| print(f"{status} {name}") | |
| def interactive_setup(config_manager: ConfigManager): | |
| """Interactive configuration setup.""" | |
| presets = config_manager.get_presets() | |
| print("=== Claude Configuration Setup ===") | |
| if presets: | |
| show_status(config_manager, verbose=True) | |
| print() | |
| # Get preset name | |
| while True: | |
| preset_name = input("Preset name: ").strip() | |
| if preset_name: | |
| break | |
| print("Preset name cannot be empty!") | |
| # Check if editing existing preset | |
| existing = presets.get(preset_name) | |
| if existing: | |
| print(f"\nEditing existing preset '{preset_name}'") | |
| print("Press Enter to keep current values:") | |
| print(f"Current Base URL: {existing['base_url']}") | |
| print(f"Current API Key: {mask_api_key(existing['api_key'])}") | |
| base_url = input(f"Base URL [{existing['base_url']}]: ").strip() | |
| api_key = input("API Key [unchanged]: ").strip() | |
| base_url = base_url or existing["base_url"] | |
| api_key = api_key or existing["api_key"] | |
| else: | |
| print(f"\nCreating new preset '{preset_name}'") | |
| base_url = ( | |
| input(f"Base URL [{config_manager.default_base_url}]: ").strip() | |
| or config_manager.default_base_url | |
| ) | |
| api_key = input("API Key: ").strip() | |
| if not api_key: | |
| print("Error: API Key is required for new presets") | |
| return False | |
| # Save configuration | |
| if config_manager.set_preset(preset_name, base_url, api_key): | |
| print(f"\n✓ Preset '{preset_name}' saved and activated") | |
| return True | |
| else: | |
| print("✗ Failed to save configuration") | |
| return False | |
| def print_shell(config_manager: ConfigManager, shell_type: str): | |
| """Export environment variables for shell evaluation.""" | |
| preset = config_manager.get_active_preset() | |
| if not preset: | |
| print("# No active preset available", file=sys.stderr) | |
| return | |
| base_url = preset["base_url"] | |
| api_key = preset["api_key"] | |
| if shell_type in ["bash", "zsh"]: | |
| print(f'export ANTHROPIC_BASE_URL="{base_url}"') | |
| print(f'export ANTHROPIC_AUTH_TOKEN="{api_key}"') | |
| elif shell_type in ["powershell", "pwsh"]: | |
| print(f'$env:ANTHROPIC_BASE_URL="{base_url}"') | |
| print(f'$env:ANTHROPIC_AUTH_TOKEN="{api_key}"') | |
| else: | |
| print(f"Unsupported shell: {shell_type}", file=sys.stderr) | |
| sys.exit(1) | |
| def create_parser(): | |
| """Create and configure the argument parser.""" | |
| parser = argparse.ArgumentParser( | |
| prog="cc_manage", | |
| description="Claude Configuration Manager - Manage API configurations with multiple presets", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| ) | |
| subparsers = parser.add_subparsers( | |
| dest="command", help="Available commands", metavar="COMMAND" | |
| ) | |
| # Setup command | |
| config_parser = subparsers.add_parser( | |
| "config", | |
| aliases=["set", "setup"], | |
| help="Interactive configuration setup", | |
| ) | |
| # Show command | |
| show_parser = subparsers.add_parser( | |
| "show", | |
| aliases=["status", "list"], | |
| help="Show detailed configuration status", | |
| ) | |
| # Switch command | |
| switch_parser = subparsers.add_parser( | |
| "switch", aliases=["use"], help="Switch to a different preset" | |
| ) | |
| switch_parser.add_argument( | |
| "preset_name", help="Name of the preset to switch to" | |
| ) | |
| # Delete command | |
| remove_parser = subparsers.add_parser( | |
| "remove", aliases=["rm"], help="Delete a preset" | |
| ) | |
| remove_parser.add_argument( | |
| "preset_name", help="Name of the preset to delete" | |
| ) | |
| # Export command | |
| active_parser = subparsers.add_parser( | |
| "active", help="Export environment variables for shell evaluation" | |
| ) | |
| active_parser.add_argument( | |
| "shell", | |
| choices=["bash", "zsh", "powershell", "pwsh"], | |
| help="Shell type for export format", | |
| ) | |
| # Path command | |
| path_parser = subparsers.add_parser( | |
| "path", help="Show configuration file path" | |
| ) | |
| # JSON command | |
| json_parser = subparsers.add_parser( | |
| "json", help="Output configuration as JSON" | |
| ) | |
| return parser | |
| def main(): | |
| """Main CLI entry point.""" | |
| config_manager = ConfigManager() | |
| parser = create_parser() | |
| # Handle the case where no arguments are provided - show status | |
| if len(sys.argv) < 2: | |
| print(f"Run `{sys.argv[0]} -h` to get usage\n") | |
| show_status(config_manager) | |
| return | |
| try: | |
| args = parser.parse_args() | |
| if args.command in ["setup", "set", "config"]: | |
| interactive_setup(config_manager) | |
| elif args.command in ["show", "status", "list"]: | |
| show_status(config_manager, verbose=True) | |
| elif args.command in ["switch", "use"]: | |
| if config_manager.switch_preset(args.preset_name): | |
| print(f"✓ Switched to preset '{args.preset_name}'") | |
| else: | |
| sys.exit(1) | |
| if config_manager.apply_to_environment(): | |
| print("✓ Configuration loaded to environment") | |
| show_status(config_manager) | |
| else: | |
| sys.exit(1) | |
| elif args.command in ["remove", "rm"]: | |
| if config_manager.delete_preset(args.preset_name): | |
| print(f"✓ Deleted preset '{args.preset_name}'") | |
| else: | |
| sys.exit(1) | |
| elif args.command == "active": | |
| print_shell(config_manager, args.shell) | |
| elif args.command == "path": | |
| print(config_manager.config_path) | |
| elif args.command == "json": | |
| config = config_manager.load_config() | |
| print(json.dumps(config, indent=2)) | |
| except KeyboardInterrupt: | |
| print("\n\nOperation cancelled.") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment