openapi-to-agent-skills 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. openapi_to_agent_skills-0.3.2/.gitignore +31 -0
  2. openapi_to_agent_skills-0.3.2/PKG-INFO +112 -0
  3. openapi_to_agent_skills-0.3.2/README.md +83 -0
  4. openapi_to_agent_skills-0.3.2/pyproject.toml +63 -0
  5. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/__init__.py +5 -0
  6. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/__main__.py +5 -0
  7. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/cli.py +133 -0
  8. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/converter.py +139 -0
  9. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/parser.py +501 -0
  10. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/renderer.py +253 -0
  11. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/spec_loader.py +53 -0
  12. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/templates/authentication.md.j2 +46 -0
  13. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/templates/operation.md.j2 +85 -0
  14. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/templates/resource.md.j2 +13 -0
  15. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/templates/schema.md.j2 +74 -0
  16. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/templates/schema_index.md.j2 +9 -0
  17. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/templates/skill.md.j2 +53 -0
  18. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/types.py +201 -0
  19. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/validate.py +33 -0
  20. openapi_to_agent_skills-0.3.2/src/openapi_to_skills/writer.py +23 -0
  21. openapi_to_agent_skills-0.3.2/tests/__init__.py +0 -0
  22. openapi_to_agent_skills-0.3.2/tests/e2e/__init__.py +0 -0
  23. openapi_to_agent_skills-0.3.2/tests/e2e/test_snapshots.py +86 -0
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ *.egg
9
+ .eggs/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # IDE
17
+ .idea/
18
+ .vscode/
19
+ *.swp
20
+
21
+ # Test output
22
+ test-output/
23
+ test-output-e2e/
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Distribution
30
+ *.whl
31
+ *.tar.gz
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: openapi-to-agent-skills
3
+ Version: 0.3.2
4
+ Summary: Convert OpenAPI specifications to Agent Skills format
5
+ Project-URL: Homepage, https://github.com/xuyong/openapi-to-agent-skills
6
+ Project-URL: Repository, https://github.com/xuyong/openapi-to-agent-skills
7
+ Author: Xu Yong
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Code Generators
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: httpx>=0.27.0
19
+ Requires-Dist: jinja2>=3.1.0
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: regex>=2024.0
22
+ Requires-Dist: rich>=13.0
23
+ Requires-Dist: typer>=0.12.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.10; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # openapi-to-agent-skills
31
+
32
+ Convert OpenAPI specifications to Agent Skills format — structured markdown documentation optimized for AI agents.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install openapi-to-agent-skills
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```bash
43
+ openapi-to-agent-skills <path-or-url-to-openapi-spec> [OPTIONS]
44
+ ```
45
+
46
+ ### Options
47
+
48
+ | Option | Description | Default |
49
+ |--------|-------------|---------|
50
+ | `-o, --output` | Output directory | `./output` |
51
+ | `-n, --name` | Skill name (derived from API title if omitted) | |
52
+ | `--include-tags` | Only include these tags (comma-separated) | |
53
+ | `--exclude-tags` | Exclude these tags (comma-separated) | |
54
+ | `--exclude-deprecated` | Exclude deprecated operations | `False` |
55
+ | `-t, --templates` | Custom templates directory | |
56
+ | `-a, --assets` | Static assets directory overlaid onto output | |
57
+ | `--exclude-paths` | Exclude paths matching prefixes (comma-separated) | |
58
+ | `-g, --group-by` | Grouping strategy: `tags`, `path`, `auto` | `auto` |
59
+ | `--case-strategy` | `lowercase` for case-insensitive filesystem safety | |
60
+ | `-f, --force` | Overwrite existing output directory | `False` |
61
+ | `-q, --quiet` | Suppress output except errors | `False` |
62
+
63
+ ### Examples
64
+
65
+ ```bash
66
+ # From a local file
67
+ openapi-to-agent-skills api.yaml -o ./skills
68
+
69
+ # From a URL
70
+ openapi-to-agent-skills https://petstore3.swagger.io/api/v3/openapi.json -o ./skills
71
+
72
+ # Filter by tags
73
+ openapi-to-agent-skills api.yaml --include-tags users,orders
74
+
75
+ # Case-safe output for Windows/macOS
76
+ openapi-to-agent-skills api.yaml --case-strategy lowercase
77
+ ```
78
+
79
+ ## Output Structure
80
+
81
+ ```
82
+ <skill-name>/
83
+ ├── SKILL.md # Entry point with YAML frontmatter
84
+ └── references/
85
+ ├── authentication.md # Auth schemes (if any)
86
+ ├── resources/
87
+ │ └── <tag>.md # One per resource group
88
+ ├── operations/
89
+ │ └── <operationId>.md # One per operation
90
+ └── schemas/
91
+ └── <prefix>/
92
+ ├── _index.md
93
+ └── <SchemaName>.md # One per schema
94
+ ```
95
+
96
+ ## Programmatic Usage
97
+
98
+ ```python
99
+ from openapi_to_skills import convert_openapi_to_skill
100
+ from openapi_to_skills.types import ConvertOptions
101
+
102
+ import yaml
103
+
104
+ with open("api.yaml") as f:
105
+ spec = yaml.safe_load(f)
106
+
107
+ convert_openapi_to_skill(spec, ConvertOptions(output_dir="./output"))
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,83 @@
1
+ # openapi-to-agent-skills
2
+
3
+ Convert OpenAPI specifications to Agent Skills format — structured markdown documentation optimized for AI agents.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install openapi-to-agent-skills
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ openapi-to-agent-skills <path-or-url-to-openapi-spec> [OPTIONS]
15
+ ```
16
+
17
+ ### Options
18
+
19
+ | Option | Description | Default |
20
+ |--------|-------------|---------|
21
+ | `-o, --output` | Output directory | `./output` |
22
+ | `-n, --name` | Skill name (derived from API title if omitted) | |
23
+ | `--include-tags` | Only include these tags (comma-separated) | |
24
+ | `--exclude-tags` | Exclude these tags (comma-separated) | |
25
+ | `--exclude-deprecated` | Exclude deprecated operations | `False` |
26
+ | `-t, --templates` | Custom templates directory | |
27
+ | `-a, --assets` | Static assets directory overlaid onto output | |
28
+ | `--exclude-paths` | Exclude paths matching prefixes (comma-separated) | |
29
+ | `-g, --group-by` | Grouping strategy: `tags`, `path`, `auto` | `auto` |
30
+ | `--case-strategy` | `lowercase` for case-insensitive filesystem safety | |
31
+ | `-f, --force` | Overwrite existing output directory | `False` |
32
+ | `-q, --quiet` | Suppress output except errors | `False` |
33
+
34
+ ### Examples
35
+
36
+ ```bash
37
+ # From a local file
38
+ openapi-to-agent-skills api.yaml -o ./skills
39
+
40
+ # From a URL
41
+ openapi-to-agent-skills https://petstore3.swagger.io/api/v3/openapi.json -o ./skills
42
+
43
+ # Filter by tags
44
+ openapi-to-agent-skills api.yaml --include-tags users,orders
45
+
46
+ # Case-safe output for Windows/macOS
47
+ openapi-to-agent-skills api.yaml --case-strategy lowercase
48
+ ```
49
+
50
+ ## Output Structure
51
+
52
+ ```
53
+ <skill-name>/
54
+ ├── SKILL.md # Entry point with YAML frontmatter
55
+ └── references/
56
+ ├── authentication.md # Auth schemes (if any)
57
+ ├── resources/
58
+ │ └── <tag>.md # One per resource group
59
+ ├── operations/
60
+ │ └── <operationId>.md # One per operation
61
+ └── schemas/
62
+ └── <prefix>/
63
+ ├── _index.md
64
+ └── <SchemaName>.md # One per schema
65
+ ```
66
+
67
+ ## Programmatic Usage
68
+
69
+ ```python
70
+ from openapi_to_skills import convert_openapi_to_skill
71
+ from openapi_to_skills.types import ConvertOptions
72
+
73
+ import yaml
74
+
75
+ with open("api.yaml") as f:
76
+ spec = yaml.safe_load(f)
77
+
78
+ convert_openapi_to_skill(spec, ConvertOptions(output_dir="./output"))
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,63 @@
1
+ [project]
2
+ name = "openapi-to-agent-skills"
3
+ version = "0.3.2"
4
+ description = "Convert OpenAPI specifications to Agent Skills format"
5
+ requires-python = ">=3.10"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Xu Yong" },
9
+ ]
10
+ readme = "README.md"
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Software Development :: Code Generators",
20
+ ]
21
+ dependencies = [
22
+ "typer>=0.12.0",
23
+ "pyyaml>=6.0",
24
+ "jinja2>=3.1.0",
25
+ "httpx>=0.27.0",
26
+ "rich>=13.0",
27
+ "regex>=2024.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/xuyong/openapi-to-agent-skills"
32
+ Repository = "https://github.com/xuyong/openapi-to-agent-skills"
33
+
34
+ [project.scripts]
35
+ openapi-to-agent-skills = "openapi_to_skills.cli:app"
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=8.0",
40
+ "ruff>=0.5.0",
41
+ "mypy>=1.10",
42
+ ]
43
+
44
+ [build-system]
45
+ requires = ["hatchling"]
46
+ build-backend = "hatchling.build"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/openapi_to_skills"]
50
+
51
+ [tool.ruff]
52
+ target-version = "py310"
53
+ line-length = 100
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "N", "W"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+
61
+ [tool.mypy]
62
+ python_version = "3.10"
63
+ strict = true
@@ -0,0 +1,5 @@
1
+ """OpenAPI to Skills - Convert OpenAPI specifications to Agent Skills format."""
2
+
3
+ from openapi_to_skills.converter import convert_openapi_to_skill
4
+
5
+ __all__ = ["convert_openapi_to_skill"]
@@ -0,0 +1,5 @@
1
+ """Entry point for python -m openapi_to_skills."""
2
+
3
+ from openapi_to_skills.cli import app
4
+
5
+ app()
@@ -0,0 +1,133 @@
1
+ """CLI entry point using Typer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import sys
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+
13
+ from openapi_to_skills.converter import convert_openapi_to_skill
14
+ from openapi_to_skills.renderer import to_file_name
15
+ from openapi_to_skills.spec_loader import load_spec_from_input
16
+ from openapi_to_skills.types import ConvertOptions, ParserFilter, ParserOptions
17
+ from openapi_to_skills.validate import validate_case_strategy, validate_group_by, validate_spec
18
+
19
+ app = typer.Typer(add_completion=False)
20
+ console = Console()
21
+ err_console = Console(stderr=True)
22
+
23
+
24
+ @app.command()
25
+ def main(
26
+ input_path: str = typer.Argument(..., help="Path or URL to OpenAPI spec (JSON or YAML)"),
27
+ output: str = typer.Option("./output", "-o", "--output", help="Output directory"),
28
+ name: Optional[str] = typer.Option(None, "-n", "--name", help="Skill name (default: derived from API title)"),
29
+ include_tags: Optional[str] = typer.Option(None, "--include-tags", help="Only include these tags (comma-separated)"),
30
+ exclude_tags: Optional[str] = typer.Option(None, "--exclude-tags", help="Exclude these tags (comma-separated)"),
31
+ exclude_deprecated: bool = typer.Option(False, "--exclude-deprecated", help="Exclude deprecated operations"),
32
+ templates: Optional[str] = typer.Option(None, "-t", "--templates", help="Custom templates directory"),
33
+ assets: Optional[str] = typer.Option(None, "-a", "--assets", help="Static assets directory overlaid onto the skill after generation"),
34
+ exclude_paths: Optional[str] = typer.Option(None, "--exclude-paths", help="Exclude paths matching these prefixes (comma-separated)"),
35
+ group_by: str = typer.Option("auto", "-g", "--group-by", help="How to group operations: tags, path, auto"),
36
+ case_strategy: Optional[str] = typer.Option(None, "--case-strategy", help="Strategy for case-insensitive filesystem safety: lowercase"),
37
+ force: bool = typer.Option(False, "-f", "--force", help="Overwrite existing output directory"),
38
+ quiet: bool = typer.Option(False, "-q", "--quiet", help="Suppress output except errors"),
39
+ ) -> None:
40
+ """Convert OpenAPI specifications to Agent Skills format."""
41
+ if not quiet:
42
+ console.print(f"[bold]Reading OpenAPI spec:[/bold] {input_path}")
43
+
44
+ try:
45
+ spec = load_spec_from_input(input_path)
46
+ except Exception as e:
47
+ err_console.print(f"[red]Error loading spec:[/red] {e}")
48
+ raise typer.Exit(1)
49
+
50
+ # Validate spec
51
+ spec_error = validate_spec(spec)
52
+ if spec_error:
53
+ err_console.print(f"[red]Error:[/red] {spec_error}")
54
+ raise typer.Exit(1)
55
+
56
+ # Validate options
57
+ try:
58
+ validated_group_by = validate_group_by(group_by)
59
+ validated_case_strategy = validate_case_strategy(case_strategy)
60
+ except ValueError as e:
61
+ err_console.print(f"[red]Error:[/red] {e}")
62
+ raise typer.Exit(1)
63
+
64
+ # Derive skill name for output path check
65
+ skill_name = name or to_file_name(spec["info"]["title"]).lower()[:64]
66
+ output_path = os.path.join(output, skill_name)
67
+
68
+ # Check if output exists
69
+ if os.path.exists(output_path):
70
+ if not force:
71
+ err_console.print(
72
+ f"[red]Error:[/red] Output directory already exists: {output_path}\nUse --force to overwrite.",
73
+ )
74
+ raise typer.Exit(1)
75
+ shutil.rmtree(output_path)
76
+
77
+ # Validate assets dir
78
+ if assets:
79
+ if not os.path.exists(assets):
80
+ console.print(f"[red]Error:[/red] Assets directory not found: {assets}")
81
+ raise typer.Exit(1)
82
+ assets_abs = os.path.abspath(assets)
83
+ output_abs = os.path.abspath(output_path)
84
+ if assets_abs == output_abs or assets_abs.startswith(output_abs + os.sep):
85
+ err_console.print(
86
+ f"[red]Error:[/red] Assets directory must be outside the output directory: {assets}",
87
+ )
88
+ raise typer.Exit(1)
89
+
90
+ if not quiet:
91
+ console.print(f" API: {spec['info']['title']} (v{spec['info'].get('version', '')})")
92
+ console.print(f" OpenAPI version: {spec.get('openapi', '')}")
93
+ console.print(f" Paths: {len(spec.get('paths', {}))}")
94
+ tags_list = spec.get("tags", [])
95
+ tag_names = ", ".join(t["name"] for t in tags_list) if tags_list else "none"
96
+ console.print(f" Tags: {tag_names}")
97
+ schemas = (spec.get("components") or {}).get("schemas")
98
+ if schemas:
99
+ console.print(f" Schemas: {len(schemas)}")
100
+ console.print("[bold]Converting to Agent Skill...[/bold]")
101
+
102
+ # Build options
103
+ parser_filter = ParserFilter(
104
+ include_tags=[t.strip() for t in include_tags.split(",")] if include_tags else None,
105
+ exclude_tags=[t.strip() for t in exclude_tags.split(",")] if exclude_tags else None,
106
+ exclude_deprecated=exclude_deprecated,
107
+ exclude_paths=[p.strip() for p in exclude_paths.split(",")] if exclude_paths else None,
108
+ )
109
+ parser_options = ParserOptions(
110
+ skill_name=name,
111
+ filter=parser_filter,
112
+ group_by=validated_group_by,
113
+ )
114
+ convert_options = ConvertOptions(
115
+ output_dir=output,
116
+ parser=parser_options,
117
+ template_dir=templates,
118
+ case_strategy=validated_case_strategy,
119
+ assets_dir=assets,
120
+ )
121
+
122
+ try:
123
+ convert_openapi_to_skill(spec, convert_options)
124
+ except Exception as e:
125
+ console.print(f"[red]Error during conversion:[/red] {e}")
126
+ raise typer.Exit(1)
127
+
128
+ if not quiet:
129
+ console.print(f"[green]Skill generated at:[/green] {output_path}")
130
+
131
+
132
+ if __name__ == "__main__":
133
+ app()
@@ -0,0 +1,139 @@
1
+ """Converter: orchestrates Parser -> Renderer -> Writer pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from openapi_to_skills.parser import create_parser
8
+ from openapi_to_skills.renderer import create_renderer, to_file_name
9
+ from openapi_to_skills.types import (
10
+ ConvertOptions,
11
+ SchemaDocument,
12
+ SchemaGroupDocument,
13
+ SkillDocument,
14
+ )
15
+ from openapi_to_skills.writer import create_writer
16
+
17
+
18
+ def convert_openapi_to_skill(spec: dict, options: ConvertOptions) -> None:
19
+ parser = create_parser()
20
+ renderer = create_renderer(options.template_dir)
21
+ writer = create_writer()
22
+
23
+ # Parse OpenAPI to IR
24
+ doc = parser.parse(spec, options.parser)
25
+
26
+ # Apply case strategy to IR before rendering
27
+ if options.case_strategy == "lowercase":
28
+ doc = _apply_case_strategy_lowercase(doc)
29
+
30
+ # Write output
31
+ _write_skill_output(doc, options.output_dir, renderer, writer)
32
+
33
+ # Overlay hand-authored static assets
34
+ if options.assets_dir:
35
+ skill_dir = os.path.join(options.output_dir, doc.meta.name)
36
+ writer.copy_dir(options.assets_dir, skill_dir)
37
+
38
+
39
+ def _apply_case_strategy_lowercase(doc: SkillDocument) -> SkillDocument:
40
+ """Merge schema groups by case-insensitive prefix, lowercase names, disambiguate."""
41
+ merged_map: dict[str, dict] = {}
42
+
43
+ for group in doc.schema_groups:
44
+ key = group.prefix.lower()
45
+ if key in merged_map:
46
+ merged_map[key]["schemas"].extend(group.schemas)
47
+ else:
48
+ merged_map[key] = {"prefix": key, "schemas": list(group.schemas)}
49
+
50
+ new_groups: list[SchemaGroupDocument] = []
51
+ for entry in merged_map.values():
52
+ used_names: set[str] = set()
53
+ renamed_schemas: list[SchemaDocument] = []
54
+
55
+ for schema in entry["schemas"]:
56
+ base_name = to_file_name(schema.name).lower()
57
+ final_name = base_name
58
+ counter = 2
59
+
60
+ while final_name in used_names:
61
+ final_name = f"{base_name}-{counter}"
62
+ counter += 1
63
+
64
+ used_names.add(final_name)
65
+ renamed_schemas.append(
66
+ SchemaDocument(
67
+ name=final_name,
68
+ type=schema.type,
69
+ description=schema.description,
70
+ fields=schema.fields,
71
+ enum_values=schema.enum_values,
72
+ composition=schema.composition,
73
+ items=schema.items,
74
+ )
75
+ )
76
+
77
+ new_groups.append(SchemaGroupDocument(prefix=entry["prefix"], schemas=renamed_schemas))
78
+
79
+ return SkillDocument(
80
+ meta=doc.meta,
81
+ resources=doc.resources,
82
+ schema_groups=new_groups,
83
+ auth_schemes=doc.auth_schemes,
84
+ )
85
+
86
+
87
+ def _write_skill_output(doc: SkillDocument, output_dir: str, renderer, writer) -> None:
88
+ skill_dir = os.path.join(output_dir, doc.meta.name)
89
+ references_dir = os.path.join(skill_dir, "references")
90
+ resources_dir = os.path.join(references_dir, "resources")
91
+ operations_dir = os.path.join(references_dir, "operations")
92
+ schemas_dir = os.path.join(references_dir, "schemas")
93
+
94
+ # Create directories
95
+ writer.mkdir(skill_dir)
96
+ if doc.resources:
97
+ writer.mkdir(resources_dir)
98
+ writer.mkdir(operations_dir)
99
+ if doc.schema_groups:
100
+ writer.mkdir(schemas_dir)
101
+
102
+ # Write SKILL.md
103
+ skill_md = renderer.render_skill(doc)
104
+ writer.write_file(os.path.join(skill_dir, "SKILL.md"), skill_md)
105
+
106
+ # Write resources and operations
107
+ for resource in doc.resources:
108
+ file_name = to_file_name(resource.tag)
109
+
110
+ # Resource index
111
+ resource_md = renderer.render_resource(resource)
112
+ writer.write_file(os.path.join(resources_dir, f"{file_name}.md"), resource_md)
113
+
114
+ # Individual operation files
115
+ for op in resource.operations:
116
+ op_file_name = to_file_name(op.operation_id)
117
+ op_md = renderer.render_operation(op)
118
+ writer.write_file(os.path.join(operations_dir, f"{op_file_name}.md"), op_md)
119
+
120
+ # Write schema groups
121
+ for group in doc.schema_groups:
122
+ prefix_dir = os.path.join(schemas_dir, to_file_name(group.prefix))
123
+ writer.mkdir(prefix_dir)
124
+
125
+ # Schema index
126
+ index_md = renderer.render_schema_index(group)
127
+ writer.write_file(os.path.join(prefix_dir, "_index.md"), index_md)
128
+
129
+ # Individual schema files
130
+ for schema in group.schemas:
131
+ schema_md = renderer.render_schema(schema)
132
+ writer.write_file(
133
+ os.path.join(prefix_dir, f"{to_file_name(schema.name)}.md"), schema_md
134
+ )
135
+
136
+ # Write authentication
137
+ if doc.auth_schemes:
138
+ auth_md = renderer.render_authentication(doc.auth_schemes)
139
+ writer.write_file(os.path.join(references_dir, "authentication.md"), auth_md)