""" Terminal Launcher Module Handles launching Claude in WSL terminal with specified directory """ import subprocess import os import tempfile from datetime import datetime from typing import Optional, Union from utils.logger import logger class TerminalLauncher: def __init__(self): self.claude_path = self.find_claude_path() def find_claude_path(self) -> str: """Find Claude path dynamically in WSL""" # Try to find claude using 'which' command in WSL with login shell try: # Hide console window on Windows startupinfo = None if hasattr(subprocess, 'STARTUPINFO'): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE # Use login shell to ensure all PATH and aliases are loaded command = ["wsl", "bash", "-l", "-c", "which claude 2>/dev/null || command -v claude 2>/dev/null || type -p claude 2>/dev/null"] logger.debug(f"Executing command to find Claude: {' '.join(command)}") result = subprocess.run( command, capture_output=True, text=True, timeout=5, startupinfo=startupinfo ) if result.returncode == 0 and result.stdout.strip(): claude_path = result.stdout.strip() # Clean up any extra output from 'type' command if " is " in claude_path: claude_path = claude_path.split(" is ")[-1].strip() logger.info(f"Found Claude at: {claude_path}") print(f"Found Claude at: {claude_path}") return claude_path except Exception as e: logger.error(f"Error finding Claude path: {e}") print(f"Error finding Claude path: {e}") # Fallback paths to try fallback_paths = [ "claude", # Try just 'claude' - let WSL PATH handle it "/usr/local/bin/claude", "/usr/bin/claude", "~/.local/bin/claude", "/home/$USER/.nvm/versions/node/*/bin/claude" # Wildcard for any node version ] # Test each fallback path for path in fallback_paths: try: # Hide console window on Windows startupinfo = None if hasattr(subprocess, 'STARTUPINFO'): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE result = subprocess.run( ["wsl", "bash", "-c", f"test -f {path} && echo 'exists'"], capture_output=True, text=True, timeout=2, startupinfo=startupinfo ) if result.stdout.strip() == 'exists': print(f"Found Claude at fallback: {path}") return path except: continue # If nothing found, default to 'claude' and hope it's in PATH print("Claude path not found, using 'claude' (hoping it's in PATH)") return "claude" def convert_to_wsl_path(self, windows_path: str) -> str: """Convert Windows path to WSL format""" # Handle different path formats windows_path = windows_path.replace('/', '\\') # Extract drive letter and path if ':' in windows_path: drive = windows_path[0].lower() path = windows_path[2:].replace('\\', '/') return f'/mnt/{drive}{path}' return windows_path def launch_claude_wsl(self, project_path: str, project_name: str = "Claude Session") -> Union[subprocess.Popen, None]: """Launch Claude in WSL terminal for specified directory""" try: wsl_path = self.convert_to_wsl_path(project_path) logger.info(f"Launching Claude for project: {project_name} at path: {project_path}") # Check if Windows Terminal is available if os.path.exists(r"C:\Windows\System32\wt.exe"): # Use Windows Terminal cmd = [ "wt.exe", "-w", "0", "new-tab", "--title", f"Claude - {project_name}", "--suppressApplicationTitle", "--", "wsl", "bash", "-l", "-i", "-c", f"cd '{wsl_path}' && echo 'Working directory: {wsl_path}' && echo '' && claude || (echo 'Error: Claude not found. Please install Claude in WSL.' && exec bash)" ] logger.info(f"Executing Windows Terminal command: {' '.join(cmd)}") process = subprocess.Popen(cmd, shell=False) logger.info(f"Successfully launched Claude in Windows Terminal for: {project_path}") print(f"Launched Claude in Windows Terminal for: {project_path}") return process else: # Fallback to cmd window batch_content = f'''@echo off title Claude - {project_name} echo Starting Claude for project: {project_name} echo Working directory: {project_path} echo. wsl bash -l -i -c "cd '{wsl_path}' && claude || (echo 'Error: Claude not found. Please install Claude in WSL.' && exec bash)" pause ''' # Create temporary batch file with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: f.write(batch_content) batch_path = f.name # Launch in new cmd window cmd = ['cmd', '/c', 'start', '', batch_path] logger.info(f"Executing CMD command: {' '.join(cmd)}") logger.debug(f"Batch file content:\n{batch_content}") process = subprocess.Popen(cmd, shell=False) logger.info(f"Successfully launched Claude in CMD for: {project_path}") print(f"Launched Claude in CMD for: {project_path}") return process except Exception as e: logger.error(f"Error launching Claude: {str(e)}", extra={"project_path": project_path, "project_name": project_name}) print(f"Error launching Claude: {str(e)}") return None def launch_claude_vps(self, server: str, username: str, password: str) -> bool: """Launch Claude on VPS via SSH""" try: # Create SSH command with password ssh_command = f'ssh {username}@{server}' # Create batch file for SSH connection batch_content = f'''@echo off title Claude - VPS Server echo Connecting to VPS Server... echo Server: {server} echo Username: {username} echo. echo Please enter password when prompted: {password} echo. echo After connection, type 'claude' to start Claude echo. {ssh_command} pause ''' # Create temporary batch file with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: f.write(batch_content) batch_path = f.name # Launch in new window if os.path.exists(r"C:\Windows\System32\wt.exe"): # Windows Terminal cmd = [ "wt.exe", "-w", "0", "new-tab", "--title", "Claude - VPS", "--", "cmd", "/c", batch_path ] subprocess.Popen(cmd) else: # CMD fallback subprocess.Popen(['cmd', '/c', 'start', '', batch_path]) print(f"Launched VPS connection to: {server}") return True except Exception as e: print(f"Error launching VPS connection: {str(e)}") return False def launch_claude_admin_panel(self, project_name: str = "Admin Panel") -> Union[subprocess.Popen, None]: """Launch Claude for Admin Panel with directory change to /opt/v2-Docker""" try: # Check if Windows Terminal is available if os.path.exists(r"C:\Windows\System32\wt.exe"): # Use Windows Terminal cmd = [ "wt.exe", "-w", "0", "new-tab", "--title", f"Claude - {project_name}", "--suppressApplicationTitle", "--", "wsl", "bash", "-l", "-i", "-c", f"cd /opt/v2-Docker && echo 'Working directory: /opt/v2-Docker' && echo '' && claude || (echo 'Error: Claude not found. Please install Claude in WSL.' && exec bash)" ] process = subprocess.Popen(cmd, shell=False) print(f"Launched Claude in Windows Terminal for Admin Panel") return process else: # Fallback to cmd window batch_content = f'''@echo off title Claude - {project_name} echo Starting Claude for: {project_name} echo Working directory: /opt/v2-Docker echo. wsl bash -l -i -c "cd /opt/v2-Docker && claude || (echo 'Error: Claude not found. Please install Claude in WSL.' && exec bash)" pause ''' # Create temporary batch file with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: f.write(batch_content) batch_path = f.name # Launch in new cmd window process = subprocess.Popen(['cmd', '/c', 'start', '', batch_path], shell=False) print(f"Launched Claude in CMD for Admin Panel") return process except Exception as e: print(f"Error launching Claude for Admin Panel: {str(e)}") return None # Test functions if __name__ == "__main__": launcher = TerminalLauncher() # Test WSL path conversion test_paths = [ "C:\\Users\\hendr\\Desktop\\test", "D:/Projects/MyApp", "C:\\Program Files\\test" ] print("Testing WSL path conversion:") for path in test_paths: wsl_path = launcher.convert_to_wsl_path(path) print(f"{path} -> {wsl_path}")