-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsync_manifests.py
More file actions
executable file
·131 lines (103 loc) · 4.12 KB
/
sync_manifests.py
File metadata and controls
executable file
·131 lines (103 loc) · 4.12 KB
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
#!/usr/bin/env python3
"""Sync marketplace and plugin manifests across tool-specific paths.
Sources of truth (edit these):
- .plugin/marketplace.json Open Plugins catalog manifest.
- plugins/<name>/.plugin/plugin.json Open Plugins per-plugin manifest.
Generated (do not edit by hand — re-run this script):
- .claude-plugin/marketplace.json Byte-identical copy for Claude Code.
- .agents/plugins/marketplace.json Codex catalog (different schema).
- plugins/<name>/.codex-plugin/plugin.json Byte-identical copy for Codex.
Usage:
python scripts/sync_manifests.py # apply (writes generated files)
python scripts/sync_manifests.py --check # exit 1 on drift; do not modify files
"""
import argparse
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SOURCE_MARKETPLACE = REPO_ROOT / ".plugin" / "marketplace.json"
CLAUDE_MARKETPLACE = REPO_ROOT / ".claude-plugin" / "marketplace.json"
CODEX_MARKETPLACE = REPO_ROOT / ".agents" / "plugins" / "marketplace.json"
# Codex catalog defaults. When adding a plugin, register its category here so
# the Codex listing has the right label; otherwise CODEX_DEFAULT_CATEGORY wins.
CODEX_MARKETPLACE_DISPLAY_NAME = "Robot Framework Agent Plugins"
CODEX_PLUGIN_CATEGORIES = {
"robotcode": "Developer Tools",
}
CODEX_DEFAULT_CATEGORY = "Developer Tools"
CODEX_DEFAULT_POLICY = {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL",
}
def die(msg: str) -> None:
print(f"sync-manifests: {msg}", file=sys.stderr)
sys.exit(1)
def plugin_dir(source_path: str) -> Path:
return (REPO_ROOT / source_path.lstrip("./")).resolve()
def codex_marketplace_from(src: dict) -> dict:
return {
"name": src["name"],
"interface": {"displayName": CODEX_MARKETPLACE_DISPLAY_NAME},
"plugins": [
{
"name": p["name"],
"source": {
"source": "local",
"path": p["source"].rstrip("/"),
},
"policy": CODEX_DEFAULT_POLICY,
"category": CODEX_PLUGIN_CATEGORIES.get(p["name"], CODEX_DEFAULT_CATEGORY),
}
for p in src["plugins"]
],
}
def dump_json(obj: dict) -> str:
return json.dumps(obj, indent="\t") + "\n"
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument(
"--check",
action="store_true",
help="Exit 1 on drift; do not modify files.",
)
args = ap.parse_args()
if not SOURCE_MARKETPLACE.is_file():
die(f"missing source: {SOURCE_MARKETPLACE.relative_to(REPO_ROOT)}")
source_text = SOURCE_MARKETPLACE.read_text()
source_data = json.loads(source_text)
targets: dict[Path, str] = {
CLAUDE_MARKETPLACE: source_text,
CODEX_MARKETPLACE: dump_json(codex_marketplace_from(source_data)),
}
for entry in source_data["plugins"]:
pdir = plugin_dir(entry["source"])
src_plugin = pdir / ".plugin" / "plugin.json"
codex_plugin = pdir / ".codex-plugin" / "plugin.json"
if not src_plugin.is_file():
die(f"missing source: {src_plugin.relative_to(REPO_ROOT)}")
targets[codex_plugin] = src_plugin.read_text()
drift: list[Path] = []
for path, content in targets.items():
existing = path.read_text() if path.is_file() else None
if existing != content:
drift.append(path)
if args.check:
if drift:
print("manifest drift detected:", file=sys.stderr)
for p in drift:
print(f" {p.relative_to(REPO_ROOT)}", file=sys.stderr)
print("run `python scripts/sync_manifests.py`", file=sys.stderr)
return 1
print("manifests in sync")
return 0
if not drift:
print("manifests already in sync")
return 0
for path in drift:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(targets[path])
print(f"wrote {path.relative_to(REPO_ROOT)}")
return 0
if __name__ == "__main__":
sys.exit(main())