KManager / services /kiro_service.py
StarrySkyWorld's picture
Initial commit
494c89b
"""
Kiro Service - интеграция с Kiro IDE
"""
import json
import subprocess
import platform
from pathlib import Path
from typing import Optional, Dict, Any
from dataclasses import dataclass
import sys
from pathlib import Path as SysPath
sys.path.insert(0, str(SysPath(__file__).parent.parent))
from core.paths import get_paths
from core.config import get_config
from core.exceptions import KiroError, KiroNotInstalledError
from core.process_utils import is_kiro_running
@dataclass
class KiroStatus:
"""Статус Kiro IDE"""
installed: bool = False
running: bool = False
data_dir: Optional[Path] = None
install_path: Optional[Path] = None
version: Optional[str] = None
current_account: Optional[str] = None
token_valid: bool = False
class KiroService:
"""Сервис для работы с Kiro IDE"""
def __init__(self):
self.paths = get_paths()
self.config = get_config()
self.os_type = platform.system().lower()
def get_status(self) -> KiroStatus:
"""Получить статус Kiro IDE"""
status = KiroStatus(
installed=self.paths.is_kiro_installed(),
data_dir=self.paths.kiro_data_dir,
install_path=self._get_install_path()
)
if status.installed:
status.running = is_kiro_running()
status.version = self._get_version()
# Текущий аккаунт
if self.paths.kiro_token_file.exists():
try:
data = json.loads(self.paths.kiro_token_file.read_text())
status.current_account = data.get('provider', 'Unknown')
# Проверяем валидность токена
from datetime import datetime
expires_at = data.get('expiresAt')
if expires_at:
exp = datetime.fromisoformat(expires_at.replace('Z', '+00:00'))
status.token_valid = exp > datetime.now(exp.tzinfo)
except:
pass
return status
def start(self) -> bool:
"""Запустить Kiro IDE"""
if not self.paths.is_kiro_installed():
raise KiroNotInstalledError("Kiro is not installed")
try:
if self.os_type == 'windows':
# Ищем Kiro.exe
possible_paths = [
Path.home() / 'AppData' / 'Local' / 'Programs' / 'Kiro' / 'Kiro.exe',
Path('C:/Program Files/Kiro/Kiro.exe'),
Path('C:/Program Files (x86)/Kiro/Kiro.exe'),
]
for kiro_path in possible_paths:
if kiro_path.exists():
subprocess.Popen([str(kiro_path)], shell=True)
return True
# Пробуем через PATH
subprocess.Popen(['kiro'], shell=True)
return True
else:
subprocess.Popen(['kiro'])
return True
except Exception as e:
raise KiroError(f"Failed to start Kiro: {e}")
def stop(self) -> bool:
"""Остановить Kiro IDE"""
try:
if self.os_type == 'windows':
subprocess.run(['taskkill', '/F', '/IM', 'Kiro.exe'],
capture_output=True)
else:
subprocess.run(['pkill', '-f', 'Kiro'], capture_output=True)
return True
except Exception:
return False
def restart(self) -> bool:
"""Перезапустить Kiro IDE"""
was_running = is_kiro_running()
if was_running:
self.stop()
import time
time.sleep(1)
return self.start()
def _get_version(self) -> Optional[str]:
"""Получить версию Kiro"""
# Пробуем прочитать из package.json
if self.paths.kiro_data_dir:
package_json = self.paths.kiro_data_dir.parent / 'package.json'
if package_json.exists():
try:
data = json.loads(package_json.read_text())
return data.get('version')
except:
pass
return None
def _get_install_path(self) -> Optional[Path]:
"""Получить путь установки Kiro"""
if self.os_type == 'windows':
possible_paths = [
Path.home() / 'AppData' / 'Local' / 'Programs' / 'Kiro',
Path('C:/Program Files/Kiro'),
Path('C:/Program Files (x86)/Kiro'),
]
elif self.os_type == 'darwin':
possible_paths = [
Path('/Applications/Kiro.app'),
Path.home() / 'Applications' / 'Kiro.app',
]
else:
possible_paths = [
Path('/usr/share/kiro'),
Path.home() / '.local' / 'share' / 'kiro',
]
for path in possible_paths:
if path.exists():
return path
return None
# =========================================================================
# MCP Configuration
# =========================================================================
def get_mcp_config(self) -> Dict[str, Any]:
"""Получить MCP конфигурацию"""
if not self.paths.kiro_mcp_config.exists():
return {'mcpServers': {}}
try:
return json.loads(self.paths.kiro_mcp_config.read_text())
except:
return {'mcpServers': {}}
def save_mcp_config(self, config: Dict[str, Any]):
"""Сохранить MCP конфигурацию"""
self.paths.kiro_mcp_config.parent.mkdir(parents=True, exist_ok=True)
self.paths.kiro_mcp_config.write_text(json.dumps(config, indent=2))
def add_mcp_server(self, name: str, server_config: Dict[str, Any]):
"""Добавить MCP сервер"""
config = self.get_mcp_config()
config.setdefault('mcpServers', {})[name] = server_config
self.save_mcp_config(config)
def remove_mcp_server(self, name: str) -> bool:
"""Удалить MCP сервер"""
config = self.get_mcp_config()
if name in config.get('mcpServers', {}):
del config['mcpServers'][name]
self.save_mcp_config(config)
return True
return False
def toggle_mcp_server(self, name: str, enabled: bool):
"""Включить/выключить MCP сервер"""
config = self.get_mcp_config()
if name in config.get('mcpServers', {}):
config['mcpServers'][name]['disabled'] = not enabled
self.save_mcp_config(config)
# =========================================================================
# Helpers
# =========================================================================
def print_status(self):
"""Вывести статус Kiro"""
status = self.get_status()
print(f"\n{'='*50}")
print("[C] Kiro IDE Status")
print(f"{'='*50}")
print(f"\n[P] Installed: {'[OK]' if status.installed else '[X]'}")
if status.installed:
print(f"[R] Running: {'[OK]' if status.running else '[X]'}")
if status.version:
print(f"[V] Version: {status.version}")
print(f"[F] Data dir: {status.data_dir}")
if status.current_account:
token_status = '[OK] valid' if status.token_valid else '[X] expired'
print(f"\n[K] Current account: {status.current_account}")
print(f" Token: {token_status}")
print(f"\n{'='*50}")