Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 144 additions & 13 deletions skills/.system/skill-creator/scripts/generate_openai_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,77 @@ def yaml_quote(value):
return f'"{escaped}"'


def yaml_scalar(value):
if isinstance(value, str):
return yaml_quote(value)
if value is True:
return "true"
if value is False:
return "false"
if value is None:
return "null"
return str(value)


def render_yaml_node(node, indent=0):
lines = []
prefix = " " * indent

if isinstance(node, dict):
for key, value in node.items():
key_prefix = f"{prefix}{key}:"
if isinstance(value, dict):
if value:
lines.append(key_prefix)
lines.extend(render_yaml_node(value, indent + 2))
else:
lines.append(f"{key_prefix} {{}}")
elif isinstance(value, list):
if value:
lines.append(key_prefix)
lines.extend(render_yaml_node(value, indent + 2))
else:
lines.append(f"{key_prefix} []")
else:
lines.append(f"{key_prefix} {yaml_scalar(value)}")
return lines

if isinstance(node, list):
for item in node:
item_prefix = f"{prefix}-"
if isinstance(item, dict):
if not item:
lines.append(f"{item_prefix} {{}}")
continue
first_key = next(iter(item))
first_value = item[first_key]
if isinstance(first_value, (dict, list)):
lines.append(f"{item_prefix} {first_key}:")
lines.extend(render_yaml_node(first_value, indent + 4))
else:
lines.append(f"{item_prefix} {first_key}: {yaml_scalar(first_value)}")
remaining = dict(item)
del remaining[first_key]
if remaining:
lines.extend(render_yaml_node(remaining, indent + 2))
elif isinstance(item, list):
if item:
lines.append(item_prefix)
lines.extend(render_yaml_node(item, indent + 2))
else:
lines.append(f"{item_prefix} []")
else:
lines.append(f"{item_prefix} {yaml_scalar(item)}")
return lines

lines.append(f"{prefix}{yaml_scalar(node)}")
return lines


def dump_yaml(data):
return "\n".join(render_yaml_node(data)) + "\n"


def format_display_name(skill_name):
words = [word for word in skill_name.split("-") if word]
formatted = []
Expand Down Expand Up @@ -152,11 +223,71 @@ def parse_interface_overrides(raw_overrides):
return overrides, optional_order


def load_existing_openai_yaml(output_path):
if not output_path.exists():
return {}

content = output_path.read_text()
if not content.strip():
return {}

try:
data = yaml.safe_load(content)
except yaml.YAMLError as exc:
print(f"[ERROR] Existing agents/openai.yaml is invalid YAML: {exc}")
return None

if data is None:
return {}
if not isinstance(data, dict):
print("[ERROR] Existing agents/openai.yaml must be a YAML dictionary.")
return None

if "interface" in data and not isinstance(data["interface"], dict):
print("[ERROR] Existing agents/openai.yaml field 'interface' must be a YAML dictionary.")
return None

return data


def build_interface(existing_interface, display_name, short_description, overrides, optional_order):
updated_interface = dict(existing_interface)
updated_interface["display_name"] = display_name
updated_interface["short_description"] = short_description

for key, value in overrides.items():
if key not in ("display_name", "short_description"):
updated_interface[key] = value

ordered_interface = {
"display_name": updated_interface.pop("display_name"),
"short_description": updated_interface.pop("short_description"),
}

for key in optional_order:
if key in updated_interface:
ordered_interface[key] = updated_interface.pop(key)

for key, value in updated_interface.items():
ordered_interface[key] = value

return ordered_interface


def write_openai_yaml(skill_dir, skill_name, raw_overrides):
overrides, optional_order = parse_interface_overrides(raw_overrides)
if overrides is None:
return None

agents_dir = Path(skill_dir) / "agents"
agents_dir.mkdir(parents=True, exist_ok=True)
output_path = agents_dir / "openai.yaml"

existing_data = load_existing_openai_yaml(output_path)
if existing_data is None:
return None

existing_interface = existing_data.get("interface", {})
display_name = overrides.get("display_name") or format_display_name(skill_name)
short_description = overrides.get("short_description") or generate_short_description(display_name)

Expand All @@ -167,21 +298,21 @@ def write_openai_yaml(skill_dir, skill_name, raw_overrides):
)
return None

interface_lines = [
"interface:",
f" display_name: {yaml_quote(display_name)}",
f" short_description: {yaml_quote(short_description)}",
]
output_data = {
"interface": build_interface(
existing_interface,
display_name,
short_description,
overrides,
optional_order,
)
}

for key in optional_order:
value = overrides.get(key)
if value is not None:
interface_lines.append(f" {key}: {yaml_quote(value)}")
for key, value in existing_data.items():
if key != "interface":
output_data[key] = value

agents_dir = Path(skill_dir) / "agents"
agents_dir.mkdir(parents=True, exist_ok=True)
output_path = agents_dir / "openai.yaml"
output_path.write_text("\n".join(interface_lines) + "\n")
output_path.write_text(dump_yaml(output_data))
print(f"[OK] Created agents/openai.yaml")
return output_path

Expand Down
72 changes: 72 additions & 0 deletions skills/.system/skill-creator/scripts/quick_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,74 @@
import yaml

MAX_SKILL_NAME_LENGTH = 64
INTERFACE_STRING_FIELDS = {
"display_name",
"short_description",
"icon_small",
"icon_large",
"brand_color",
"default_prompt",
}
DEPENDENCY_TOOL_STRING_FIELDS = {
"type",
"value",
"description",
"transport",
"url",
}


def validate_openai_yaml(skill_path):
openai_yaml = skill_path / "agents" / "openai.yaml"
if not openai_yaml.exists():
return True, None

