Setting the file. One moment. Generate OpenAI YAML · Skill Creator · openai/skills · Skills DocsReference
scripts/generate_openai_yaml.py
Python·225 lines·6 KB
16ACRONYMS = {
17 "GH",
18 "MCP",
19 "API",
20 "CI",
21 "CLI",
22 "LLM",
23 "PDF",
24 "PR",
25 "UI",
26 "URL",
27 "SQL",
28}
29
30BRANDS = {
31 "openai": "OpenAI",
32 "openapi": "OpenAPI",
33 "github": "GitHub",
34 "pagerduty": "PagerDuty",
35 "datadog": "DataDog",
36 "sqlite": "SQLite",
37 "fastapi": "FastAPI",
38}
39
40SMALL_WORDS = {"and", "or", "to", "up", "with"}
41
42ALLOWED_INTERFACE_KEYS = {
43 "display_name",
44 "short_description",
45 "icon_small",
46 "icon_large",
47 "brand_color",
48 "default_prompt",
49}
50
51
52def yaml_quote(value):
53 escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
54 return f'"{escaped}"'
55
56
57def format_display_name(skill_name):
58 words = [word for word in skill_name.split("-") if word]
59 formatted = []
60 for index, word in enumerate(words):
61 lower = word.lower()
62 upper = word.upper()
63 if upper in ACRONYMS:
64 formatted.append(upper)
65 continue
66 if lower in BRANDS:
67 formatted.append(BRANDS[lower])
68 continue
69 if index > 0 and lower in SMALL_WORDS:
70 formatted.append(lower)
71 continue
72 formatted.append(word.capitalize())
73 return " ".join(formatted)
74
75
76def generate_short_description(display_name):
77 description = f"Help with {display_name} tasks"
78
79 if len(description) < 25:
80 description = f"Help with {display_name} tasks and workflows"
81 if len(description) < 25:
82 description = f"Help with {display_name} tasks with guidance"
83
84 if len(description) > 64:
85 description = f"Help with {display_name}"
86 if len(description) > 64:
87 description = f"{display_name} helper"
88 if len(description) > 64:
89 description = f"{display_name} tools"
90 if len(description) > 64:
91 suffix = " helper"
92 max_name_length = 64 - len(suffix)
93 trimmed = display_name[:max_name_length].rstrip()
94 description = f"{trimmed}{suffix}"
95 if len(description) > 64:
96 description = description[:64].rstrip()
97
98 if len(description) < 25:
99 description = f"{description} workflows"
100 if len(description) > 64:
101 description = description[:64].rstrip()
102
103 return description
104
105
106def read_frontmatter_name(skill_dir):
107 skill_md = Path(skill_dir) / "SKILL.md"
108 if not skill_md.exists():
109 print(f"[ERROR] SKILL.md not found in {skill_dir}")
110 return None
111 content = skill_md.read_text()
112 match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
113 if not match:
114 print("[ERROR] Invalid SKILL.md frontmatter format.")
115 return None
116 frontmatter_text = match.group(1)
117 try:
118 frontmatter = yaml.safe_load(frontmatter_text)
119 except yaml.YAMLError as exc:
120 print(f"[ERROR] Invalid YAML frontmatter: {exc}")
121 return None
122 if not isinstance(frontmatter, dict):
123 print("[ERROR] Frontmatter must be a YAML dictionary.")
124 return None
125 name = frontmatter.get("name", "")
126 if not isinstance(name, str) or not name.strip():
127 print("[ERROR] Frontmatter 'name' is missing or invalid.")
128 return None
129 return name.strip()
130
131
132def parse_interface_overrides(raw_overrides):
133 overrides = {}
134 optional_order = []
135 for item in raw_overrides:
136 if "=" not in item:
137 print(f"[ERROR] Invalid interface override '{item}'. Use key=value.")
138 return None, None
139 key, value = item.split("=", 1)
140 key = key.strip()
141 value = value.strip()
142 if not key:
143 print(f"[ERROR] Invalid interface override '{item}'. Key is empty.")
144 return None, None
145 if key not in ALLOWED_INTERFACE_KEYS:
146 allowed = ", ".join(sorted(ALLOWED_INTERFACE_KEYS))
147 print(f"[ERROR] Unknown interface field '{key}'. Allowed: {allowed}")
148 return None, None
149 overrides[key] = value
150 if key not in ("display_name", "short_description") and key not in optional_order:
151 optional_order.append(key)
152 return overrides, optional_order
153
154
155def write_openai_yaml(skill_dir, skill_name, raw_overrides):
156 overrides, optional_order = parse_interface_overrides(raw_overrides)
157 if overrides is None:
158 return None
159
160 display_name = overrides.get("display_name") or format_display_name(skill_name)
161 short_description = overrides.get("short_description") or generate_short_description(display_name)
162
163 if not (25 <= len(short_description) <= 64):
164 print(
165 "[ERROR] short_description must be 25-64 characters "
166 f"(got {len(short_description)})."
167 )
168 return None
169
170 interface_lines = [
171 "interface:",
172 f" display_name: {yaml_quote(display_name)}",
173 f" short_description: {yaml_quote(short_description)}",
174 ]
175
176 for key in optional_order:
177 value = overrides.get(key)
178 if value is not None:
179 interface_lines.append(f" {key}: {yaml_quote(value)}")
180
181 agents_dir = Path(skill_dir) / "agents"
182 agents_dir.mkdir(parents=True, exist_ok=True)
183 output_path = agents_dir / "openai.yaml"
184 output_path.write_text("\n".join(interface_lines) + "\n")
185 print(f"[OK] Created agents/openai.yaml")
186 return output_path
187
188
189def main():
190 parser = argparse.ArgumentParser(
191 description="Create agents/openai.yaml for a skill directory.",
192 )
193 parser.add_argument("skill_dir", help="Path to the skill directory")
194 parser.add_argument(
195 "--name",
196 help="Skill name override (defaults to SKILL.md frontmatter)",
197 )
198 parser.add_argument(
199 "--interface",
200 action="append",
201 default=[],
202 help="Interface override in key=value format (repeatable)",
203 )
204 args = parser.parse_args()
205
206 skill_dir = Path(args.skill_dir).resolve()
207 if not skill_dir.exists():
208 print(f"[ERROR] Skill directory not found: {skill_dir}")
209 sys.exit(1)
210 if not skill_dir.is_dir():
211 print(f"[ERROR] Path is not a directory: {skill_dir}")
212 sys.exit(1)
213
214 skill_name = args.name or read_frontmatter_name(skill_dir)
215 if not skill_name:
216 sys.exit(1)
217
218 result = write_openai_yaml(skill_dir, skill_name, args.interface)
219 if result:
220 sys.exit(0)
221 sys.exit(1)
222
223
224if __name__ == "__main__":
225 main()