KManager / core /kiro_config.py
StarrySkyWorld's picture
Initial commit
494c89b
"""
Kiro Config - динамическое чтение конфигурации из установленного Kiro IDE
Читает актуальную версию, пути и другие параметры напрямую из Kiro IDE,
чтобы не хардкодить значения которые могут измениться.
"""
import os
import json
import hashlib
import platform
from pathlib import Path
from typing import Optional, Dict, Any
from functools import lru_cache
# Fallback значения если Kiro не установлен
FALLBACK_VERSION = "0.7.45"
FALLBACK_MACHINE_ID = None
def get_kiro_install_path() -> Optional[Path]:
"""
Находит путь установки Kiro IDE.
Uses centralized paths from core.paths module.
Приоритет:
1. Переменная окружения KIRO_PATH
2. Централизованные пути из core.paths
"""
# Priority 1: Environment variable
kiro_env = os.environ.get('KIRO_PATH')
if kiro_env:
kiro_path = Path(kiro_env)
if kiro_path.exists():
return kiro_path
# Priority 2: Use centralized paths
from .paths import get_paths
return get_paths().kiro_install_path
@lru_cache(maxsize=1)
def get_kiro_version() -> str:
"""
Читает актуальную версию Kiro IDE из package.json.
Кэшируется для производительности.
"""
kiro_path = get_kiro_install_path()
if not kiro_path:
return FALLBACK_VERSION
# Ищем package.json
package_paths = [
kiro_path / 'resources' / 'app' / 'package.json', # Windows
kiro_path / 'package.json', # macOS/Linux
]
for pkg_path in package_paths:
if pkg_path.exists():
try:
data = json.loads(pkg_path.read_text(encoding='utf-8'))
version = data.get('version')
if version:
return version
except (json.JSONDecodeError, IOError):
pass
return FALLBACK_VERSION
def get_custom_machine_id_path() -> Path:
"""Путь к файлу с кастомным Machine ID (создаётся патчером)"""
return Path.home() / '.kiro-manager-wb' / 'machine-id.txt'
def get_machine_id(use_custom: bool = True) -> str:
"""
Получает Machine ID.
ВАЖНО: Эта функция НЕ кэшируется! Каждый вызов читает файл заново,
чтобы корректно обрабатывать ротацию machine-id.txt.
Приоритет:
1. Кастомный ID из ~/.kiro-manager-wb/machine-id.txt (если use_custom=True)
2. Системный ID (как node-machine-id):
- Windows: SHA256 от MachineGuid из реестра
- Linux: SHA256 от /etc/machine-id
- macOS: SHA256 от IOPlatformUUID
Args:
use_custom: Использовать кастомный ID если есть (по умолчанию True)
"""
# Сначала проверяем кастомный ID (от патчера)
if use_custom:
custom_path = get_custom_machine_id_path()
if custom_path.exists():
try:
custom_id = custom_path.read_text().strip()
# Валидация: должен быть 64-символьный hex
if custom_id and len(custom_id) == 64 and all(c in '0123456789abcdef' for c in custom_id.lower()):
return custom_id.lower()
except Exception:
pass
# Fallback на системный ID (этот кэшируется - системный ID не меняется)
return _get_system_machine_id()
def clear_machine_id_cache() -> None:
"""
Очищает кэш системного Machine ID.
Вызывайте эту функцию если нужно принудительно перечитать
системный machine ID (редкий случай, обычно не нужно).
Примечание: get_machine_id() НЕ кэшируется и всегда читает
файл ~/.kiro-manager-wb/machine-id.txt заново.
"""
_get_system_machine_id.cache_clear()
@lru_cache(maxsize=1)
def _get_system_machine_id() -> str:
"""
Получает системный Machine ID (как node-machine-id).
Кэшируется для производительности.
"""
try:
system = platform.system()
if system == 'Windows':
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Cryptography",
0, winreg.KEY_READ
)
value, _ = winreg.QueryValueEx(key, "MachineGuid")
winreg.CloseKey(key)
return hashlib.sha256(value.encode()).hexdigest()
elif system == 'Linux':
for path in ['/etc/machine-id', '/var/lib/dbus/machine-id']:
if Path(path).exists():
machine_id = Path(path).read_text().strip()
return hashlib.sha256(machine_id.encode()).hexdigest()
elif system == 'Darwin':
# macOS - используем IOPlatformUUID
import subprocess
result = subprocess.run(
['ioreg', '-rd1', '-c', 'IOPlatformExpertDevice'],
capture_output=True, text=True
)
for line in result.stdout.split('\n'):
if 'IOPlatformUUID' in line:
uuid = line.split('"')[-2]
return hashlib.sha256(uuid.encode()).hexdigest()
except Exception:
pass
# Fallback: hostname-based ID
hostname = platform.node()
return hashlib.sha256(hostname.encode()).hexdigest()
def get_kiro_user_agent() -> str:
"""
Формирует User-Agent точно как Kiro IDE.
Формат: KiroIDE-{version}-{machineId}
"""
version = get_kiro_version()
machine_id = get_machine_id()
return f"KiroIDE-{version}-{machine_id}"
def get_kiro_scopes() -> list:
"""
Возвращает scopes для Kiro/CodeWhisperer.
Эти scopes стабильны и вряд ли изменятся.
"""
return [
"codewhisperer:completions",
"codewhisperer:analysis",
"codewhisperer:conversations",
"codewhisperer:taskassist",
"codewhisperer:transformations"
]
def get_client_id_hash(start_url: str = "https://view.awsapps.com/start") -> str:
"""
Вычисляет clientIdHash как Kiro IDE.
SHA1 от JSON.stringify({startUrl})
"""
hash_input = json.dumps({"startUrl": start_url}, separators=(',', ':'))
return hashlib.sha1(hash_input.encode('utf-8')).hexdigest()
@lru_cache(maxsize=1)
def get_kiro_storage_path() -> Optional[Path]:
"""
Возвращает путь к Kiro storage (где хранятся настройки и токены).
"""
system = platform.system()
if system == 'Windows':
base = Path(os.environ.get('APPDATA', '')) / 'Kiro'
elif system == 'Darwin':
base = Path.home() / 'Library' / 'Application Support' / 'Kiro'
else:
base = Path.home() / '.config' / 'Kiro'
if base.exists():
return base
return None
def get_kiro_info() -> Dict[str, Any]:
"""
Возвращает полную информацию о Kiro IDE.
Полезно для отладки.
"""
return {
'install_path': str(get_kiro_install_path() or 'Not found'),
'version': get_kiro_version(),
'machine_id': get_machine_id(),
'user_agent': get_kiro_user_agent(),
'storage_path': str(get_kiro_storage_path() or 'Not found'),
'scopes': get_kiro_scopes(),
}
# Для удобства импорта
__all__ = [
'get_kiro_version',
'get_machine_id',
'get_custom_machine_id_path',
'clear_machine_id_cache',
'_get_system_machine_id',
'get_kiro_user_agent',
'get_kiro_scopes',
'get_client_id_hash',
'get_kiro_install_path',
'get_kiro_storage_path',
'get_kiro_info',
]
if __name__ == '__main__':
# Тест
print("Kiro IDE Configuration:")
print("-" * 40)
info = get_kiro_info()
for key, value in info.items():
print(f" {key}: {value}")