@dotflash/openapi-semantic-generator 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FlaSh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # OpenAPI-Agent-Sync (oas-agent-sync)
2
+
3
+ [English](#english) | [한국어](#한국어)
4
+
5
+ ---
6
+
7
+ <a name="english"></a>
8
+
9
+ ## English
10
+
11
+ Middleware tool that analyzes code generated by OpenAPI Generator and automatically creates semantic matching documents (`llms.txt`: A unified index mapping API intents, `operationId`, HTTP paths, and actual TypeScript source file locations; `agent.md`: A semantic guide explaining how to interpret `llms.txt`.) optimized for LLM agents.
12
+
13
+ ### 💡 Best Practices for AI Agents
14
+
15
+ To maximize the effectiveness of this tool, reference the generated files in your AI configuration files (e.g., `.cursorrules`, `claude.md`, `copilot-instructions.md`).
16
+
17
+ #### Example: `.cursorrules` or `claude.md`
18
+
19
+ ```markdown
20
+ # API Usage Guidelines
21
+
22
+ Always refer to the following files to understand available APIs and their usage:
23
+
24
+ - [API Index](openapi/llms.txt)
25
+ - [Agent Guide](openapi/agent.md)
26
+
27
+ 1. Search `llms.txt` for the desired operation.
28
+ 2. Read the implementation in the "Source File" path.
29
+ 3. Call the API as defined in the source code.
30
+ ```
31
+
32
+ It provides an index and guide that allows LLMs to understand large-scale API code (even 2,500+ lines) at a glance, maximizing agent code comprehension.
33
+
34
+ ### Key Features
35
+
36
+ - **Middleware Mode**: Analyzes already generated code folders and compares them with OpenAPI specs.
37
+ - **Smart Path Mapping**: Automatically detects actual file locations within generated code (e.g., `typescript-axios`).
38
+ - **URL Support**: Supports direct input from remote OpenAPI JSON/YAML URLs in addition to local files.
39
+ - **Agent Optimized**: Provides LLM-exclusive summaries (`llms.txt`) and interaction guides (`agent.md`).
40
+
41
+ ### Installation
42
+
43
+ ```bash
44
+ npm install -g oas-agent-sync
45
+ ```
46
+
47
+ ### Usage
48
+
49
+ ```bash
50
+ npx oas-agent-sync <spec-url-or-path> -o <generated-code-dir> [options]
51
+ ```
52
+
53
+ #### Required Arguments
54
+
55
+ 1. `<spec>`: Path or URL to the OpenAPI spec file (e.g., `https://.../openapi.json`)
56
+
57
+ #### Options
58
+
59
+ - `-o, --output <dir>`: Directory where the code is already generated (e.g., `./src/api`)
60
+ - `--api-dir <path>`: Relative path to APIs within the output directory (e.g., `services`)
61
+
62
+ ### Generated Documents
63
+
64
+ Two core documents are generated to help agents understand the API structure with minimal tokens and start implementation immediately.
65
+
66
+ 1. **llms.txt (Semantic Index)**:
67
+ - A unified index mapping API intents, `operationId`, HTTP paths, and **actual TypeScript source file locations**.
68
+ - Agents can see at a glance what functionality to use and which files to read to check types.
69
+
70
+ 2. **agent.md (Agent Interaction Guide)**:
71
+ - Provides guidelines on how the agent should utilize this package.
72
+ - Defines procedures for searching via `llms.txt` and implementing by referencing source code.
73
+
74
+ ---
75
+
76
+ <a name="한국어"></a>
77
+
78
+ ## 한국어
79
+
80
+ OpenAPI Generator로 생성된 코드들을 분석하여 LLM 에이전트가 활용하기 좋은 시맨틱 매칭 문서(`llms.txt`, `agent.md`)를 자동으로 생성해주는 미들웨어 도구입니다.
81
+
82
+ 2,500라인이 넘는 대규모 API 코드라도 LLM이 한눈에 파악할 수 있는 인덱스와 가이드를 제공하여 에이전트의 코드 이해도를 극대화합니다.
83
+
84
+ ### 핵심 기능
85
+
86
+ - **Middleware Mode**: 이미 생성된 코드 폴더와 OpenAPI 스펙을 비교 분석하여 문서 생성.
87
+ - **Smart Path Mapping**: `typescript-axios` 등 생성된 코드 내의 실제 파일 위치 자동 탐색.
88
+ - **URL Support**: 로컬 파일뿐만 아니라 원격 OpenAPI JSON/YAML URL 직접 지원.
89
+ - **Agent Optimized**: LLM 전용 요약(`llms.txt`) 및 상세 가이드(`agent.md`) 제공.
90
+
91
+ ### 설치 방법
92
+
93
+ ```bash
94
+ npm install -g oas-agent-sync
95
+ ```
96
+
97
+ ### 사용 방법
98
+
99
+ ```bash
100
+ npx oas-agent-sync <spec-url-or-path> -o <generated-code-dir> [options]
101
+ ```
102
+
103
+ #### 필수 인자
104
+
105
+ 1. `<spec>`: OpenAPI 스펙 파일 경로 또는 URL (예: `https://.../openapi.json`)
106
+
107
+ #### 옵션
108
+
109
+ - `-o, --output <dir>`: 이미 생성된 코드가 위치한 디렉토리 (예: `./src/api`)
110
+ - `--api-dir <path>`: 출력 디렉토리 내 API 파일이 위치한 상대 경로 (예: `services`)
111
+
112
+ ### 생성되는 문서
113
+
114
+ 에이전트가 최소한의 토큰으로 API 구조를 파악하고 즉시 구현에 들어갈 수 있도록 두 가지 핵심 문서를 생성합니다.
115
+
116
+ 1. **llms.txt (Semantic Index)**:
117
+ - 전체 API의 용도(Intent), `operationId`, HTTP 경로, 그리고 **실제 TypeScript 소스 파일 위치**가 매핑된 통합 인덱스입니다.
118
+ - 에이전트는 이 파일을 통해 어떤 기능을 써야 하는지, 그리고 해당 기능의 타입을 확인하기 위해 어떤 파일을 읽어야 하는지 한눈에 파악합니다.
119
+
120
+ 2. **agent.md (에이전트 인터랙션 가이드)**:
121
+ - `agent.md`: `llms.txt`를 어떻게 해석해야 하는지 설명하는 가이드 문서입니다.
122
+
123
+ ### 💡 AI 에이전트 활용 팁
124
+
125
+ 이 도구의 효과를 극대화하려면, 생성된 파일들을 AI 설정 파일(예: `.cursorrules`, `claude.md`, `copilot-instructions.md`)에 등록하세요.
126
+
127
+ #### 예시: `.cursorrules` 또는 `claude.md`
128
+
129
+ ```markdown
130
+ # API 사용 가이드라인
131
+
132
+ 사용 가능한 API와 사용법을 파악하기 위해 항상 다음 파일을 참조하세요:
133
+
134
+ - [API 인덱스](openapi/llms.txt)
135
+ - [에이전트 가이드](openapi/agent.md)
136
+
137
+ 1. `llms.txt`에서 원하는 작업을 검색하세요.
138
+ 2. "Source File" 경로에 있는 실제 구현 코드를 읽으세요.
139
+ 3. 소스 코드에 정의된 대로 올바르게 API를 호출하세요.
140
+ ```
141
+
142
+ - `llms.txt`를 통한 검색 방법과 소스 코드를 참조하여 구현하는 절차를 정의합니다.
143
+
144
+ ---
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,189 @@
1
+ // src/core/extractor.ts
2
+ import fs from "fs-extra";
3
+ import { parse } from "yaml";
4
+ async function extractMetadata(specPath) {
5
+ let content;
6
+ const isUrl = specPath.startsWith("http://") || specPath.startsWith("https://");
7
+ if (isUrl) {
8
+ const response = await fetch(specPath);
9
+ if (!response.ok) {
10
+ throw new Error(
11
+ `Failed to fetch OpenAPI spec from ${specPath}: ${response.statusText}`
12
+ );
13
+ }
14
+ content = await response.text();
15
+ } else {
16
+ content = await fs.readFile(specPath, "utf-8");
17
+ }
18
+ const spec = specPath.endsWith(".yaml") || specPath.endsWith(".yml") || isUrl && !content.trim().startsWith("{") ? parse(content) : JSON.parse(content);
19
+ const title = spec.info?.title || "API";
20
+ const description = spec.info?.description;
21
+ const version = spec.info?.version || "1.0.0";
22
+ const operations = [];
23
+ if (spec.paths) {
24
+ for (const [path4, methods] of Object.entries(spec.paths)) {
25
+ for (const [method, details] of Object.entries(methods)) {
26
+ if (["get", "post", "put", "delete", "patch", "options", "head"].includes(
27
+ method.toLowerCase()
28
+ )) {
29
+ const typedDetails = details;
30
+ const parameters = (typedDetails.parameters || []).map((p) => ({
31
+ name: p.name,
32
+ type: p.schema?.type || "string",
33
+ required: !!p.required,
34
+ description: p.description,
35
+ in: p.in
36
+ }));
37
+ if (typedDetails.requestBody) {
38
+ }
39
+ operations.push({
40
+ operationId: typedDetails.operationId || `${method}_${path4.replace(/\//g, "_")}`,
41
+ summary: typedDetails.summary,
42
+ description: typedDetails.description,
43
+ httpMethod: method.toUpperCase(),
44
+ path: path4,
45
+ sourceFile: "",
46
+ // To be filled by scanner
47
+ className: "",
48
+ // To be filled by scanner
49
+ tags: typedDetails.tags || [],
50
+ parameters,
51
+ responseType: "any"
52
+ // To be improved
53
+ });
54
+ }
55
+ }
56
+ }
57
+ }
58
+ return {
59
+ title,
60
+ description,
61
+ version,
62
+ operations: operations.map((op) => ({
63
+ ...op,
64
+ description: cleanDescription(op.description || op.summary)
65
+ }))
66
+ };
67
+ }
68
+ function cleanDescription(desc) {
69
+ if (!desc) return "";
70
+ return desc.split("\n").filter((line) => {
71
+ const trimmed = line.trim();
72
+ return trimmed && !trimmed.startsWith(":") && !trimmed.startsWith("- ") && !trimmed.startsWith("workflow:") && !trimmed.includes("https://");
73
+ }).join(" ").trim().replace(/\s+/g, " ").slice(0, 60);
74
+ }
75
+
76
+ // src/core/scanner.ts
77
+ import fs2 from "fs-extra";
78
+ import path from "path";
79
+ async function scanGeneratedFiles(outputDir, metadata, options = {}) {
80
+ const apis = [];
81
+ const searchDirs = options.apiDir ? [options.apiDir, ...["api", "apis", "."]] : ["api", "apis", "."];
82
+ for (const dirName of searchDirs) {
83
+ const fullDir = path.join(outputDir, dirName);
84
+ if (!await fs2.pathExists(fullDir)) continue;
85
+ const files = await fs2.readdir(fullDir);
86
+ for (const file of files) {
87
+ if ((file.endsWith("Api.ts") || file.endsWith("api.ts")) && !file.endsWith(".test.ts")) {
88
+ apis.push({
89
+ className: file.replace(".ts", ""),
90
+ // Simplified
91
+ sourceFile: path.join(dirName, file)
92
+ });
93
+ }
94
+ }
95
+ }
96
+ if (metadata.operations) {
97
+ for (const op of metadata.operations) {
98
+ if (apis.length > 0) {
99
+ op.className = apis[0].className;
100
+ op.sourceFile = apis[0].sourceFile;
101
+ }
102
+ }
103
+ }
104
+ return {
105
+ ...metadata,
106
+ packageName: "api-client",
107
+ // Default
108
+ importPath: "./api",
109
+ // Default
110
+ apis,
111
+ operations: metadata.operations || []
112
+ };
113
+ }
114
+
115
+ // src/core/renderer.ts
116
+ import Mustache from "mustache";
117
+ import fs3 from "fs-extra";
118
+ import path2 from "path";
119
+ async function renderDocs(metadata, outputDir) {
120
+ const currentDir = path2.dirname(new URL(import.meta.url).pathname);
121
+ let templateDir = path2.join(currentDir, "../templates");
122
+ if (!fs3.existsSync(templateDir)) {
123
+ const possiblePaths = [
124
+ path2.join(currentDir, "./templates"),
125
+ path2.join(currentDir, "../../templates"),
126
+ path2.join(process.cwd(), "templates"),
127
+ path2.join(process.cwd(), "dist/templates")
128
+ ];
129
+ for (const p of possiblePaths) {
130
+ if (fs3.existsSync(p)) {
131
+ templateDir = p;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ const llmsTemplate = await fs3.readFile(path2.join(templateDir, "llms-full.mustache"), "utf-8");
137
+ const agentTemplate = await fs3.readFile(path2.join(templateDir, "agent.mustache"), "utf-8");
138
+ const operationsByTag = {};
139
+ metadata.operations.forEach((op) => {
140
+ const tag = op.tags?.[0] || "Default";
141
+ if (!operationsByTag[tag]) operationsByTag[tag] = [];
142
+ operationsByTag[tag].push(op);
143
+ });
144
+ const groupedOperations = Object.entries(operationsByTag).map(([tag, ops]) => ({
145
+ tag,
146
+ ops: ops.map((op, index) => ({
147
+ ...op,
148
+ last: index === ops.length - 1
149
+ }))
150
+ }));
151
+ const enrichedMetadata = {
152
+ ...metadata,
153
+ groupedOperations,
154
+ apis: metadata.apis.map((api, index) => ({
155
+ ...api,
156
+ last: index === metadata.apis.length - 1
157
+ }))
158
+ };
159
+ const llms = Mustache.render(llmsTemplate, enrichedMetadata);
160
+ const agent = Mustache.render(agentTemplate, enrichedMetadata);
161
+ await fs3.writeFile(path2.join(outputDir, "llms.txt"), llms);
162
+ await fs3.writeFile(path2.join(outputDir, "agent.md"), agent);
163
+ }
164
+
165
+ // src/core/processor.ts
166
+ import path3 from "path";
167
+ import fs4 from "fs-extra";
168
+ async function generateDocs(options) {
169
+ const { inputSpec, outputDir, apiDir } = options;
170
+ try {
171
+ console.log("\u{1F4DD} Generating LLM documentation...");
172
+ let metadata = await extractMetadata(inputSpec);
173
+ const fullMetadata = await scanGeneratedFiles(outputDir, metadata, { apiDir });
174
+ await fs4.ensureDir(outputDir);
175
+ await renderDocs(fullMetadata, outputDir);
176
+ console.log(`\u2728 Documentation generated at: ${outputDir}`);
177
+ console.log(` - ${path3.join(outputDir, "llms.txt")}`);
178
+ } catch (err) {
179
+ console.error("\u274C Failed to generate LLM documentation:", err);
180
+ throw err;
181
+ }
182
+ }
183
+
184
+ export {
185
+ extractMetadata,
186
+ scanGeneratedFiles,
187
+ renderDocs,
188
+ generateDocs
189
+ };
package/dist/cli.cjs ADDED
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js
27
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
28
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
29
+
30
+ // src/cli.ts
31
+ var import_commander = require("commander");
32
+ var import_path4 = __toESM(require("path"), 1);
33
+ var import_fs_extra5 = __toESM(require("fs-extra"), 1);
34
+
35
+ // src/core/processor.ts
36
+ var import_path3 = __toESM(require("path"), 1);
37
+ var import_fs_extra4 = __toESM(require("fs-extra"), 1);
38
+
39
+ // src/core/extractor.ts
40
+ var import_fs_extra = __toESM(require("fs-extra"), 1);
41
+ var import_yaml = require("yaml");
42
+ async function extractMetadata(specPath) {
43
+ let content;
44
+ const isUrl = specPath.startsWith("http://") || specPath.startsWith("https://");
45
+ if (isUrl) {
46
+ const response = await fetch(specPath);
47
+ if (!response.ok) {
48
+ throw new Error(
49
+ `Failed to fetch OpenAPI spec from ${specPath}: ${response.statusText}`
50
+ );
51
+ }
52
+ content = await response.text();
53
+ } else {
54
+ content = await import_fs_extra.default.readFile(specPath, "utf-8");
55
+ }
56
+ const spec = specPath.endsWith(".yaml") || specPath.endsWith(".yml") || isUrl && !content.trim().startsWith("{") ? (0, import_yaml.parse)(content) : JSON.parse(content);
57
+ const title = spec.info?.title || "API";
58
+ const description = spec.info?.description;
59
+ const version = spec.info?.version || "1.0.0";
60
+ const operations = [];
61
+ if (spec.paths) {
62
+ for (const [path5, methods] of Object.entries(spec.paths)) {
63
+ for (const [method, details] of Object.entries(methods)) {
64
+ if (["get", "post", "put", "delete", "patch", "options", "head"].includes(
65
+ method.toLowerCase()
66
+ )) {
67
+ const typedDetails = details;
68
+ const parameters = (typedDetails.parameters || []).map((p) => ({
69
+ name: p.name,
70
+ type: p.schema?.type || "string",
71
+ required: !!p.required,
72
+ description: p.description,
73
+ in: p.in
74
+ }));
75
+ if (typedDetails.requestBody) {
76
+ }
77
+ operations.push({
78
+ operationId: typedDetails.operationId || `${method}_${path5.replace(/\//g, "_")}`,
79
+ summary: typedDetails.summary,
80
+ description: typedDetails.description,
81
+ httpMethod: method.toUpperCase(),
82
+ path: path5,
83
+ sourceFile: "",
84
+ // To be filled by scanner
85
+ className: "",
86
+ // To be filled by scanner
87
+ tags: typedDetails.tags || [],
88
+ parameters,
89
+ responseType: "any"
90
+ // To be improved
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return {
97
+ title,
98
+ description,
99
+ version,
100
+ operations: operations.map((op) => ({
101
+ ...op,
102
+ description: cleanDescription(op.description || op.summary)
103
+ }))
104
+ };
105
+ }
106
+ function cleanDescription(desc) {
107
+ if (!desc) return "";
108
+ return desc.split("\n").filter((line) => {
109
+ const trimmed = line.trim();
110
+ return trimmed && !trimmed.startsWith(":") && !trimmed.startsWith("- ") && !trimmed.startsWith("workflow:") && !trimmed.includes("https://");
111
+ }).join(" ").trim().replace(/\s+/g, " ").slice(0, 60);
112
+ }
113
+
114
+ // src/core/scanner.ts
115
+ var import_fs_extra2 = __toESM(require("fs-extra"), 1);
116
+ var import_path = __toESM(require("path"), 1);
117
+ async function scanGeneratedFiles(outputDir, metadata, options = {}) {
118
+ const apis = [];
119
+ const searchDirs = options.apiDir ? [options.apiDir, ...["api", "apis", "."]] : ["api", "apis", "."];
120
+ for (const dirName of searchDirs) {
121
+ const fullDir = import_path.default.join(outputDir, dirName);
122
+ if (!await import_fs_extra2.default.pathExists(fullDir)) continue;
123
+ const files = await import_fs_extra2.default.readdir(fullDir);
124
+ for (const file of files) {
125
+ if ((file.endsWith("Api.ts") || file.endsWith("api.ts")) && !file.endsWith(".test.ts")) {
126
+ apis.push({
127
+ className: file.replace(".ts", ""),
128
+ // Simplified
129
+ sourceFile: import_path.default.join(dirName, file)
130
+ });
131
+ }
132
+ }
133
+ }
134
+ if (metadata.operations) {
135
+ for (const op of metadata.operations) {
136
+ if (apis.length > 0) {
137
+ op.className = apis[0].className;
138
+ op.sourceFile = apis[0].sourceFile;
139
+ }
140
+ }
141
+ }
142
+ return {
143
+ ...metadata,
144
+ packageName: "api-client",
145
+ // Default
146
+ importPath: "./api",
147
+ // Default
148
+ apis,
149
+ operations: metadata.operations || []
150
+ };
151
+ }
152
+
153
+ // src/core/renderer.ts
154
+ var import_mustache = __toESM(require("mustache"), 1);
155
+ var import_fs_extra3 = __toESM(require("fs-extra"), 1);
156
+ var import_path2 = __toESM(require("path"), 1);
157
+ async function renderDocs(metadata, outputDir) {
158
+ const currentDir = import_path2.default.dirname(new URL(importMetaUrl).pathname);
159
+ let templateDir = import_path2.default.join(currentDir, "../templates");
160
+ if (!import_fs_extra3.default.existsSync(templateDir)) {
161
+ const possiblePaths = [
162
+ import_path2.default.join(currentDir, "./templates"),
163
+ import_path2.default.join(currentDir, "../../templates"),
164
+ import_path2.default.join(process.cwd(), "templates"),
165
+ import_path2.default.join(process.cwd(), "dist/templates")
166
+ ];
167
+ for (const p of possiblePaths) {
168
+ if (import_fs_extra3.default.existsSync(p)) {
169
+ templateDir = p;
170
+ break;
171
+ }
172
+ }
173
+ }
174
+ const llmsTemplate = await import_fs_extra3.default.readFile(import_path2.default.join(templateDir, "llms-full.mustache"), "utf-8");
175
+ const agentTemplate = await import_fs_extra3.default.readFile(import_path2.default.join(templateDir, "agent.mustache"), "utf-8");
176
+ const operationsByTag = {};
177
+ metadata.operations.forEach((op) => {
178
+ const tag = op.tags?.[0] || "Default";
179
+ if (!operationsByTag[tag]) operationsByTag[tag] = [];
180
+ operationsByTag[tag].push(op);
181
+ });
182
+ const groupedOperations = Object.entries(operationsByTag).map(([tag, ops]) => ({
183
+ tag,
184
+ ops: ops.map((op, index) => ({
185
+ ...op,
186
+ last: index === ops.length - 1
187
+ }))
188
+ }));
189
+ const enrichedMetadata = {
190
+ ...metadata,
191
+ groupedOperations,
192
+ apis: metadata.apis.map((api, index) => ({
193
+ ...api,
194
+ last: index === metadata.apis.length - 1
195
+ }))
196
+ };
197
+ const llms = import_mustache.default.render(llmsTemplate, enrichedMetadata);
198
+ const agent = import_mustache.default.render(agentTemplate, enrichedMetadata);
199
+ await import_fs_extra3.default.writeFile(import_path2.default.join(outputDir, "llms.txt"), llms);
200
+ await import_fs_extra3.default.writeFile(import_path2.default.join(outputDir, "agent.md"), agent);
201
+ }
202
+
203
+ // src/core/processor.ts
204
+ async function generateDocs(options) {
205
+ const { inputSpec, outputDir, apiDir } = options;
206
+ try {
207
+ console.log("\u{1F4DD} Generating LLM documentation...");
208
+ let metadata = await extractMetadata(inputSpec);
209
+ const fullMetadata = await scanGeneratedFiles(outputDir, metadata, { apiDir });
210
+ await import_fs_extra4.default.ensureDir(outputDir);
211
+ await renderDocs(fullMetadata, outputDir);
212
+ console.log(`\u2728 Documentation generated at: ${outputDir}`);
213
+ console.log(` - ${import_path3.default.join(outputDir, "llms.txt")}`);
214
+ } catch (err) {
215
+ console.error("\u274C Failed to generate LLM documentation:", err);
216
+ throw err;
217
+ }
218
+ }
219
+
220
+ // src/cli.ts
221
+ var program = new import_commander.Command();
222
+ program.name("oas-agent-sync").description("Generate LLM documentation for existing generated code (Middleware mode)").version("0.1.0");
223
+ program.argument("<spec>", "OpenAPI specification file (local path or URL)").requiredOption("-o, --output <dir>", "Existing generated code directory").option("--api-dir <path>", "Relative path to APIs within output directory", "").action(async (spec, options) => {
224
+ try {
225
+ let inputSpec = spec;
226
+ const isUrl = inputSpec.startsWith("http://") || inputSpec.startsWith("https://");
227
+ if (!isUrl) {
228
+ inputSpec = import_path4.default.resolve(process.cwd(), inputSpec);
229
+ if (!await import_fs_extra5.default.pathExists(inputSpec)) {
230
+ console.error(`Error: Input file not found: ${inputSpec}`);
231
+ process.exit(1);
232
+ }
233
+ }
234
+ const outputDir = import_path4.default.resolve(process.cwd(), options.output);
235
+ if (!await import_fs_extra5.default.pathExists(outputDir)) {
236
+ console.error(`Error: Output directory not found: ${outputDir}`);
237
+ process.exit(1);
238
+ }
239
+ await generateDocs({
240
+ inputSpec,
241
+ outputDir,
242
+ apiDir: options.apiDir || void 0
243
+ });
244
+ } catch (error) {
245
+ console.error(`Error: ${error.message}`);
246
+ process.exit(1);
247
+ }
248
+ });
249
+ program.parse(process.argv);
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateDocs
4
+ } from "./chunk-JGV5ULAN.js";
5
+
6
+ // src/cli.ts
7
+ import { Command } from "commander";
8
+ import path from "path";
9
+ import fs from "fs-extra";
10
+ var program = new Command();
11
+ program.name("oas-agent-sync").description("Generate LLM documentation for existing generated code (Middleware mode)").version("0.1.0");
12
+ program.argument("<spec>", "OpenAPI specification file (local path or URL)").requiredOption("-o, --output <dir>", "Existing generated code directory").option("--api-dir <path>", "Relative path to APIs within output directory", "").action(async (spec, options) => {
13
+ try {
14
+ let inputSpec = spec;
15
+ const isUrl = inputSpec.startsWith("http://") || inputSpec.startsWith("https://");
16
+ if (!isUrl) {
17
+ inputSpec = path.resolve(process.cwd(), inputSpec);
18
+ if (!await fs.pathExists(inputSpec)) {
19
+ console.error(`Error: Input file not found: ${inputSpec}`);
20
+ process.exit(1);
21
+ }
22
+ }
23
+ const outputDir = path.resolve(process.cwd(), options.output);
24
+ if (!await fs.pathExists(outputDir)) {
25
+ console.error(`Error: Output directory not found: ${outputDir}`);
26
+ process.exit(1);
27
+ }
28
+ await generateDocs({
29
+ inputSpec,
30
+ outputDir,
31
+ apiDir: options.apiDir || void 0
32
+ });
33
+ } catch (error) {
34
+ console.error(`Error: ${error.message}`);
35
+ process.exit(1);
36
+ }
37
+ });
38
+ program.parse(process.argv);
package/dist/index.cjs ADDED
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ extractMetadata: () => extractMetadata,
34
+ generateDocs: () => generateDocs,
35
+ renderDocs: () => renderDocs,
36
+ scanGeneratedFiles: () => scanGeneratedFiles
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js
41
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
42
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
43
+
44
+ // src/core/processor.ts
45
+ var import_path3 = __toESM(require("path"), 1);
46
+ var import_fs_extra4 = __toESM(require("fs-extra"), 1);
47
+
48
+ // src/core/extractor.ts
49
+ var import_fs_extra = __toESM(require("fs-extra"), 1);
50
+ var import_yaml = require("yaml");
51
+ async function extractMetadata(specPath) {
52
+ let content;
53
+ const isUrl = specPath.startsWith("http://") || specPath.startsWith("https://");
54
+ if (isUrl) {
55
+ const response = await fetch(specPath);
56
+ if (!response.ok) {
57
+ throw new Error(
58
+ `Failed to fetch OpenAPI spec from ${specPath}: ${response.statusText}`
59
+ );
60
+ }
61
+ content = await response.text();
62
+ } else {
63
+ content = await import_fs_extra.default.readFile(specPath, "utf-8");
64
+ }
65
+ const spec = specPath.endsWith(".yaml") || specPath.endsWith(".yml") || isUrl && !content.trim().startsWith("{") ? (0, import_yaml.parse)(content) : JSON.parse(content);
66
+ const title = spec.info?.title || "API";
67
+ const description = spec.info?.description;
68
+ const version = spec.info?.version || "1.0.0";
69
+ const operations = [];
70
+ if (spec.paths) {
71
+ for (const [path4, methods] of Object.entries(spec.paths)) {
72
+ for (const [method, details] of Object.entries(methods)) {
73
+ if (["get", "post", "put", "delete", "patch", "options", "head"].includes(
74
+ method.toLowerCase()
75
+ )) {
76
+ const typedDetails = details;
77
+ const parameters = (typedDetails.parameters || []).map((p) => ({
78
+ name: p.name,
79
+ type: p.schema?.type || "string",
80
+ required: !!p.required,
81
+ description: p.description,
82
+ in: p.in
83
+ }));
84
+ if (typedDetails.requestBody) {
85
+ }
86
+ operations.push({
87
+ operationId: typedDetails.operationId || `${method}_${path4.replace(/\//g, "_")}`,
88
+ summary: typedDetails.summary,
89
+ description: typedDetails.description,
90
+ httpMethod: method.toUpperCase(),
91
+ path: path4,
92
+ sourceFile: "",
93
+ // To be filled by scanner
94
+ className: "",
95
+ // To be filled by scanner
96
+ tags: typedDetails.tags || [],
97
+ parameters,
98
+ responseType: "any"
99
+ // To be improved
100
+ });
101
+ }
102
+ }
103
+ }
104
+ }
105
+ return {
106
+ title,
107
+ description,
108
+ version,
109
+ operations: operations.map((op) => ({
110
+ ...op,
111
+ description: cleanDescription(op.description || op.summary)
112
+ }))
113
+ };
114
+ }
115
+ function cleanDescription(desc) {
116
+ if (!desc) return "";
117
+ return desc.split("\n").filter((line) => {
118
+ const trimmed = line.trim();
119
+ return trimmed && !trimmed.startsWith(":") && !trimmed.startsWith("- ") && !trimmed.startsWith("workflow:") && !trimmed.includes("https://");
120
+ }).join(" ").trim().replace(/\s+/g, " ").slice(0, 60);
121
+ }
122
+
123
+ // src/core/scanner.ts
124
+ var import_fs_extra2 = __toESM(require("fs-extra"), 1);
125
+ var import_path = __toESM(require("path"), 1);
126
+ async function scanGeneratedFiles(outputDir, metadata, options = {}) {
127
+ const apis = [];
128
+ const searchDirs = options.apiDir ? [options.apiDir, ...["api", "apis", "."]] : ["api", "apis", "."];
129
+ for (const dirName of searchDirs) {
130
+ const fullDir = import_path.default.join(outputDir, dirName);
131
+ if (!await import_fs_extra2.default.pathExists(fullDir)) continue;
132
+ const files = await import_fs_extra2.default.readdir(fullDir);
133
+ for (const file of files) {
134
+ if ((file.endsWith("Api.ts") || file.endsWith("api.ts")) && !file.endsWith(".test.ts")) {
135
+ apis.push({
136
+ className: file.replace(".ts", ""),
137
+ // Simplified
138
+ sourceFile: import_path.default.join(dirName, file)
139
+ });
140
+ }
141
+ }
142
+ }
143
+ if (metadata.operations) {
144
+ for (const op of metadata.operations) {
145
+ if (apis.length > 0) {
146
+ op.className = apis[0].className;
147
+ op.sourceFile = apis[0].sourceFile;
148
+ }
149
+ }
150
+ }
151
+ return {
152
+ ...metadata,
153
+ packageName: "api-client",
154
+ // Default
155
+ importPath: "./api",
156
+ // Default
157
+ apis,
158
+ operations: metadata.operations || []
159
+ };
160
+ }
161
+
162
+ // src/core/renderer.ts
163
+ var import_mustache = __toESM(require("mustache"), 1);
164
+ var import_fs_extra3 = __toESM(require("fs-extra"), 1);
165
+ var import_path2 = __toESM(require("path"), 1);
166
+ async function renderDocs(metadata, outputDir) {
167
+ const currentDir = import_path2.default.dirname(new URL(importMetaUrl).pathname);
168
+ let templateDir = import_path2.default.join(currentDir, "../templates");
169
+ if (!import_fs_extra3.default.existsSync(templateDir)) {
170
+ const possiblePaths = [
171
+ import_path2.default.join(currentDir, "./templates"),
172
+ import_path2.default.join(currentDir, "../../templates"),
173
+ import_path2.default.join(process.cwd(), "templates"),
174
+ import_path2.default.join(process.cwd(), "dist/templates")
175
+ ];
176
+ for (const p of possiblePaths) {
177
+ if (import_fs_extra3.default.existsSync(p)) {
178
+ templateDir = p;
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ const llmsTemplate = await import_fs_extra3.default.readFile(import_path2.default.join(templateDir, "llms-full.mustache"), "utf-8");
184
+ const agentTemplate = await import_fs_extra3.default.readFile(import_path2.default.join(templateDir, "agent.mustache"), "utf-8");
185
+ const operationsByTag = {};
186
+ metadata.operations.forEach((op) => {
187
+ const tag = op.tags?.[0] || "Default";
188
+ if (!operationsByTag[tag]) operationsByTag[tag] = [];
189
+ operationsByTag[tag].push(op);
190
+ });
191
+ const groupedOperations = Object.entries(operationsByTag).map(([tag, ops]) => ({
192
+ tag,
193
+ ops: ops.map((op, index) => ({
194
+ ...op,
195
+ last: index === ops.length - 1
196
+ }))
197
+ }));
198
+ const enrichedMetadata = {
199
+ ...metadata,
200
+ groupedOperations,
201
+ apis: metadata.apis.map((api, index) => ({
202
+ ...api,
203
+ last: index === metadata.apis.length - 1
204
+ }))
205
+ };
206
+ const llms = import_mustache.default.render(llmsTemplate, enrichedMetadata);
207
+ const agent = import_mustache.default.render(agentTemplate, enrichedMetadata);
208
+ await import_fs_extra3.default.writeFile(import_path2.default.join(outputDir, "llms.txt"), llms);
209
+ await import_fs_extra3.default.writeFile(import_path2.default.join(outputDir, "agent.md"), agent);
210
+ }
211
+
212
+ // src/core/processor.ts
213
+ async function generateDocs(options) {
214
+ const { inputSpec, outputDir, apiDir } = options;
215
+ try {
216
+ console.log("\u{1F4DD} Generating LLM documentation...");
217
+ let metadata = await extractMetadata(inputSpec);
218
+ const fullMetadata = await scanGeneratedFiles(outputDir, metadata, { apiDir });
219
+ await import_fs_extra4.default.ensureDir(outputDir);
220
+ await renderDocs(fullMetadata, outputDir);
221
+ console.log(`\u2728 Documentation generated at: ${outputDir}`);
222
+ console.log(` - ${import_path3.default.join(outputDir, "llms.txt")}`);
223
+ } catch (err) {
224
+ console.error("\u274C Failed to generate LLM documentation:", err);
225
+ throw err;
226
+ }
227
+ }
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ extractMetadata,
231
+ generateDocs,
232
+ renderDocs,
233
+ scanGeneratedFiles
234
+ });
@@ -0,0 +1,49 @@
1
+ interface ProjectMetadata {
2
+ title: string;
3
+ description?: string;
4
+ version: string;
5
+ packageName: string;
6
+ importPath: string;
7
+ apis: ApiClass[];
8
+ operations: OperationMetadata[];
9
+ }
10
+ interface ApiClass {
11
+ className: string;
12
+ sourceFile: string;
13
+ }
14
+ interface OperationMetadata {
15
+ operationId: string;
16
+ summary?: string;
17
+ description?: string;
18
+ httpMethod: string;
19
+ path: string;
20
+ sourceFile: string;
21
+ className: string;
22
+ tags?: string[];
23
+ parameters: ParameterMetadata[];
24
+ responseType: string;
25
+ }
26
+ interface ParameterMetadata {
27
+ name: string;
28
+ type: string;
29
+ required: boolean;
30
+ description?: string;
31
+ in: 'path' | 'query' | 'header' | 'body' | 'cookie';
32
+ }
33
+ interface SyncOptions {
34
+ inputSpec: string;
35
+ outputDir: string;
36
+ apiDir?: string;
37
+ }
38
+
39
+ declare function generateDocs(options: SyncOptions): Promise<void>;
40
+
41
+ declare function extractMetadata(specPath: string): Promise<Partial<ProjectMetadata>>;
42
+
43
+ declare function scanGeneratedFiles(outputDir: string, metadata: Partial<ProjectMetadata>, options?: {
44
+ apiDir?: string;
45
+ }): Promise<ProjectMetadata>;
46
+
47
+ declare function renderDocs(metadata: ProjectMetadata, outputDir: string): Promise<void>;
48
+
49
+ export { type ApiClass, type OperationMetadata, type ParameterMetadata, type ProjectMetadata, type SyncOptions, extractMetadata, generateDocs, renderDocs, scanGeneratedFiles };
@@ -0,0 +1,49 @@
1
+ interface ProjectMetadata {
2
+ title: string;
3
+ description?: string;
4
+ version: string;
5
+ packageName: string;
6
+ importPath: string;
7
+ apis: ApiClass[];
8
+ operations: OperationMetadata[];
9
+ }
10
+ interface ApiClass {
11
+ className: string;
12
+ sourceFile: string;
13
+ }
14
+ interface OperationMetadata {
15
+ operationId: string;
16
+ summary?: string;
17
+ description?: string;
18
+ httpMethod: string;
19
+ path: string;
20
+ sourceFile: string;
21
+ className: string;
22
+ tags?: string[];
23
+ parameters: ParameterMetadata[];
24
+ responseType: string;
25
+ }
26
+ interface ParameterMetadata {
27
+ name: string;
28
+ type: string;
29
+ required: boolean;
30
+ description?: string;
31
+ in: 'path' | 'query' | 'header' | 'body' | 'cookie';
32
+ }
33
+ interface SyncOptions {
34
+ inputSpec: string;
35
+ outputDir: string;
36
+ apiDir?: string;
37
+ }
38
+
39
+ declare function generateDocs(options: SyncOptions): Promise<void>;
40
+
41
+ declare function extractMetadata(specPath: string): Promise<Partial<ProjectMetadata>>;
42
+
43
+ declare function scanGeneratedFiles(outputDir: string, metadata: Partial<ProjectMetadata>, options?: {
44
+ apiDir?: string;
45
+ }): Promise<ProjectMetadata>;
46
+
47
+ declare function renderDocs(metadata: ProjectMetadata, outputDir: string): Promise<void>;
48
+
49
+ export { type ApiClass, type OperationMetadata, type ParameterMetadata, type ProjectMetadata, type SyncOptions, extractMetadata, generateDocs, renderDocs, scanGeneratedFiles };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import {
2
+ extractMetadata,
3
+ generateDocs,
4
+ renderDocs,
5
+ scanGeneratedFiles
6
+ } from "./chunk-JGV5ULAN.js";
7
+ export {
8
+ extractMetadata,
9
+ generateDocs,
10
+ renderDocs,
11
+ scanGeneratedFiles
12
+ };
@@ -0,0 +1,12 @@
1
+ # AI Agent Semantic Guide
2
+
3
+ This package provides tools to help AI agents semantically search and use APIs based on OpenAPI specifications.
4
+
5
+ ## Core Features
6
+ 1. **llms.txt**: A primary index file mapping all API intents, operationIDs, paths, and actual source code locations.
7
+
8
+ ## Agent Interaction Guide
9
+ 1. **Search & Implement**: Search for the required functionality in `llms.txt` to fulfill the user's request.
10
+ 2. **Understand**: Read the actual code at the `Source File` path provided in `llms.txt` to understand exact parameters and return types before writing code.
11
+
12
+ This guide is designed to help agents select the correct API without unnecessary token waste and move quickly to the implementation phase.
@@ -0,0 +1,9 @@
1
+ # {{title}} APIs
2
+ {{#groupedOperations}}
3
+ ## {{tag}}
4
+ |Intent|API|Path|Source File|
5
+ |:---|:---|:---|:---|
6
+ {{#ops}}
7
+ |{{{description}}}|`{{{operationId}}}`|`{{{httpMethod}}} {{{path}}}`|`{{{sourceFile}}}`|
8
+ {{/ops}}
9
+ {{/groupedOperations}}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@dotflash/openapi-semantic-generator",
3
+ "version": "0.1.0",
4
+ "description": "Semantic mapping automation tool for LLM agents and OpenAPI generated code",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "oas-agent-sync": "dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "templates",
15
+ "src/templates"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --watch --shims",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "gen:example": "openapi-generator-cli generate -i test/fixtures/petstore.yaml -g typescript-axios -o test/generated/petstore && node dist/cli.js test/fixtures/petstore.yaml -o test/generated/petstore --api-dir api"
23
+ },
24
+ "keywords": [
25
+ "openapi",
26
+ "generator",
27
+ "llm",
28
+ "agent",
29
+ "semantic-mapping",
30
+ "documentation"
31
+ ],
32
+ "author": "no1flash",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "commander": "^13.1.0",
36
+ "fs-extra": "^11.3.0",
37
+ "mustache": "^4.2.0",
38
+ "yaml": "^2.7.0"
39
+ },
40
+ "devDependencies": {
41
+ "@openapitools/openapi-generator-cli": "^2.28.2",
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/mustache": "^4.2.5",
44
+ "@types/node": "^22.13.1",
45
+ "tsup": "^8.3.6",
46
+ "typescript": "^5.7.3",
47
+ "vitest": "^3.0.5"
48
+ }
49
+ }
@@ -0,0 +1,12 @@
1
+ # AI Agent Semantic Guide
2
+
3
+ This package provides tools to help AI agents semantically search and use APIs based on OpenAPI specifications.
4
+
5
+ ## Core Features
6
+ 1. **llms.txt**: A primary index file mapping all API intents, operationIDs, paths, and actual source code locations.
7
+
8
+ ## Agent Interaction Guide
9
+ 1. **Search & Implement**: Search for the required functionality in `llms.txt` to fulfill the user's request.
10
+ 2. **Understand**: Read the actual code at the `Source File` path provided in `llms.txt` to understand exact parameters and return types before writing code.
11
+
12
+ This guide is designed to help agents select the correct API without unnecessary token waste and move quickly to the implementation phase.
@@ -0,0 +1,9 @@
1
+ # {{title}} APIs
2
+ {{#groupedOperations}}
3
+ ## {{tag}}
4
+ |Intent|API|Path|Source File|
5
+ |:---|:---|:---|:---|
6
+ {{#ops}}
7
+ |{{{description}}}|`{{{operationId}}}`|`{{{httpMethod}}} {{{path}}}`|`{{{sourceFile}}}`|
8
+ {{/ops}}
9
+ {{/groupedOperations}}