From 373b8601c8586b2fa123ac48d2d959aaa715ea4e Mon Sep 17 00:00:00 2001 From: wxm <18854896936@163.com> Date: Mon, 18 May 2026 23:21:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BD=BB=E9=87=8F=20runtime?= =?UTF-8?q?=20=E5=AE=89=E8=A3=85=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 13 +++ scripts/install_codex_runtime.py | 149 +++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100755 scripts/install_codex_runtime.py diff --git a/.gitignore b/.gitignore index 3323b34..b8b2a20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .env .DS_Store +__pycache__/ +*.pyc diff --git a/README.md b/README.md index bad9e6e..8442a5c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Current state: - References: `director-gates.md`, `templates/game-concept.md` - Standards: `design-docs.md` - Commands: `/brainstorm` wrapper document +- Scripts: `scripts/install_codex_runtime.py` installs bundled custom agents into a target project - Marketplace: `.agents/plugins/marketplace.json` - No hooks - No rules @@ -25,6 +26,18 @@ The director agents are pre-defined Codex custom agents stored in: Each custom agent is a standalone TOML file with `name`, `description`, and `developer_instructions`, matching the Codex subagents documentation. +To install the bundled custom agents into a game project: + +```bash +python3 scripts/install_codex_runtime.py /path/to/game-project +``` + +The current installer only writes custom agents to: + +```text +/path/to/game-project/.codex/agents/ +``` + The only required plugin manifest is: ```text diff --git a/scripts/install_codex_runtime.py b/scripts/install_codex_runtime.py new file mode 100755 index 0000000..b2faa5b --- /dev/null +++ b/scripts/install_codex_runtime.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Install Codex Game Studios runtime files into a game project. + +Current runtime scope: +- Copy bundled Codex custom agents from this plugin's `.codex/agents/` + directory into the target project's `.codex/agents/` directory. + +This installer intentionally does not install hooks, rules, MCP servers, apps, +or AGENTS.md templates yet. +""" + +from __future__ import annotations + +import argparse +import filecmp +import shutil +import sys +from dataclasses import dataclass +from pathlib import Path + + +PLUGIN_ROOT = Path(__file__).resolve().parents[1] +SOURCE_AGENTS_DIR = PLUGIN_ROOT / ".codex" / "agents" + + +@dataclass(frozen=True) +class InstallResult: + installed: list[Path] + unchanged: list[Path] + conflicts: list[Path] + + +def relative_to_target(path: Path, target: Path) -> str: + try: + return str(path.relative_to(target)) + except ValueError: + return str(path) + + +def discover_agent_files() -> list[Path]: + if not SOURCE_AGENTS_DIR.exists(): + raise FileNotFoundError(f"Source agent directory not found: {SOURCE_AGENTS_DIR}") + + agent_files = sorted(SOURCE_AGENTS_DIR.glob("*.toml")) + if not agent_files: + raise FileNotFoundError(f"No custom agent TOML files found in: {SOURCE_AGENTS_DIR}") + + return agent_files + + +def install_agents(target: Path, *, force: bool, dry_run: bool) -> InstallResult: + agent_files = discover_agent_files() + target_agents_dir = target / ".codex" / "agents" + + planned: list[tuple[Path, Path]] = [] + unchanged: list[Path] = [] + conflicts: list[Path] = [] + + for source in agent_files: + destination = target_agents_dir / source.name + + if source.resolve() == destination.resolve(): + unchanged.append(destination) + continue + + if destination.exists(): + if filecmp.cmp(source, destination, shallow=False): + unchanged.append(destination) + continue + + if not force: + conflicts.append(destination) + continue + + planned.append((source, destination)) + + if conflicts and not force: + return InstallResult(installed=[], unchanged=unchanged, conflicts=conflicts) + + installed = [destination for _, destination in planned] + if not dry_run: + target_agents_dir.mkdir(parents=True, exist_ok=True) + for source, destination in planned: + shutil.copy2(source, destination) + + return InstallResult(installed=installed, unchanged=unchanged, conflicts=conflicts) + + +def print_path_list(label: str, paths: list[Path], target: Path) -> None: + if not paths: + return + print(f"{label}:") + for path in paths: + print(f"- {relative_to_target(path, target)}") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "target", + nargs="?", + default=".", + help="Target game project root. Defaults to the current directory.", + ) + parser.add_argument( + "--force", + action="store_true", + help="Overwrite existing agent files when their content differs.", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be installed without writing files.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + target = Path(args.target).expanduser().resolve() + + print("Codex Game Studios runtime installer") + print(f"Plugin root: {PLUGIN_ROOT}") + print(f"Target project: {target}") + print("Runtime scope: custom agents only") + + try: + result = install_agents(target, force=args.force, dry_run=args.dry_run) + except FileNotFoundError as error: + print(f"ERROR: {error}", file=sys.stderr) + return 1 + + print_path_list("Installed" if not args.dry_run else "Would install", result.installed, target) + print_path_list("Unchanged", result.unchanged, target) + print_path_list("Conflicts", result.conflicts, target) + + if result.conflicts: + print( + "ERROR: Existing agent files differ. Re-run with --force to overwrite them.", + file=sys.stderr, + ) + return 2 + + print("Done.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())