File size: 3,421 Bytes
4f4b027
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""
Generate a JSON spec for normies.

Usage example:
  ./scripts/make_spec.py \
    --output /tmp/normies-spec.json \
    --repo owner/repo \
    --agent "lint::npm ci && npm run lint" \
    --agent "test::npm test" \
    --check "git diff --check"
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path


def parse_agent(raw: str) -> dict[str, str]:
    if "::" not in raw:
        raise ValueError("agent must use format 'name::command'")
    name, cmd = raw.split("::", 1)
    name = name.strip()
    cmd = cmd.strip()
    if not name:
        raise ValueError("agent name cannot be empty")
    if not cmd:
        raise ValueError("agent command cannot be empty")
    return {"name": name, "cmd": cmd}


def main() -> int:
    parser = argparse.ArgumentParser(description="Generate a JSON normies spec.")
    parser.add_argument("--output", required=True, help="Output JSON file path.")
    parser.add_argument("--repo", help="Optional repo (local path, URL, or owner/repo).")
    parser.add_argument("--base-ref", default="main", help="Base ref. Default: main")
    parser.add_argument("--image", default="ubuntu:24.04", help="Default container image.")
    parser.add_argument(
        "--agent",
        action="append",
        default=[],
        help="Agent in format 'name::command'. Repeat for multiple agents.",
    )
    parser.add_argument(
        "--network-agent",
        action="append",
        default=[],
        help="Agent name that needs network access. Repeatable.",
    )
    parser.add_argument(
        "--check",
        action="append",
        default=[],
        help="Review required check command. Repeatable.",
    )
    parser.add_argument("--cpus", default="2", help="Default CPU limit.")
    parser.add_argument("--memory", default="4g", help="Default memory limit.")
    parser.add_argument("--pids-limit", type=int, default=256, help="Default pids limit.")
    parser.add_argument(
        "--auto-commit",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable orchestrator auto-commit.",
    )
    args = parser.parse_args()

    if not args.agent:
        parser.error("at least one --agent is required")

    try:
        agents = [parse_agent(item) for item in args.agent]
    except ValueError as exc:
        parser.error(str(exc))

    network_set = set(args.network_agent or [])
    for agent in agents:
        if agent["name"] in network_set:
            agent["needs_network"] = True

    spec: dict[str, object] = {
        "base_ref": args.base_ref,
        "image": args.image,
        "defaults": {
            "cpus": str(args.cpus),
            "memory": str(args.memory),
            "pids_limit": int(args.pids_limit),
            "needs_network": False,
            "auto_commit": bool(args.auto_commit),
            "read_only_rootfs": False,
            "commit_prefix": "agent",
        },
        "review": {
            "required_checks": list(args.check or []),
        },
        "agents": agents,
    }
    if args.repo:
        spec["repo"] = args.repo

    output_path = Path(args.output).resolve()
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(spec, indent=2) + "\n", encoding="utf-8")
    print(output_path)
    return 0


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