/
AlexandrNarbaev
/
opencode_initializer
Обзор
Документация
Войти
/
AlexandrNarbaev
/
opencode_initializer
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/lib/18-opencode-json.sh
862 строки
38 KB
Alexandr Narbaev
v3.1.0: core hardening + SSOT completion + macOS compat + SDD commands
08 авг 2026, 22:20
08 авг 2026, 22:20
af6ca43
Код
Авторство
О чём код?
#!/usr/bin/env bash # lib/18-opencode-json.sh — opencode.json generation with all detected MCPs + LSP (STEP 19) # Requires: MODE, PROJECT_DIR set -euo pipefail if [ "$MODE" = "full" ] || [ "$MODE" = "reinit" ] || [ "$MODE" = "fix-config" ]; then section "Generating opencode.json with all MCPs" generate_opencode_json() { python3 <<'PYGEN' import json, os, shutil, subprocess, copy, sys home = os.path.expanduser("~") project_dir = os.environ.get("PROJECT_DIR", os.path.join(home, "projects")) config_path = os.path.join(home, ".config", "opencode", "opencode.json") # Load existing config to preserve user choices existing_mcp = {} if os.path.exists(config_path): try: with open(config_path) as f: existing = json.load(f) existing_mcp = existing.get("mcp", {}) except: pass # Load secrets from secrets.env for provider and token detection secrets = {} secrets_file = os.path.join(home, ".config", "opencode", "secrets.env") if os.path.exists(secrets_file): try: for line in open(secrets_file).readlines(): line = line.strip() if "=" in line: line = line.replace("export ", "", 1) k, v = line.split("=", 1) secrets[k.strip()] = v.strip().strip('"').strip("'") except: pass def cmd_exists(cmd): return shutil.which(cmd) is not None def npx_available(): return shutil.which("npx") is not None or shutil.which("bunx") is not None def pkg_installed(pkg_name): """Check if npm package is installed globally (npm or bun)""" if cmd_exists(pkg_name): return True try: result = os.popen(f"npm list -g {pkg_name} 2>/dev/null").read() if pkg_name in result and "(empty)" not in result: return True except: pass try: result = os.popen(f"bun pm ls -g 2>/dev/null").read() if pkg_name in result: return True except: pass return False # Env-var name registry for each provider — used to generate api_key references PROVIDER_ENV_VARS = { "deepseek": "DEEPSEEK_API_KEY", "opencode": "OPENCODE_API_KEY", "zai": "ZAI_API_KEY", "openrouter": "OPENROUTER_API_KEY", "xai": "XAI_API_KEY", "mimo": "MIMO_API_KEY", "minimax": "MINIMAX_API_KEY", "openai": "OPENAI_API_KEY", "anthropic": "ANTHROPIC_API_KEY", "google": "GOOGLE_API_KEY", "mistral": "MISTRAL_API_KEY", "groq": "GROQ_API_KEY", "together": "TOGETHER_API_KEY", "cohere": "COHERE_API_KEY", "fireworks": "FIREWORKS_API_KEY", "cerebras": "CEREBRAS_API_KEY", "perplexity": "PERPLEXITY_API_KEY", "alibaba": "ALIBABA_API_KEY", "deepinfra": "DEEPINFRA_API_KEY", } def _build_providers(): """Build provider config based on available API keys, with fallback chains. When ISOLATED_CIRCUIT=true, generates local-only OpenAI-compatible providers. API keys use ${ENV_VAR} syntax — team-shareable without exposing secrets.""" opts = {"options": {"timeout": 600000, "chunkTimeout": 60000, "setCacheKey": True}} providers = {} isolated = os.environ.get("ISOLATED_CIRCUIT", "false").lower() in ("true", "1", "yes", "on") local_endpoint = os.environ.get("OPENCODE_LOCAL_ENDPOINT") or os.environ.get("OPencode_LOCAL_ENDPOINT") or "http://localhost:4000/v1" if isolated: # ── Isolated Circuit: local OpenAI-compatible providers only ───────── local_backends = { "ollama": ("http://localhost:11434/v1", "ollama/qwen3:14b", "ollama/qwen3:0.6b"), } # Auto-detect running backends import urllib.request as urlreq for name, (url, default_model, small_model) in local_backends.items(): try: urlreq.urlopen(url + "/models", timeout=2) providers[name] = dict(opts) providers[name]["base_url"] = url providers[name]["default_model"] = default_model providers[name]["small_model"] = small_model providers[name]["api_key"] = "not-needed" except Exception: pass primary = "ollama" if primary in providers: providers[primary].pop("fallback", None) return providers # ── Cloud providers (normal mode) ──────────────────────────────────────── all_keys = { "deepseek": os.environ.get("DEEPSEEK_API_KEY") or secrets.get("DEEPSEEK_API_KEY", ""), "opencode": os.environ.get("OPENCODE_API_KEY") or secrets.get("OPENCODE_API_KEY", ""), "zai": os.environ.get("ZAI_API_KEY") or secrets.get("ZAI_API_KEY", ""), "openrouter": os.environ.get("OPENROUTER_API_KEY") or secrets.get("OPENROUTER_API_KEY", ""), "xai": os.environ.get("XAI_API_KEY") or secrets.get("XAI_API_KEY", ""), "mimo": os.environ.get("MIMO_API_KEY") or secrets.get("MIMO_API_KEY", ""), "minimax": os.environ.get("MINIMAX_API_KEY") or secrets.get("MINIMAX_API_KEY", ""), "openai": os.environ.get("OPENAI_API_KEY") or secrets.get("OPENAI_API_KEY", ""), "anthropic": os.environ.get("ANTHROPIC_API_KEY") or secrets.get("ANTHROPIC_API_KEY", ""), "google": os.environ.get("GOOGLE_API_KEY") or secrets.get("GOOGLE_API_KEY", ""), "mistral": os.environ.get("MISTRAL_API_KEY") or secrets.get("MISTRAL_API_KEY", ""), "groq": os.environ.get("GROQ_API_KEY") or secrets.get("GROQ_API_KEY", ""), "together": os.environ.get("TOGETHER_API_KEY") or secrets.get("TOGETHER_API_KEY", ""), "cohere": os.environ.get("COHERE_API_KEY") or secrets.get("COHERE_API_KEY", ""), "fireworks": os.environ.get("FIREWORKS_API_KEY") or secrets.get("FIREWORKS_API_KEY", ""), "cerebras": os.environ.get("CEREBRAS_API_KEY") or secrets.get("CEREBRAS_API_KEY", ""), "perplexity": os.environ.get("PERPLEXITY_API_KEY") or secrets.get("PERPLEXITY_API_KEY", ""), "alibaba": os.environ.get("ALIBABA_API_KEY") or secrets.get("ALIBABA_API_KEY", ""), "deepinfra": os.environ.get("DEEPINFRA_API_KEY") or secrets.get("DEEPINFRA_API_KEY", ""), } # ── Load provider models from SSOT JSON (with hardcoded fallback) ────── def _model_list_from_json(json_path="src/data/providers.json"): """Read provider→(model, small_model) from providers.json SSOT.""" pmodels = {} try: with open(json_path) as f: data = json.load(f) for name, info in data.get("providers", {}).items(): model = info.get("model", "") small = info.get("small_model", "") or model if model: pmodels[name] = (f"{name}/{model}", f"{name}/{small}" if small else f"{name}/{model}") except Exception: pass return pmodels provider_models = _model_list_from_json() if not provider_models: # Hardcoded fallback (v3.0 baseline) provider_models = { "deepseek": ("deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash"), "zai": ("zai/glm-5.2", "zai/glm-5-turbo"), "openrouter": ("openrouter/deepseek/deepseek-v4-pro", "openrouter/deepseek/deepseek-v4-flash"), "openai": ("openai/gpt-5.5", "openai/gpt-5.4-mini"), "anthropic": ("anthropic/claude-opus-4-8", "anthropic/claude-sonnet-4-6"), "google": ("google/gemini-3.5-flash", "google/gemini-3.1-flash-lite"), "mistral": ("mistral/mistral-large-latest", "mistral/mistral-small-latest"), "groq": ("groq/llama-4-maverick", "groq/llama-4-scout"), "together": ("together/meta-llama/Llama-4-Maverick", "together/meta-llama/Llama-4-Scout"), "xai": ("xai/grok-4.3", "xai/grok-4.20-0309-non-reasoning"), "minimax": ("minimax/MiniMax-M3", "minimax/MiniMax-M3"), "mimo": ("mimo/mimo-v2.5", "mimo/mimo-v2.5"), "perplexity": ("perplexity/sonar-pro", "perplexity/sonar"), "alibaba": ("alibaba/qwen3.7-plus", "alibaba/qwen3.6-flash"), "deepinfra": ("deepinfra/meta-llama/Llama-4-Maverick", "deepinfra/meta-llama/Llama-4-Scout"), } fallback_chain = ["deepseek", "zai", "groq", "together", "openai", "minimax"] # ── Model governance: load policy and filter providers ─────────────────── denied = set() allowed = set() policy_path = os.path.join(home, ".config", "opencode", "model-policy.json") if os.path.exists(policy_path): try: with open(policy_path) as pf: policy = json.load(pf) mode = policy.get("mode", "allow-all") denied = set(policy.get("denied_providers", [])) if mode in ("allowlist", "corporate"): allowed = set(policy.get("allowed_providers", [])) if denied: sys.stderr.write(f" [policy] denied providers: {', '.join(sorted(denied))}\n") if allowed: sys.stderr.write(f" [policy] allowed providers (mode={mode}): {', '.join(sorted(allowed))}\n") except Exception: pass for provider, key in all_keys.items(): # Skip providers denied by model governance policy if provider in denied: sys.stderr.write(f" [policy] provider {provider} denied — skipped\n") continue # In allowlist/corporate mode, only include explicitly allowed providers if allowed and provider not in allowed: sys.stderr.write(f" [policy] provider {provider} not in allowlist — skipped\n") continue if key or provider in ("deepseek", "opencode", "zai"): providers[provider] = copy.deepcopy(opts) if provider in provider_models: providers[provider]["default_model"] = provider_models[provider][0] providers[provider]["small_model"] = provider_models[provider][1] # Use ${ENV_VAR} reference so teams can share config without exposing keys env_name = PROVIDER_ENV_VARS.get(provider) if env_name: providers[provider]["api_key"] = "${" + env_name + "}" # Reset stale top-level base_url/api_key from previous broken configs # (legacy configs incorrectly shared one local proxy across ALL providers). # Custom OpenAI-compatible providers must use options.baseURL (camelCase), # and native providers (deepseek, opencode-go, xai, etc.) must NOT have base_url. providers[provider].pop("base_url", None) providers[provider].pop("api_key", None) if "api_key" in providers[provider] and provider != "opencode" else None # Set baseURL ONLY for OpenAI-compatible custom providers (use options.baseURL) if provider == "zai": providers[provider].setdefault("options", {}) providers[provider]["options"]["baseURL"] = "https://api.z.ai/api/paas/v4" elif provider == "openrouter": providers[provider].setdefault("options", {}) providers[provider]["options"]["baseURL"] = "https://openrouter.ai/api/v1" elif provider == "deepinfra": providers[provider].setdefault("options", {}) providers[provider]["options"]["baseURL"] = "https://api.deepinfra.com/v1/openai" elif provider == "fireworks": providers[provider].setdefault("options", {}) providers[provider]["options"]["baseURL"] = "https://api.fireworks.ai/inference/v1" elif provider == "minimax": providers[provider].setdefault("options", {}) providers[provider]["options"]["baseURL"] = "https://api.minimax.io/v1" if provider not in ("deepseek", "zai"): providers[provider]["fallback"] = ["deepseek"] elif provider == "deepseek": chain = [p for p in fallback_chain if p != "deepseek" and p in providers] if chain: providers["deepseek"]["fallback"] = chain[:3] elif provider == "zai": chain = [p for p in fallback_chain if p != "zai" and p in providers] if chain: providers["zai"]["fallback"] = chain[:3] return providers def bun_bin(name): """Return absolute path to bun global binary, or None""" path = os.path.join(home, ".bun", "bin", name) return path if os.path.exists(path) else None def local_cmd(pkg, bun_name, *extra_args): """Return [bun_bin, ...extra] if bun binary exists, else [npx, -y, pkg, ...extra]""" bb = bun_bin(bun_name) if bb: return [bb] + list(extra_args) return ["npx", "-y", pkg] + list(extra_args) # Detect all installed MCPs mcps = {} if cmd_exists("c7-mcp-server"): mcps["context7"] = {"type": "local", "command": ["c7-mcp-server"], "enabled": True} # Git MCP (via uvx or pip) if cmd_exists("mcp-server-git"): mcps["git"] = {"type": "local", "command": ["uvx", "mcp-server-git"], "enabled": True} if pkg_installed("@modelcontextprotocol/server-memory"): mcps["memory"] = {"type": "local", "command": local_cmd("@modelcontextprotocol/server-memory", "mcp-server-memory"), "enabled": True} # Fetch MCP (Python, via uvx) if cmd_exists("mcp-server-fetch"): mcps["fetch"] = {"type": "local", "command": ["uvx", "mcp-server-fetch"], "enabled": True} # Time MCP (Python, via uvx) if cmd_exists("mcp-server-time"): mcps["time"] = {"type": "local", "command": ["uvx", "mcp-server-time"], "enabled": True} if pkg_installed("@modelcontextprotocol/server-redis"): mcps["redis"] = {"type": "local", "command": local_cmd("@modelcontextprotocol/server-redis", "mcp-server-redis"), "enabled": True} if pkg_installed("@modelcontextprotocol/server-filesystem"): mcps["filesystem"] = {"type": "local", "command": local_cmd("@modelcontextprotocol/server-filesystem", "mcp-server-filesystem", project_dir), "enabled": True} if pkg_installed("@pimzino/agentic-tools-mcp"): mcps["agentic-tools"] = {"type": "local", "command": local_cmd("@pimzino/agentic-tools-mcp", "agentic-tools-mcp"), "enabled": True} if cmd_exists("codegraph"): mcps["codegraph"] = {"type": "local", "command": ["codegraph", "serve", "--mcp"], "enabled": True} if pkg_installed("@playwright/mcp"): mcps["playwright"] = {"type": "local", "command": local_cmd("@playwright/mcp", "playwright-mcp"), "enabled": True} if pkg_installed("agent-browser-mcp-server"): mcps["agent-browser"] = {"type": "local", "command": local_cmd("agent-browser-mcp-server", "agent-browser-mcp-server"), "enabled": True} if pkg_installed("@loopsense/mcp"): mcps["loopsense"] = {"type": "local", "command": local_cmd("@loopsense/mcp", "loopsense"), "enabled": True} if pkg_installed("@modelcontextprotocol/server-github"): gh_token = os.environ.get("GITHUB_TOKEN") or secrets.get("GITHUB_TOKEN", "") gh_entry = {"type": "local", "command": local_cmd("@modelcontextprotocol/server-github", "mcp-server-github")} if gh_token: gh_entry["enabled"] = True gh_entry["env"] = {"GITHUB_PERSONAL_ACCESS_TOKEN": gh_token} else: gh_entry["enabled"] = False mcps["github"] = gh_entry if pkg_installed("@modelcontextprotocol/server-postgres"): gh_entry_pg = {"type": "local", "command": local_cmd("@modelcontextprotocol/server-postgres", "mcp-server-postgres")} gh_entry_pg["enabled"] = os.environ.get("PG_CONNECTION_STRING", "") != "" if gh_entry_pg["enabled"]: gh_entry_pg["env"] = {"PG_CONNECTION_STRING": os.environ.get("PG_CONNECTION_STRING", "")} mcps["postgres"] = gh_entry_pg if pkg_installed("mcp-server-gitlab"): gl_token = os.environ.get("GITLAB_TOKEN") or secrets.get("GITLAB_TOKEN", "") gl_entry = {"type": "local", "command": local_cmd("mcp-server-gitlab", "mcp-server-gitlab")} if gl_token: gl_entry["enabled"] = True gl_entry["env"] = {"GITLAB_PERSONAL_ACCESS_TOKEN": gl_token} else: gl_entry["enabled"] = False mcps["gitlab"] = gl_entry if pkg_installed("mcp-server-google-maps"): gm_key = os.environ.get("GOOGLE_MAPS_KEY") or secrets.get("GOOGLE_MAPS_KEY", "") gm_entry = {"type": "local", "command": local_cmd("mcp-server-google-maps", "mcp-server-google-maps")} if gm_key: gm_entry["enabled"] = True gm_entry["env"] = {"GOOGLE_MAPS_API_KEY": gm_key} else: gm_entry["enabled"] = False mcps["google-maps"] = gm_entry if pkg_installed("@modelcontextprotocol/server-sequential-thinking"): mcps["sequential-thinking"] = {"type": "local", "command": local_cmd("@modelcontextprotocol/server-sequential-thinking", "mcp-server-sequential-thinking"), "enabled": True} if pkg_installed("@scitrera/memorylayer-mcp-server"): mcps["memorylayer"] = {"type": "local", "command": local_cmd("@scitrera/memorylayer-mcp-server", "memorylayer-mcp"), "enabled": True} if pkg_installed("chrome-devtools-mcp"): mcps["chrome-devtools"] = {"type": "local", "command": local_cmd("chrome-devtools-mcp", "chrome-devtools-mcp"), "enabled": True} if pkg_installed("brave-search-mcp"): bs_key = os.environ.get("BRAVE_API_KEY") or secrets.get("BRAVE_API_KEY", "") bs_entry = {"type": "local", "command": local_cmd("brave-search-mcp", "brave-search-mcp")} if bs_key: bs_entry["enabled"] = True bs_entry["env"] = {"BRAVE_API_KEY": bs_key} else: bs_entry["enabled"] = False mcps["brave-search"] = bs_entry # SearXNG web search MCP — self-hosted, no API key needed # Prioritize sanitizer proxy (port 8888) over direct SearXNG (port 8080) if pkg_installed("mcp-searxng"): sanitizer_ok = os.system("curl -sf http://localhost:8888/search?q=test >/dev/null 2>&1") == 0 searxng_url = "http://localhost:8888" if sanitizer_ok else "http://localhost:8080" mcps["websearch"] = { "type": "local", "command": local_cmd("mcp-searxng", "mcp-searxng"), "enabled": True, "env": {"SEARXNG_URL": searxng_url} } # SQLite MCP (Python, via uvx) if cmd_exists("mcp-server-sqlite") or shutil.which("uvx"): mcps["sqlite"] = {"type": "local", "command": ["uvx", "mcp-server-sqlite"], "enabled": True} # Excalidraw MCP — diagram generation (Python, via uvx) if shutil.which("uvx"): mcps["excalidraw"] = {"type": "local", "command": ["uvx", "excalidraw-architect-mcp"], "enabled": True} # Context7 official MCP (replaces community c7-mcp-server) if pkg_installed("@upstash/context7-mcp"): mcps["context7-official"] = {"type": "local", "command": local_cmd("@upstash/context7-mcp", "context7-mcp"), "enabled": True} # Notion MCP (optional, requires API key) if pkg_installed("@notionhq/notion-mcp-server"): n_key = os.environ.get("NOTION_API_KEY") or secrets.get("NOTION_API_KEY", "") n_entry = {"type": "local", "command": local_cmd("@notionhq/notion-mcp-server", "notion-mcp-server")} n_entry["enabled"] = bool(n_key) if n_key: n_entry["env"] = {"NOTION_API_KEY": n_key} mcps["notion"] = n_entry # Remote MCP servers (no local binary needed) mcps["sentry"] = {"type": "remote", "url": "https://mcp.sentry.dev/mcp", "enabled": existing_mcp.get("sentry", {}).get("enabled", False), "timeout": 30000} mcps["grep"] = {"type": "remote", "url": "https://mcp.grep.app", "enabled": existing_mcp.get("grep", {}).get("enabled", False), "timeout": 30000} # LSP servers — detect installed, configure in opencode.json lsp_config = {} def lsp_check(binary_name, lsp_key, lsp_command, extensions): if cmd_exists(binary_name): lsp_config[lsp_key] = {"command": lsp_command, "extensions": extensions} lsp_check("gopls", "gopls", ["gopls"], [".go"]) lsp_check("rust-analyzer", "rust-analyzer", ["rust-analyzer"], [".rs"]) lsp_check("typescript-language-server", "typescript", ["typescript-language-server", "--stdio"], [".ts", ".tsx", ".js", ".jsx"]) lsp_check("pyright-langserver", "pyright", ["pyright-langserver", "--stdio"], [".py"]) lsp_check("omnisharp", "omnisharp", ["omnisharp", "--stdio"], [".cs"]) lsp_check("yaml-language-server", "yaml", ["yaml-language-server", "--stdio"], [".yaml", ".yml"]) lsp_check("marksman", "marksman", ["marksman", "server"], [".md"]) lsp_check("taplo", "taplo", ["taplo", "lsp", "stdio"], [".toml"]) lsp_check("lua-language-server", "lua", ["lua-language-server"], [".lua"]) lsp_check("zls", "zls", ["zls"], [".zig"]) lsp_check("bash-language-server", "bash", ["bash-language-server", "start"], [".sh", ".bash"]) lsp_check("docker-langserver", "dockerfile", ["docker-langserver", "--stdio"], ["Dockerfile"]) lsp_check("vscode-css-language-server", "css", ["vscode-css-language-server", "--stdio"], [".css", ".scss", ".less"]) lsp_check("vscode-html-language-server", "html", ["vscode-html-language-server", "--stdio"], [".html", ".htm"]) lsp_check("vscode-json-language-server", "json", ["vscode-json-language-server", "--stdio"], [".json", ".jsonc"]) # Plugins — read from tier-based registry (plugins.yml) plugins = ["opencode-codegraph"] plugin_registry_path = os.path.join(home, ".config", "opencode", "plugins.json") project_override = os.path.join(os.environ.get("PROJECT_DIR", os.path.join(home, "projects")), ".opencode", "plugins.json") # Built-in default: when no registry file exists, include all known plugins # (design intent from commit 4a665b6 — "without plugins.json, all plugins included as before") _DEFAULT_ALL_PLUGINS = [ "opencode-codegraph", "opencode-goal-mode", "opencode-swarm", "open-orchestra", "@tarquinen/opencode-dcp", "opencode-background-agents", "opencode-devcontainers", "opencode-worktree", "opencode-daytona", "opencode-scheduler", "opencode-conductor", "opencode-token-tracker", "opencode-vibeguard", "opencode-supermemory", "opencode-notify", "opencode-pty", "opencode-ignore", "opencode-snip", "opencode-snippets", "envsitter-guard", "opencode-command-inject", "opencode-auto-fallback", "opencode-goal-plugin", "opencode-zellij-namer", "@zenobius/opencode-skillful", "@morphllm/opencode-morph-plugin", "@lyculs/opencode-firecrawl", "opencode-websearch-cited", "@devtheops/opencode-plugin-otel", ] def load_plugin_registry(): registry = {"tiers": {"always": [], "conditional": {}, "on_demand": []}} if os.path.exists(plugin_registry_path): try: with open(plugin_registry_path) as f: data = json.load(f) if "tiers" in data: registry["tiers"] = data["tiers"] except: pass if os.path.exists(project_override): try: with open(project_override) as f: data = json.load(f) if "tiers" in data: for tier_name in ["always", "on_demand"]: if tier_name in data["tiers"]: registry["tiers"][tier_name] = data["tiers"][tier_name] if "conditional" in data["tiers"]: for pkg, cfg in data["tiers"]["conditional"].items(): registry["tiers"]["conditional"][pkg] = cfg except: pass # Fallback: when no registry file exists, include ALL known plugins. # Design intent: "without plugins.json, all plugins included as before" (commit 4a665b6). if not os.path.exists(plugin_registry_path) and not os.path.exists(project_override): registry["tiers"]["always"] = _DEFAULT_ALL_PLUGINS return registry def check_dep(name): if name == "postgresql": return subprocess.run(["docker", "ps", "--format", "{{.Names}}"], capture_output=True, text=True).stdout.find("opencode-postgres") != -1 if name == "qdrant": return subprocess.run(["docker", "ps", "--format", "{{.Names}}"], capture_output=True, text=True).stdout.find("opencode-qdrant") != -1 if name == "redis": return subprocess.run(["docker", "ps", "--format", "{{.Names}}"], capture_output=True, text=True).stdout.find("opencode-redis") != -1 if name == "docker": return shutil.which("docker") is not None if name == "daytona_daemon": return shutil.which("daytona") is not None if name == "git_worktree": return os.path.exists(os.path.join(os.environ.get("PROJECT_DIR", os.path.join(home, "projects")), ".git", "worktrees")) or False if name == "goal_mode": return True # always available if name == "zellij": return shutil.which("zellij") is not None return False registry = load_plugin_registry() # Always tier — always included if package installed for pkg in registry["tiers"].get("always", []): if pkg == "opencode-codegraph": continue # already added npm_name = pkg if pkg_installed(npm_name): plugins.append(pkg) # Conditional tier — included if enabled AND deps met for pkg, cfg in registry["tiers"].get("conditional", {}).items(): npm_name = pkg if not isinstance(cfg, dict): continue enabled = cfg.get("enabled", False) auto = cfg.get("auto_enable", False) deps = cfg.get("depends", []) # Auto-enable if deps are met if auto and not enabled: if all(check_dep(d) for d in deps): enabled = True if enabled and pkg_installed(npm_name): plugins.append(pkg) # On-demand tier — only if explicitly enabled by user for pkg in registry["tiers"].get("on_demand", []): npm_name = pkg if isinstance(pkg, str) else pkg.get("name", "") if pkg_installed(npm_name): plugins.append(pkg) # Always add dcp with config if installed (legacy — will move to always tier) if pkg_installed("@tarquinen/opencode-dcp") and "opencode-dcp" not in plugins: plugins.append(["opencode-dcp", { "compress": { "enabled": True, "minContextLimit": 20000, "maxContextLimit": 48000, "autoCompaction": True }, "prune": { "enabled": True, "protectTokens": 40000, "minTokens": 20000 } }]) # Build config providers = _build_providers() config = { "$schema": "https://opencode.ai/config.json", "model": "deepseek/deepseek-v4-pro", "small_model": "deepseek/deepseek-v4-flash", "default_agent": "build", "autoupdate": True, "compaction": { "auto": True, "prune": True, "tail_turns": 3, "reserved": 8000 }, "provider": providers, "tool_output": { "max_lines": 2000, "max_bytes": 51200 }, "watcher": { "ignore": ["node_modules/**", "dist/**", ".git/**", "target/**", "__pycache__/**"] }, "experimental": { "mcp_timeout": 45000, "openTelemetry": True, "mcp_warm_start": True, "token_counter": True, "performance_stats": True, "context_tracker": True }, "lsp": lsp_config, "plugin": plugins, "mcp": mcps, "agent": { "build": { "mode": "primary", "model": "deepseek/deepseek-v4-pro", "temperature": 0.2, "system": "[CTX: build] Build agent. Apply CO-STAR output. Prefer atomic edits. Verify after write. Use System 2 for >3 files.", "permission": { "edit": "allow", "bash": { "git *": "allow", "npm *": "allow", "grep *": "allow", "rg *": "allow", "rm *": "deny", "*": "ask" }, "read": { "*.env": "deny", "*.env.*": "deny", "*": "allow" }, "glob": "allow", "grep": "allow", "write": "allow", "task": { "general": "allow", "explore": "allow", "scout": "allow", "reviewer": "allow", "researcher": "allow", "security-auditor": "allow", "code-reviewer": "allow" } } }, "plan": { "mode": "primary", "model": "zai/glm-5.2" if "zai" in providers else "deepseek/deepseek-v4-pro", "temperature": 0.1, "system": "[CTX: plan] Plan agent. Use System 2 (methodical) reasoning. Verify ALL claims against Source Ladder. Never fabricate.", "permission": { "edit": "deny", "bash": "deny", "read": "allow", "glob": "allow", "grep": "allow", "write": "deny", "webfetch": "allow", "task": { "researcher": "allow", "explore": "allow" } } }, "general": { "mode": "subagent", "model": "deepseek/deepseek-v4-pro", "temperature": 0.2, "system": "[CTX: general] General agent. Use System 1 for fast tasks, System 2 for complex. Delegate to researcher/scout for investigation." }, "explore": { "mode": "subagent", "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: explore] Explorer agent. Read-only. Use grep/glob/codegraph for discovery. Report findings concisely.", "permission": { "edit": "deny", "bash": "deny", "write": "deny" } }, "code-reviewer": { "mode": "subagent", "model": "zai/glm-5.2" if "zai" in providers else "deepseek/deepseek-v4-pro", "temperature": 0.1, "system": "[CTX: review] Code reviewer. Apply Source Ladder for API verification. Flag deprecated features. Check for security issues.", "permission": { "edit": "deny", "bash": { "git diff *": "allow", "git log *": "allow", "*": "deny" }, "task": "deny" } }, "compaction": { "mode": "primary", "hidden": True, "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: compact] Compaction agent. Preserve key decisions, WAL entries, and protected constraints. Discard redundant context." }, "researcher": { "mode": "subagent", "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: research] Research agent. Use web search and documentation. Cite sources [L1]-[L4]. Flag speculation.", "permission": {"edit": "deny", "bash": "deny", "write": "deny", "read": "allow", "grep": "allow", "glob": "allow", "webfetch": "allow"} }, "scout": { "mode": "subagent", "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: scout] Scout agent. Fast codebase exploration. Find files, symbols, patterns. Report paths and line numbers.", "permission": {"edit": "deny", "bash": "deny", "write": "deny", "read": "allow", "grep": "allow", "glob": "allow"} }, "reviewer": { "mode": "subagent", "model": "zai/glm-5.2" if "zai" in providers else "deepseek/deepseek-v4-pro", "temperature": 0.1, "system": "[CTX: review] Review agent. Adversarial review. Find bugs, regressions, security issues. Be specific with file:line references.", "permission": {"edit": "deny", "bash": {"git diff *": "allow", "git log *": "allow", "*": "deny"}, "write": "deny", "read": "allow", "grep": "allow"} }, "security-auditor": { "mode": "subagent", "model": "deepseek/deepseek-v4-pro", "temperature": 0.1, "system": "[CTX: security] Security auditor. Check for: secrets in code, injection vectors, unsafe commands, exposed ports.", "permission": {"edit": "deny", "write": "deny", "read": "allow", "grep": "allow", "bash": {"git diff *": "allow", "grep *": "allow", "*": "deny"}} }, "test-engineer": { "mode": "subagent", "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: test] Test engineer. Write tests that verify behavior, not implementation. Cover edge cases. Test-driven approach.", "permission": {"edit": "allow", "write": "allow", "read": "allow", "grep": "allow", "glob": "allow", "bash": {"npm test *": "allow", "cargo test *": "allow", "go test *": "allow", "python3 *test*": "allow", "*": "ask"}} }, "critic": { "mode": "subagent", "model": "deepseek/deepseek-v4-pro", "temperature": 0.3, "system": "[CTX: critic] Critic agent. Challenge assumptions. Find contradictions. Play devil advocate. Rate confidence 0-1.", "permission": {"edit": "deny", "write": "deny", "bash": "deny", "read": "allow", "grep": "allow", "glob": "allow"} }, "sme": { "mode": "subagent", "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: sme] Domain expert. Provide deep knowledge in specific domains. Flag when outside expertise. Cite authoritative sources.", "permission": {"edit": "deny", "write": "deny", "read": "allow", "grep": "allow", "glob": "allow", "webfetch": "allow"} }, "docs": { "mode": "subagent", "model": "deepseek/deepseek-v4-flash", "temperature": 0.1, "system": "[CTX: docs] Documentation agent. Write clear, concise docs. Use active voice. Keep examples current. Verify all commands work.", "permission": {"edit": "allow", "write": "allow", "read": "allow", "grep": "allow", "glob": "allow", "webfetch": "allow"} }, "orchestrator": { "mode": "subagent", "model": "zai/glm-5.2" if "zai" in providers else "deepseek/deepseek-v4-pro", "temperature": 0.2, "description": "Multi-agent orchestrator — decomposes tasks and dispatches to parallel workers", "system": "[CTX: orch] Orchestrator. Decompose tasks into parallel subtasks. Dispatch to specialized agents. Aggregate results. Track dependencies.", "permission": { "task": { "build": "allow", "plan": "allow", "general": "allow", "explore": "allow", "code-reviewer": "allow", "researcher": "allow", "scout": "allow", "reviewer": "allow", "security-auditor": "allow", "test-engineer": "allow", "critic": "allow", "sme": "allow", "docs": "allow" }, "edit": "allow", "read": "allow", "grep": "allow", "glob": "allow", "webfetch": "allow" } }, }, "tui": { "show_token_count": True, "show_cost": True, "show_tool_usage": True } } os.makedirs(os.path.dirname(config_path), exist_ok=True) with open(config_path, 'w') as f: f.write("// ============================================================================\n") f.write("// opencode.json — generated by opencode_initializer setup\n") f.write("//\n") f.write("// API key fields use ${ENV_VAR} substitution syntax.\n") f.write("// Set the corresponding environment variable to activate a provider.\n") f.write("// This file is safe to share — no secrets are stored in plain text.\n") f.write("// ============================================================================\n") json.dump(config, f, indent=2) print(f"VALID:{len(mcps)}") PYGEN } RESULT=$(generate_opencode_json) if echo "$RESULT" | grep -q "VALID:"; then MCP_COUNT=$(echo "$RESULT" | grep "VALID:" | cut -d: -f2) log "opencode.json valid — ${MCP_COUNT} MCP server(s) registered" _step_done step_json else warn "opencode.json generation produced unexpected output" fi # Cold-start verification of each registered MCP if [ "${MCP_COUNT:-0}" -gt 0 ] 2>/dev/null; then section "MCP cold-start test" set +e MCP_PASS=0 MCP_FAIL=0 for mcp_name in $(python3 -c " import json c=json.load(open('$HOME/.config/opencode/opencode.json')) print(' '.join(c.get('mcp',{}).keys())) " 2>/dev/null); do MCP_NAME="$mcp_name" python3 -c " import json, os, sys name = os.environ.get('MCP_NAME','') c = json.load(open(os.path.expanduser('~/.config/opencode/opencode.json'))) m = c.get('mcp',{}).get(name,{}) sys.stdout.write(json.dumps(m.get('command',[]))) " 2>/dev/null >/tmp/opencode-mcp-cmd.$$.json mcp_enabled=$(MCP_NAME="$mcp_name" python3 -c " import json, os name = os.environ.get('MCP_NAME','') c = json.load(open(os.path.expanduser('~/.config/opencode/opencode.json'))) m = c.get('mcp',{}).get(name,{}) print(m.get('enabled', True)) " 2>/dev/null) mcp_type=$(MCP_NAME="$mcp_name" python3 -c " import json, os name = os.environ.get('MCP_NAME','') c = json.load(open(os.path.expanduser('~/.config/opencode/opencode.json'))) m = c.get('mcp',{}).get(name,{}) print(m.get('type', 'local')) " 2>/dev/null) if [ "$mcp_enabled" = "False" ] || [ "$mcp_type" = "remote" ]; then MCP_PASS=$((MCP_PASS + 1)) continue fi binary=$(python3 -c "import json,sys; arr=json.load(sys.stdin); print(arr[0] if arr else '')" 2>/dev/null </tmp/opencode-mcp-cmd.$$.json) rm -f /tmp/opencode-mcp-cmd.$$.json if echo "$binary" | grep -qE "^(npx|bunx)$"; then if which npx &>/dev/null || which bunx &>/dev/null; then MCP_PASS=$((MCP_PASS + 1)) else warn "MCP $mcp_name: npx/bunx not found" MCP_FAIL=$((MCP_FAIL + 1)) fi elif [ -n "$binary" ] && which "$binary" &>/dev/null; then MCP_PASS=$((MCP_PASS + 1)) else warn "MCP $mcp_name: $binary not found" MCP_FAIL=$((MCP_FAIL + 1)) fi done log "MCP cold-start: $MCP_PASS ready, $MCP_FAIL missing" set -e fi fi