添加轻量 runtime 安装脚本
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -10,6 +10,7 @@ Current state:
|
|||||||
- References: `director-gates.md`, `templates/game-concept.md`
|
- References: `director-gates.md`, `templates/game-concept.md`
|
||||||
- Standards: `design-docs.md`
|
- Standards: `design-docs.md`
|
||||||
- Commands: `/brainstorm` wrapper document
|
- Commands: `/brainstorm` wrapper document
|
||||||
|
- Scripts: `scripts/install_codex_runtime.py` installs bundled custom agents into a target project
|
||||||
- Marketplace: `.agents/plugins/marketplace.json`
|
- Marketplace: `.agents/plugins/marketplace.json`
|
||||||
- No hooks
|
- No hooks
|
||||||
- No rules
|
- 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
|
Each custom agent is a standalone TOML file with `name`, `description`, and
|
||||||
`developer_instructions`, matching the Codex subagents documentation.
|
`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:
|
The only required plugin manifest is:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
149
scripts/install_codex_runtime.py
Executable file
149
scripts/install_codex_runtime.py
Executable file
@@ -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())
|
||||||
Reference in New Issue
Block a user