""" Connect: erzeugt saubere, getestete Konfig-Snippets für IDEs/Agenten auf dem LOKALEN PC (separate Maschine im LAN). Alle zeigen auf den **Gateway** der Box (Lanes `coding`/`chat`, Cockpit-Port :9001/v1) + den **Shared-Memory-MCP** (MC :9001). Wichtig: Host ist die LAN-IP der Box (NICHT eine Proxy-Domain) — das war in v1 die häufigste Fehlerquelle. Der Aufrufer übergibt den Host explizit. """ import json import httpx from config import LLAMA_SWAP_URL, MEM0_SERVICE_URL, PORT DEFAULT_HOST = "192.168.178.151" # IDEs bekommen NUR die 'coding'-Lane zu sehen — die Lane routet intern selbst auf das # passende Modell (Coder/Heavy/…). Ein einziger Eintrag, kein manuelles Modell-Wählen mehr. IDE_MODEL = "coding" def _gw(host: str) -> str: # Eingebauter Gateway: MC2 serviert /v1 selbst (gleicher Port wie das Cockpit). return f"http://{host}:{PORT}/v1" def build_snippets(host: str = DEFAULT_HOST, mcp_script_path: str = r"F:\\Coding Stuff\\mission-control-2\\mcp\\mcp_memory.py", mcp_python: str = "python") -> dict: gw = _gw(host) mc_url = f"http://{host}:{PORT}" cline = json.dumps({ "apiProvider": "openai", "openAiBaseUrl": gw, "openAiApiKey": "local", "openAiModelId": "coding", }, indent=2) opencode = json.dumps({ "provider": { "bosgame": { "npm": "@ai-sdk/openai-compatible", "name": "Bosgame Gateway", "options": {"baseURL": gw, "apiKey": "local"}, "models": {IDE_MODEL: {"name": IDE_MODEL}}, } } }, indent=2) cursor = json.dumps({ "Base URL": gw, "API Key": "local", "Active Model": "coding" }, indent=2) zed = json.dumps({ "language_models": { "openai_compatible": { "bosgame": { "api_url": gw, "available_models": [ {"name": IDE_MODEL, "display_name": IDE_MODEL, "max_tokens": 131072, "capabilities": {"tools": True}} ], } } }, "assistant": { "default_model": { "provider": "openai_compatible", "model": IDE_MODEL } } }, indent=2) cont = json.dumps({ "models": [ {"title": f"Bosgame / {IDE_MODEL}", "provider": "openai", "model": IDE_MODEL, "apiBase": gw, "apiKey": "local"} ] }, indent=2) # Claude Code spricht das Anthropic-Format; der Gateway ist OpenAI-kompatibel und # bietet KEIN /v1/messages (verifiziert). Daher braucht es einen kleinen Übersetzer # (Anthropic ⇄ OpenAI) als Aufsatz. Die env-Vars sind Claude Codes echte Schnittstelle. claude_code = ( f"# Claude Code spricht das Anthropic-Format — der Gateway ist OpenAI-kompatibel ({gw})\n" f"# und hat kein /v1/messages. Dazwischen muss ein Übersetzer (Anthropic ⇄ OpenAI) laufen:\n" f"# • claude-code-router (leichtgewichtig, npm)\n" f"# • oder LiteLLM mit /v1/messages-Bridge\n" f"# Den Übersetzer auf den Gateway zeigen lassen: baseURL={gw}, model=coding, apiKey=local.\n" f"# Dann Claude Code auf den lokalen Übersetzer richten (Beispiel-Port 3456):\n" f"\n" f'export ANTHROPIC_BASE_URL="http://localhost:3456"\n' f'export ANTHROPIC_AUTH_TOKEN="local"\n' f'export ANTHROPIC_MODEL="coding"' ) memory_mcp = json.dumps({ "mcpServers": { "mission-control-memory": { "command": mcp_python, "args": [mcp_script_path], "env": {"MC_URL": mc_url}, } } }, indent=2) return { "host": host, "gateway_url": gw, "mc_url": mc_url, # Leitung 1 — das MODELL. Alle Snippets zeigen auf den OpenAI-kompatiblen Gateway. "tools": { "cline": {"label": "Roo Code / Cline", "lang": "json", "snippet": cline, "note": "OpenAI-Provider → Gateway. Nur Lane 'coding' — die Box routet intern selbst."}, "cursor": {"label": "Cursor", "lang": "json", "snippet": cursor, "note": "Einstellungen ➔ Models ➔ OpenAI API key + Base URL."}, "opencode": {"label": "OpenCode", "lang": "jsonc", "snippet": opencode, "note": "Datei opencode.jsonc, Key 'provider'."}, "zed": {"label": "Zed", "lang": "json", "snippet": zed, "note": "settings.json → language_models.openai_compatible."}, "continue": {"label": "Continue", "lang": "json", "snippet": cont, "note": "~/.continue/config.json (oder config.yaml mit identischen Keys)."}, "claude_code": {"label": "Claude Code", "lang": "bash", "snippet": claude_code, "note": "Braucht einen Anthropic⇄OpenAI-Übersetzer vor dem Gateway."}, }, # Leitung 2 — das GEDÄCHTNIS. Separater MCP-Server, gilt zusätzlich zu jedem Tool oben. "memory": {"label": "Shared Memory (MCP)", "lang": "json", "snippet": memory_mcp, "note": "Eigene Leitung: MCP-Block für jedes MCP-fähige Tool. mcp_memory.py muss lokal liegen."}, } def check_health() -> dict: """Live-Erreichbarkeit der beiden Leitungen, aus Sicht der Box: Leitung 1 = Gateway/Engine (llama-swap), Leitung 2 = Gedächtnis-Sidecar (Mem0).""" gateway = {"ok": False, "detail": "nicht erreichbar"} try: with httpx.Client(timeout=3.0) as c: r = c.get(f"{LLAMA_SWAP_URL}/v1/models") if r.status_code == 200: n = len(r.json().get("data", [])) gateway = {"ok": True, "detail": f"{n} Modelle verfügbar" if n else "bereit"} else: gateway = {"ok": False, "detail": f"HTTP {r.status_code}"} except Exception: # noqa: BLE001 pass memory = {"ok": False, "detail": "nicht erreichbar"} try: with httpx.Client(timeout=3.0) as c: r = c.get(f"{MEM0_SERVICE_URL}/health") memory = ({"ok": True, "detail": "bereit"} if r.status_code == 200 else {"ok": False, "detail": f"HTTP {r.status_code}"}) except Exception: # noqa: BLE001 pass return {"gateway": gateway, "memory": memory}