#!/usr/bin/env python3 """Install the Kaiju Coder 7 OpenCode provider and lean agent locally.""" from __future__ import annotations import argparse import json import shlex import shutil import stat from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] AGENT_SOURCE_CANDIDATES = [ ROOT / ".opencode/agents/kaiju-coder-7.md", ROOT / "agents/kaiju-coder-7.md", ] CONFIG_SOURCE_CANDIDATES = [ ROOT / "release/opencode/opencode.kaiju-coder-7.jsonc", ROOT / "opencode.kaiju-coder-7.jsonc", ] PLUGIN_SOURCE_CANDIDATES = [ ROOT / "scripts/opencode-kaiju-no-autocontinue.mjs", ROOT / "opencode-kaiju-no-autocontinue.mjs", ] COMMAND_SOURCE_CANDIDATES = [ ROOT / ".opencode/commands/kaiju.md", ROOT / "commands/kaiju.md", ] PLUGIN_DEST_NAME = "kaiju-no-autocontinue.mjs" RUNTIME_DEST_NAME = "kaiju-coder-7-runtime" RUNNER_NAME = "kaiju-coder-7-run" DEFAULT_MODEL = "kaiju/kaiju-coder-7" DEFAULT_AGENT = "kaiju-coder-7" RUNTIME_REQUIRED = [ ROOT / "kaiju_harness", ROOT / "prompts", ROOT / "scripts/run_kaiju_router.py", ] def strip_jsonc(text: str) -> str: # The template intentionally stays plain JSON-compatible today. This helper # keeps the installer tolerant if comments are added later. lines = [] for line in text.splitlines(): if line.lstrip().startswith("//"): continue lines.append(line) return "\n".join(lines) def load_json(path: Path) -> dict[str, Any]: if not path.exists(): return {} return json.loads(strip_jsonc(path.read_text(encoding="utf-8"))) def write_json(path: Path, data: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") def write_plugin_package_json(config_dir: Path) -> Path: path = config_dir / "package.json" data = load_json(path) dependencies = dict(data.get("dependencies") or {}) dependencies.setdefault("@opencode-ai/plugin", "1.14.33") data["dependencies"] = dependencies write_json(path, data) return path def first_existing(candidates: list[Path], label: str) -> Path: for candidate in candidates: if candidate.is_file(): return candidate joined = ", ".join(str(candidate) for candidate in candidates) raise FileNotFoundError(f"Missing {label}. Looked in: {joined}") def plugin_list(value: Any) -> list[str]: if isinstance(value, str): return [value] if isinstance(value, list): return [item for item in value if isinstance(item, str)] return [] def runtime_available() -> bool: return all(path.exists() for path in RUNTIME_REQUIRED) def ignore_runtime_junk(_directory: str, names: list[str]) -> set[str]: return { name for name in names if name == "__pycache__" or name.endswith(".pyc") or name == ".DS_Store" } def copy_runtime(runtime_dest: Path) -> None: if not runtime_available(): missing = ", ".join(str(path) for path in RUNTIME_REQUIRED if not path.exists()) raise FileNotFoundError(f"Missing Kaiju router runtime file(s): {missing}") shutil.rmtree(runtime_dest, ignore_errors=True) (runtime_dest / "scripts").mkdir(parents=True, exist_ok=True) shutil.copytree(ROOT / "kaiju_harness", runtime_dest / "kaiju_harness", ignore=ignore_runtime_junk) shutil.copytree(ROOT / "prompts", runtime_dest / "prompts", ignore=ignore_runtime_junk) shutil.copy2(ROOT / "scripts/run_kaiju_router.py", runtime_dest / "scripts/run_kaiju_router.py") def write_runner(runner_dest: Path, runtime_dest: Path) -> None: runner_dest.parent.mkdir(parents=True, exist_ok=True) runtime_arg = shlex.quote(str(runtime_dest)) script_arg = shlex.quote(str(runtime_dest / "scripts/run_kaiju_router.py")) content = f"""#!/usr/bin/env bash set -euo pipefail RUNTIME_DIR={runtime_arg} PYTHON_BIN="${{KAIJU_PYTHON:-python3}}" BASE_URL="${{KAIJU_OPENAI_BASE_URL:-http://127.0.0.1:18181/v1}}" MODEL="${{KAIJU_MODEL:-kaiju-coder-7}}" PLANNER_TIMEOUT="${{KAIJU_PLANNER_TIMEOUT:-45}}" if [ ! -f {script_arg} ]; then echo "Kaiju Coder 7 runtime is missing: $RUNTIME_DIR" >&2 exit 2 fi exec "$PYTHON_BIN" {script_arg} \\ --openai-base-url "$BASE_URL" \\ --model "$MODEL" \\ --planner-timeout "$PLANNER_TIMEOUT" \\ "$@" """ runner_dest.write_text(content, encoding="utf-8") runner_dest.chmod(runner_dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) def merge_provider( existing: dict[str, Any], template: dict[str, Any], base_url: str | None, plugin_path: Path, *, set_defaults: bool, ) -> dict[str, Any]: merged = dict(existing) provider = dict(merged.get("provider") or {}) kaiju = dict((template.get("provider") or {})["kaiju"]) if base_url: options = dict(kaiju.get("options") or {}) options["baseURL"] = base_url kaiju["options"] = options provider["kaiju"] = kaiju merged["$schema"] = merged.get("$schema") or template.get("$schema", "https://opencode.ai/config.json") merged["provider"] = provider plugins = plugin_list(merged.get("plugin")) plugin_path_str = str(plugin_path) if plugin_path_str not in plugins: plugins.append(plugin_path_str) merged["plugin"] = plugins if set_defaults: merged["model"] = DEFAULT_MODEL merged["default_agent"] = DEFAULT_AGENT return merged def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--config-dir", type=Path, default=Path.home() / ".config/opencode", help="OpenCode config directory to update.", ) parser.add_argument("--base-url", default=None, help="Override Kaiju OpenAI-compatible base URL.") parser.add_argument( "--bin-dir", type=Path, default=Path.home() / ".local/bin", help="Directory where the kaiju-coder-7-run command is installed.", ) parser.add_argument("--skip-runner", action="store_true", help="Install only the OpenCode provider, agent, and plugin.") parser.add_argument( "--no-defaults", action="store_true", help="Do not set Kaiju Coder 7 as the default OpenCode model and default agent.", ) parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() config_path = args.config_dir / "opencode.jsonc" agent_dest = args.config_dir / "agents/kaiju-coder-7.md" plugin_dest = args.config_dir / PLUGIN_DEST_NAME command_dest = args.config_dir / "commands/kaiju.md" package_dest = args.config_dir / "package.json" runtime_dest = args.config_dir / RUNTIME_DEST_NAME runner_dest = args.bin_dir / RUNNER_NAME agent_source = first_existing(AGENT_SOURCE_CANDIDATES, "Kaiju Coder 7 OpenCode agent") config_source = first_existing(CONFIG_SOURCE_CANDIDATES, "Kaiju Coder 7 OpenCode config") plugin_source = first_existing(PLUGIN_SOURCE_CANDIDATES, "Kaiju Coder 7 OpenCode loop guard") command_source = first_existing(COMMAND_SOURCE_CANDIDATES, "Kaiju Coder 7 OpenCode command") if not args.skip_runner and not runtime_available(): missing = ", ".join(str(path) for path in RUNTIME_REQUIRED if not path.exists()) raise FileNotFoundError(f"Missing Kaiju router runtime file(s): {missing}") existing = load_json(config_path) template = load_json(config_source) merged = merge_provider(existing, template, args.base_url, plugin_dest, set_defaults=not args.no_defaults) print(f"Config: {config_path}") print(f"Agent: {agent_dest}") print(f"Plugin: {plugin_dest}") print(f"Command: {command_dest}") print(f"Package: {package_dest}") if not args.skip_runner: print(f"Runtime: {runtime_dest}") print(f"Runner: {runner_dest}") if args.dry_run: print( json.dumps( { "plugin": merged.get("plugin", []), "model": merged.get("model"), "default_agent": merged.get("default_agent"), "kaiju": merged.get("provider", {}).get("kaiju", {}), "command": str(command_dest), "package": str(package_dest), "package_dependency": "@opencode-ai/plugin", "runtime": None if args.skip_runner else str(runtime_dest), "runner": None if args.skip_runner else str(runner_dest), }, indent=2, ) ) return 0 write_json(config_path, merged) agent_dest.parent.mkdir(parents=True, exist_ok=True) command_dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(agent_source, agent_dest) shutil.copy2(plugin_source, plugin_dest) shutil.copy2(command_source, command_dest) write_plugin_package_json(args.config_dir) if not args.skip_runner: copy_runtime(runtime_dest) write_runner(runner_dest, runtime_dest) print("Installed Kaiju Coder 7 OpenCode profile.") if not args.skip_runner: print(f"Runner command: {runner_dest}") if args.no_defaults: print("Run: opencode -m kaiju/kaiju-coder-7 --agent kaiju-coder-7") else: print("Run: opencode") return 0 if __name__ == "__main__": raise SystemExit(main())