Skip to content

Commit 9f47eb4

Browse files
committed
Add cross-platform plugin support
Add neutral .plugin/plugin.json as source of truth with generated platform-specific files for Claude Code and Cursor.
1 parent 53c553f commit 9f47eb4

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

.cursor-plugin/plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "nerds",
3+
"displayName": "Nerds",
4+
"description": "Plugin voor de Nederlandse Richtlijn Digitale Systemen (NeRDS). Bevat skills voor 13 richtlijnen die richting geven aan het ontwerpen, ontwikkelen en inkopen van digitale systemen binnen de Nederlandse overheid: gebruikersbehoeften, toegankelijkheid, open source, open standaarden, cloud, veiligheid, privacy, samenwerking, integratie, data, algoritmen, inkoop en duurzaamheid.",
5+
"version": "0.1.1"
6+
}

.plugin/plugin.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "nerds",
3+
"description": "Plugin voor de Nederlandse Richtlijn Digitale Systemen (NeRDS). Bevat skills voor 13 richtlijnen die richting geven aan het ontwerpen, ontwikkelen en inkopen van digitale systemen binnen de Nederlandse overheid: gebruikersbehoeften, toegankelijkheid, open source, open standaarden, cloud, veiligheid, privacy, samenwerking, integratie, data, algoritmen, inkoop en duurzaamheid.",
4+
"version": "0.1.1"
5+
}

scripts/generate_plugin.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
"""Generate platform-specific plugin.json files from the neutral .plugin/plugin.json.
3+
4+
The .plugin/plugin.json is the single source of truth. This script generates
5+
platform-specific variants for Claude Code and Cursor (and future platforms).
6+
7+
Usage:
8+
python scripts/generate_plugin.py # generate all platform files
9+
python scripts/generate_plugin.py --check # verify files are in sync
10+
"""
11+
12+
import copy
13+
import json
14+
import sys
15+
from pathlib import Path
16+
17+
ROOT_DIR = Path(__file__).resolve().parent.parent
18+
SOURCE_PATH = ROOT_DIR / ".plugin" / "plugin.json"
19+
CLAUDE_PATH = ROOT_DIR / ".claude-plugin" / "plugin.json"
20+
CURSOR_PATH = ROOT_DIR / ".cursor-plugin" / "plugin.json"
21+
22+
23+
def load_source() -> dict:
24+
"""Load the neutral .plugin/plugin.json."""
25+
with open(SOURCE_PATH) as f:
26+
return json.load(f)
27+
28+
29+
def generate_claude(data: dict) -> dict:
30+
"""Generate Claude Code plugin.json — identical copy of source."""
31+
return copy.deepcopy(data)
32+
33+
34+
def _display_name(name: str) -> str:
35+
"""Convert a kebab-case plugin name to a human-readable display name.
36+
37+
Examples:
38+
"standaarden" -> "Standaarden"
39+
"zad-actions" -> "Zad Actions"
40+
"""
41+
return name.replace("-", " ").title()
42+
43+
44+
def generate_cursor(data: dict) -> dict:
45+
"""Generate Cursor plugin.json — adds displayName."""
46+
result = copy.deepcopy(data)
47+
# Insert displayName after name
48+
ordered = {"name": result.pop("name")}
49+
ordered["displayName"] = _display_name(ordered["name"])
50+
ordered.update(result)
51+
return ordered
52+
53+
54+
PLATFORMS: dict[str, tuple[Path, callable]] = {
55+
"claude": (CLAUDE_PATH, generate_claude),
56+
"cursor": (CURSOR_PATH, generate_cursor),
57+
}
58+
59+
60+
def write_json(path: Path, data: dict) -> None:
61+
"""Write JSON data to a file with consistent formatting."""
62+
path.parent.mkdir(parents=True, exist_ok=True)
63+
with open(path, "w") as f:
64+
json.dump(data, f, indent=2, ensure_ascii=False)
65+
f.write("\n")
66+
67+
68+
def generate_all(source_data: dict) -> None:
69+
"""Generate all platform plugin files."""
70+
for name, (path, generator) in PLATFORMS.items():
71+
generated = generator(source_data)
72+
write_json(path, generated)
73+
print(f"Gegenereerd: {path.relative_to(ROOT_DIR)}")
74+
75+
76+
def check_sync(source_data: dict) -> bool:
77+
"""Check if platform files are in sync with the source."""
78+
all_synced = True
79+
for name, (path, generator) in PLATFORMS.items():
80+
expected = generator(source_data)
81+
if not path.exists():
82+
print(f"FOUT: {path.relative_to(ROOT_DIR)} bestaat niet")
83+
all_synced = False
84+
continue
85+
with open(path) as f:
86+
actual = json.load(f)
87+
if actual != expected:
88+
print(f"FOUT: {path.relative_to(ROOT_DIR)} is niet in sync")
89+
all_synced = False
90+
else:
91+
print(f"OK: {path.relative_to(ROOT_DIR)}")
92+
return all_synced
93+
94+
95+
def main() -> None:
96+
if not SOURCE_PATH.exists():
97+
print(f"FOUT: {SOURCE_PATH} niet gevonden")
98+
sys.exit(1)
99+
100+
source_data = load_source()
101+
102+
if "--check" in sys.argv:
103+
if check_sync(source_data):
104+
print("\nAlle platform-bestanden zijn in sync")
105+
sys.exit(0)
106+
else:
107+
print("\nNiet in sync. Draai: python scripts/generate_plugin.py")
108+
sys.exit(1)
109+
else:
110+
generate_all(source_data)
111+
print("\nAlle platform-bestanden gegenereerd")
112+
113+
114+
if __name__ == "__main__":
115+
main()

0 commit comments

Comments
 (0)