try:
data = yaml.safe_load(openai_yaml.read_text())
except yaml.YAMLError as e:
return False, f"Invalid YAML in agents/openai.yaml: {e}"

if data is None:
return False, "agents/openai.yaml is empty"
if not isinstance(data, dict):
return False, "agents/openai.yaml must be a YAML dictionary"

if "interface" in data:
interface = data["interface"]
if not isinstance(interface, dict):
return False, "agents/openai.yaml field 'interface' must be a YAML dictionary"
for key, value in interface.items():
if key in INTERFACE_STRING_FIELDS and not isinstance(value, str):
return (
False,
f"agents/openai.yaml field 'interface.{key}' must be a string, "
f"got {type(value).__name__}",
)

if "dependencies" in data:
dependencies = data["dependencies"]
if not isinstance(dependencies, dict):
return False, "agents/openai.yaml field 'dependencies' must be a YAML dictionary"
tools = dependencies.get("tools")
if tools is not None:
if not isinstance(tools, list):
return False, "agents/openai.yaml field 'dependencies.tools' must be a YAML list"
for index, tool in enumerate(tools, start=1):
if not isinstance(tool, dict):
return (
False,
"agents/openai.yaml field 'dependencies.tools' entries must be YAML "
f"dictionaries (entry {index})",
)
for key, value in tool.items():
if key in DEPENDENCY_TOOL_STRING_FIELDS and not isinstance(value, str):
return (
False,
f"agents/openai.yaml field 'dependencies.tools[{index}].{key}' "
f"must be a string, got {type(value).__name__}",
)

return True, None


def validate_skill(skill_path):
Expand Down Expand Up @@ -88,6 +156,10 @@ def validate_skill(skill_path):
f"Description is too long ({len(description)} characters). Maximum is 1024 characters.",
)

openai_yaml_valid, openai_yaml_error = validate_openai_yaml(skill_path)
if not openai_yaml_valid:
return False, openai_yaml_error

return True, "Skill is valid!"


Expand Down
113 changes: 113 additions & 0 deletions skills/.system/skill-creator/scripts/skill_creator_scripts_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
import tempfile
import unittest
from pathlib import Path
import sys

import yaml

SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))

import generate_openai_yaml # type: ignore
import quick_validate # type: ignore


class SkillCreatorScriptsTest(unittest.TestCase):
def make_skill_dir(self):
temp_dir = tempfile.TemporaryDirectory()
self.addCleanup(temp_dir.cleanup)
skill_dir = Path(temp_dir.name)
skill_dir.joinpath("SKILL.md").write_text(
"---\n"
"name: openai-docs\n"
"description: Reference official OpenAI docs for implementation guidance.\n"
"---\n"
)
return skill_dir

def test_write_openai_yaml_preserves_existing_sections(self):
skill_dir = self.make_skill_dir()
agents_dir = skill_dir / "agents"
agents_dir.mkdir()
openai_yaml = agents_dir / "openai.yaml"
openai_yaml.write_text(
"interface:\n"
' display_name: "Old Name"\n'
' short_description: "Old description that is safely long enough"\n'
' icon_small: "./assets/openai-small.svg"\n'
' default_prompt: "Use $openai-docs to answer product questions."\n'
"dependencies:\n"
" tools:\n"
' - type: "mcp"\n'
' value: "openaiDeveloperDocs"\n'
' description: "OpenAI Developer Docs MCP server"\n'
' transport: "streamable_http"\n'
' url: "https://developers.openai.com/mcp"\n'
)

result = generate_openai_yaml.write_openai_yaml(skill_dir, "openai-docs", [])

self.assertEqual(result, openai_yaml)
data = yaml.safe_load(openai_yaml.read_text())
self.assertEqual(data["interface"]["display_name"], "OpenAI Docs")
self.assertEqual(
data["interface"]["short_description"],
"Help with OpenAI Docs tasks",
)
self.assertEqual(data["interface"]["icon_small"], "./assets/openai-small.svg")
self.assertEqual(
data["interface"]["default_prompt"],
"Use $openai-docs to answer product questions.",
)
self.assertEqual(
data["dependencies"]["tools"][0]["value"],
"openaiDeveloperDocs",
)

def test_write_openai_yaml_rejects_invalid_existing_yaml(self):
skill_dir = self.make_skill_dir()
agents_dir = skill_dir / "agents"
agents_dir.mkdir()
openai_yaml = agents_dir / "openai.yaml"
openai_yaml.write_text("interface: [\n")

result = generate_openai_yaml.write_openai_yaml(skill_dir, "openai-docs", [])

self.assertIsNone(result)
self.assertEqual(openai_yaml.read_text(), "interface: [\n")

def test_validate_skill_accepts_valid_openai_yaml(self):
skill_dir = self.make_skill_dir()
agents_dir = skill_dir / "agents"
agents_dir.mkdir()
agents_dir.joinpath("openai.yaml").write_text(
"interface:\n"
' display_name: "OpenAI Docs"\n'
' short_description: "Reference official OpenAI docs for implementations"\n'
"dependencies:\n"
" tools:\n"
' - type: "mcp"\n'
' value: "openaiDeveloperDocs"\n'
)

valid, message = quick_validate.validate_skill(skill_dir)

self.assertTrue(valid)
self.assertEqual(message, "Skill is valid!")

def test_validate_skill_rejects_invalid_openai_yaml(self):
skill_dir = self.make_skill_dir()
agents_dir = skill_dir / "agents"
agents_dir.mkdir()
agents_dir.joinpath("openai.yaml").write_text("interface: [\n")

valid, message = quick_validate.validate_skill(skill_dir)

self.assertFalse(valid)
self.assertIn("agents/openai.yaml", message)


if __name__ == "__main__":
unittest.main()