| |
| """ |
| 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()) |
|
|