#!/usr/bin/env python3 """ Regenerate ~/.config/opencode/config.json from live nuclide.systems state. Queries: - LiteLLM http://192.168.1.40:14000/v1/models → available models - MCP GW https://mcp.nuclide.systems/servers → registered server groups Run manually or wire into dotfiles install / Coder startup script. Requires: MCP_GATEWAY_TOKEN env var (get via https://mcp.nuclide.systems/mcp-config or set from GATEWAY_API_KEYS in .env) """ from __future__ import annotations import json import os import sys from pathlib import Path # ── config ──────────────────────────────────────────────────────────────────── LITELLM_BASE = os.environ.get("OPENAI_BASE_URL", "http://192.168.1.40:14000/v1") LITELLM_KEY = os.environ.get("OPENAI_API_KEY", "sk-tapirnase") MCP_BASE = "https://mcp.nuclide.systems" CONFIG_PATH = Path.home() / ".config" / "opencode" / "config.json" # Preferred model order for the default picker PREFERRED_CODER = ["qwen3-coder-30b-a3b-instruct", "openai-gpt-oss-120b", "gemini-2.5-pro"] PREFERRED_SMALL = ["gemini-2.5-flash-lite", "mistral-small-latest", "gemini-2.5-flash"] # ── helpers ─────────────────────────────────────────────────────────────────── def _get(url: str, token: str | None = None, api_key: str | None = None) -> dict | list | None: try: import urllib.request, urllib.error req = urllib.request.Request(url) if token: req.add_header("Authorization", f"Bearer {token}") if api_key: req.add_header("Authorization", f"Bearer {api_key}") with urllib.request.urlopen(req, timeout=10) as r: return json.loads(r.read()) except Exception as e: print(f" warn: {url}: {e}", file=sys.stderr) return None def pick_model(available: set[str], preferences: list[str]) -> str | None: for name in preferences: if name in available: return f"openai/{name}" return None # ── main ────────────────────────────────────────────────────────────────────── def main() -> None: print("opencode sync: fetching live state…") # 1. LiteLLM models models_resp = _get(f"{LITELLM_BASE}/models", api_key=LITELLM_KEY) available: set[str] = set() if models_resp and "data" in models_resp: available = {m["id"] for m in models_resp["data"]} print(f" LiteLLM: {len(available)} models") else: print(" LiteLLM: unreachable, using fallback models") default_model = pick_model(available, PREFERRED_CODER) or "openai/qwen3-coder-30b-a3b-instruct" small_model = pick_model(available, PREFERRED_SMALL) or "openai/gemini-2.5-flash-lite" print(f" default: {default_model}") print(f" small: {small_model}") # 2. MCP gateway groups gw_token = os.environ.get("MCP_GATEWAY_TOKEN", "") servers_resp = _get(f"{MCP_BASE}/servers", token=gw_token) groups: set[str] = set() if isinstance(servers_resp, list): for s in servers_resp: if isinstance(s, dict): grp = s.get("group") if grp: groups.add(grp) elif isinstance(servers_resp, dict): for name, cfg in servers_resp.items(): grp = cfg.get("group") if isinstance(cfg, dict) else None if grp: groups.add(grp) if not groups: # fallback: static known groups groups = {"dev", "research", "personal", "image", "storage"} print(f" MCP GW: unreachable, using static groups: {sorted(groups)}") else: print(f" MCP GW: groups = {sorted(groups)}") mcp_entries: dict = {} for group in sorted(groups): mcp_entries[f"nuclide-{group}"] = { "type": "remote", "url": f"{MCP_BASE}/group/{group}/mcp", "enabled": True, "headers": {"Authorization": "Bearer {env:MCP_GATEWAY_TOKEN}"}, } # 3. Build config cfg = { "$schema": "https://opencode.ai/config.json", "model": default_model, "small_model": small_model, "provider": { "openai": { "name": "nuclide-litellm", "options": { "apiKey": "{env:OPENAI_API_KEY}", "baseURL": "{env:OPENAI_BASE_URL}", "timeout": 600000, "chunkTimeout": 60000, }, }, "anthropic": { "options": { "apiKey": "{env:ANTHROPIC_API_KEY}", "baseURL": "{env:ANTHROPIC_BASE_URL}", "timeout": 600000, }, }, }, "mcp": mcp_entries, } CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) CONFIG_PATH.write_text(json.dumps(cfg, indent=2) + "\n") print(f" written: {CONFIG_PATH}") if __name__ == "__main__": main()