File size: 4,555 Bytes
c1bf102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python3
"""Install the Kaiju Coder 7 OpenCode provider and lean agent locally."""

from __future__ import annotations

import argparse
import json
import shutil
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",
]
PLUGIN_DEST_NAME = "kaiju-no-autocontinue.mjs"


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 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 merge_provider(
    existing: dict[str, Any],
    template: dict[str, Any],
    base_url: str | None,
    plugin_path: Path,
) -> 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
    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("--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
    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")
    existing = load_json(config_path)
    template = load_json(config_source)
    merged = merge_provider(existing, template, args.base_url, plugin_dest)

    print(f"Config: {config_path}")
    print(f"Agent:  {agent_dest}")
    print(f"Plugin: {plugin_dest}")
    if args.dry_run:
        print(
            json.dumps(
                {
                    "plugin": merged.get("plugin", []),
                    "kaiju": merged.get("provider", {}).get("kaiju", {}),
                },
                indent=2,
            )
        )
        return 0

    write_json(config_path, merged)
    agent_dest.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(agent_source, agent_dest)
    shutil.copy2(plugin_source, plugin_dest)
    print("Installed Kaiju Coder 7 OpenCode profile.")
    print("Run: opencode -m kaiju/kaiju-coder-7 --agent kaiju-coder-7")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())