KManager / app /api /system.py
StarrySkyWorld's picture
Initial commit
494c89b
"""
System API - Kiro status, paths, configuration
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional, List
import platform
import os
from services.kiro_service import KiroService
from core.kiro_config import get_kiro_info, get_kiro_version, get_machine_id
from core.paths import get_paths
from core.config import get_config
router = APIRouter()
kiro_service = KiroService()
class KiroStatus(BaseModel):
installed: bool
running: bool
version: Optional[str] = None
installPath: Optional[str] = None
currentAccount: Optional[str] = None
tokenValid: bool = False
class SystemInfo(BaseModel):
platform: str
osVersion: str
kiroVersion: str
machineId: str
userAgent: str
storagePath: Optional[str] = None
tokensPath: str
backupsPath: str
class PathsInfo(BaseModel):
tokensDir: str
backupsDir: str
kiroStorage: Optional[str] = None
kiroStateDb: Optional[str] = None
customMachineIdFile: str
@router.get("/kiro/status", response_model=KiroStatus)
async def get_kiro_status():
"""Get Kiro IDE status"""
status = kiro_service.get_status()
return KiroStatus(
installed=status.installed,
running=status.running,
version=status.version,
installPath=str(status.install_path) if status.install_path else None,
currentAccount=status.current_account,
tokenValid=status.token_valid
)
@router.post("/kiro/start")
async def start_kiro():
"""Start Kiro IDE"""
try:
kiro_service.start()
return {"success": True, "message": "Kiro started"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/kiro/stop")
async def stop_kiro():
"""Stop Kiro IDE"""
if kiro_service.stop():
return {"success": True, "message": "Kiro stopped"}
else:
raise HTTPException(status_code=500, detail="Failed to stop Kiro")
@router.post("/kiro/restart")
async def restart_kiro():
"""Restart Kiro IDE"""
try:
kiro_service.restart()
return {"success": True, "message": "Kiro restarted"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/info", response_model=SystemInfo)
async def get_system_info():
"""Get system and Kiro configuration info"""
paths = get_paths()
kiro_info = get_kiro_info()
return SystemInfo(
platform=platform.system(),
osVersion=platform.version(),
kiroVersion=kiro_info['version'],
machineId=kiro_info['machine_id'],
userAgent=kiro_info['user_agent'],
storagePath=kiro_info['storage_path'],
tokensPath=str(paths.tokens_dir),
backupsPath=str(paths.backups_dir)
)
@router.get("/paths", response_model=PathsInfo)
async def get_paths_info():
"""Get all relevant paths"""
paths = get_paths()
from core.kiro_config import get_custom_machine_id_path
return PathsInfo(
tokensDir=str(paths.tokens_dir),
backupsDir=str(paths.backups_dir),
kiroStorage=str(paths.kiro_storage_json) if paths.kiro_storage_json else None,
kiroStateDb=str(paths.kiro_state_db) if paths.kiro_state_db else None,
customMachineIdFile=str(get_custom_machine_id_path())
)
@router.get("/health")
async def health_check():
"""Health check with detailed status"""
paths = get_paths()
kiro_status = kiro_service.get_status()
return {
"status": "ok",
"kiro": {
"installed": kiro_status.installed,
"running": kiro_status.running,
"version": kiro_status.version
},
"paths": {
"tokensExist": paths.tokens_dir.exists(),
"backupsExist": paths.backups_dir.exists()
}
}
class ImapSettings(BaseModel):
server: str
user: str
password: str
domain: str
hasDefaults: bool
@router.get("/imap-defaults", response_model=ImapSettings)
async def get_imap_defaults():
"""
Get default IMAP settings from .env file.
Used when user hasn't configured their own catch-all.
"""
config = get_config()
# Get from environment (loaded from .env)
server = os.environ.get('IMAP_SERVER', config.imap.host)
user = os.environ.get('IMAP_USER', config.imap.email)
password = os.environ.get('IMAP_PASSWORD', config.imap.password)
domain = os.environ.get('EMAIL_DOMAIN', config.registration.email_domain)
has_defaults = bool(server and user and password and domain)
return ImapSettings(
server=server or '',
user=user or '',
password=password or '',
domain=domain or '',
hasDefaults=has_defaults
)
class ImapSettingsUpdate(BaseModel):
server: str
user: str
password: str
domain: str
@router.post("/imap-settings")
async def save_imap_settings(settings: ImapSettingsUpdate):
"""
Save IMAP settings to .env file.
This allows web UI to persist settings.
"""
from pathlib import Path
# Get .env path
autoreg_dir = Path(__file__).parent.parent.parent
env_file = autoreg_dir / '.env'
# Read existing .env or create new
env_content = {}
if env_file.exists():
with open(env_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
env_content[key.strip()] = value.strip()
# Update with new settings
env_content['IMAP_SERVER'] = settings.server
env_content['IMAP_USER'] = settings.user
env_content['IMAP_PASSWORD'] = settings.password
env_content['EMAIL_DOMAIN'] = settings.domain
# Write back
with open(env_file, 'w') as f:
for key, value in env_content.items():
f.write(f'{key}={value}\n')
# Update environment variables for current session
os.environ['IMAP_SERVER'] = settings.server
os.environ['IMAP_USER'] = settings.user
os.environ['IMAP_PASSWORD'] = settings.password
os.environ['EMAIL_DOMAIN'] = settings.domain
return {"success": True, "message": "Settings saved"}