264 Zeilen
10 KiB
Python
264 Zeilen
10 KiB
Python
"""
|
|
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}") |