rapidoc 12.0.0 → 12.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.
Files changed (77) hide show
  1. package/README.md +127 -25
  2. package/dist/rapidoc-flow-min.js +580 -0
  3. package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
  4. package/dist/rapidoc-min.js +541 -1309
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +21 -18
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +43 -0
  10. package/src/components/api-response.ts +9 -4
  11. package/src/components/schema-base-element.ts +18 -4
  12. package/src/components/schema-table.ts +58 -13
  13. package/src/components/schema-tree.ts +7 -0
  14. package/src/components/tag-input.ts +5 -9
  15. package/src/flow.js +3 -0
  16. package/src/rapi-flow.ts +1178 -0
  17. package/src/rapidoc-mini.ts +27 -6
  18. package/src/rapidoc.ts +108 -62
  19. package/src/styles/README.md +96 -0
  20. package/src/styles/css-bundle.js +2 -2
  21. package/src/styles/endpoint-styles.js +6 -1
  22. package/src/styles/flow-styles.js +248 -0
  23. package/src/styles/nav-styles.js +39 -15
  24. package/src/styles/schema-styles.js +13 -5
  25. package/src/styles/theme-tokens.js +2 -2
  26. package/src/templates/README.md +89 -0
  27. package/src/templates/api-response-template.js +14 -10
  28. package/src/templates/callback-template.js +1 -1
  29. package/src/templates/downloads-template.js +12 -27
  30. package/src/templates/endpoint-template.js +15 -24
  31. package/src/templates/expanded-endpoint-template.js +8 -4
  32. package/src/templates/focused-endpoint-template.js +4 -3
  33. package/src/templates/navbar-template.js +20 -11
  34. package/src/templates/request-body-template.js +29 -7
  35. package/src/templates/request-params-template.js +15 -4
  36. package/src/templates/security-scheme-template.js +11 -2
  37. package/src/templates/server-template.js +3 -2
  38. package/src/utils/README.md +146 -0
  39. package/src/utils/arazzo/README.md +119 -0
  40. package/src/utils/arazzo/components.js +15 -0
  41. package/src/utils/arazzo/graph.js +269 -0
  42. package/src/utils/arazzo/index.js +52 -0
  43. package/src/utils/arazzo/operations.js +106 -0
  44. package/src/utils/arazzo/parse.js +196 -0
  45. package/src/utils/arazzo/refs.js +40 -0
  46. package/src/utils/arazzo/svg.js +707 -0
  47. package/src/utils/curl-utils.js +5 -2
  48. package/src/utils/highlighter.js +3 -0
  49. package/src/utils/mcp/README.md +155 -0
  50. package/src/utils/mcp/common.js +45 -346
  51. package/src/utils/mcp/manifest.js +382 -0
  52. package/src/utils/mcp/project/server.ts +53 -0
  53. package/src/utils/mcp/project.js +88 -0
  54. package/src/utils/mcp/runtime.js +289 -0
  55. package/src/utils/mcp/schema.js +98 -0
  56. package/src/utils/mcp/workflow-runtime.js +374 -0
  57. package/src/utils/mcp/workflows.js +240 -0
  58. package/src/utils/mcp/zip.js +102 -0
  59. package/src/utils/mcp-generator.js +29 -125
  60. package/src/utils/mock-interceptor.js +134 -85
  61. package/src/utils/request-utils.js +61 -0
  62. package/src/utils/schema-utils.js +33 -6
  63. package/src/utils/slugger.js +5 -7
  64. package/src/utils/spec-parser.js +190 -164
  65. package/src/utils/url-utils.js +36 -1
  66. package/dist/rapidoc-static-min.js +0 -13542
  67. package/src/static/index.d.ts +0 -149
  68. package/src/static/index.js +0 -62
  69. package/src/static/navigation.js +0 -497
  70. package/src/static/render.js +0 -490
  71. package/src/static/serializer.js +0 -216
  72. package/src/utils/mcp/generators/csharp.js +0 -242
  73. package/src/utils/mcp/generators/go.js +0 -298
  74. package/src/utils/mcp/generators/java.js +0 -330
  75. package/src/utils/mcp/generators/python.js +0 -248
  76. package/src/utils/mcp/generators/rust.js +0 -281
  77. package/src/utils/mcp/generators/typescript.js +0 -290
@@ -1,216 +0,0 @@
1
- /**
2
- * Isomorphic HTML serializer for Lit TemplateResult in Node.js, Bun, and SSR/SSG runtimes.
3
- *
4
- * Flattens declarative Lit templates into pure, static HTML strings without requiring
5
- * a browser window, headless browser, or full DOM emulator (JSDOM/HappyDOM).
6
- */
7
-
8
- // Minimal polyfill for customElements registry in pure Node/Bun environments
9
- if (typeof globalThis.customElements === "undefined") {
10
- globalThis.customElements = {
11
- define: () => {},
12
- get: () => undefined,
13
- whenDefined: () => Promise.resolve(),
14
- };
15
- }
16
-
17
- /**
18
- * Escapes characters for HTML text nodes and attribute values to prevent injection.
19
- * @param {string} str
20
- * @returns {string}
21
- */
22
- export function escapeHtml(str) {
23
- if (typeof str !== "string") return String(str ?? "");
24
- return str
25
- .replace(/&/g, "&")
26
- .replace(/</g, "&lt;")
27
- .replace(/>/g, "&gt;")
28
- .replace(/"/g, "&quot;")
29
- .replace(/'/g, "&#39;");
30
- }
31
-
32
- /**
33
- * Escapes characters for HTML text nodes (quotes are left as is, since they are inert in text).
34
- * @param {string} str
35
- * @returns {string}
36
- */
37
- function escapeText(str) {
38
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
39
- }
40
-
41
- /**
42
- * Directives whose value is inserted as markup, matching Lit's behavior in the browser.
43
- * Matched by the static `directiveName` (survives minification, unlike class names).
44
- */
45
- const RAW_MARKUP_DIRECTIVES = new Set(["unsafeHTML", "unsafeSVG"]);
46
-
47
- /**
48
- * Recursively serializes a Lit TemplateResult, directive, CSSResult, primitive, or array
49
- * into a pure static HTML string.
50
- *
51
- * - Event listeners (`@click="..."`) are stripped.
52
- * - Non-attribute property bindings (`.prop="..."`) are stripped.
53
- * - Boolean attributes (`?disabled="..."`) are converted to standard HTML attributes if truthy, or omitted if falsy.
54
- * - Strings from plain bindings are HTML-escaped, as Lit does in the browser (`&<>` in text,
55
- * plus quotes in attribute values). Unquoted attribute bindings (`value=${x}`) are quoted.
56
- * - Lit directives (`unsafeHTML`, `ifDefined`, `guard`) are evaluated transparently; only
57
- * `unsafeHTML` / `unsafeSVG` values are inserted as raw markup. An attribute whose whole value is
58
- * `nothing` (e.g. `ifDefined(undefined)`) is omitted.
59
- * - CSSResult instances (`css\`...\``) are resolved via their `.cssText` property.
60
- *
61
- * @param {any} result - The value or TemplateResult to serialize.
62
- * @param {Object} [options]
63
- * @param {boolean} [options.stripComments=false] - Whether to strip HTML comments.
64
- * @returns {string} Serialized HTML string.
65
- */
66
- export function renderTemplateToString(result, options = {}) {
67
- return serializeValue(result, options, false);
68
- }
69
-
70
- /**
71
- * @param {any} result
72
- * @param {Object} options
73
- * @param {boolean} inAttribute - Whether the value is bound inside an attribute value.
74
- * @returns {string}
75
- */
76
- function serializeValue(result, options, inAttribute) {
77
- if (result == null || result === false) {
78
- return "";
79
- }
80
-
81
- if (typeof result === "string") {
82
- return inAttribute ? escapeHtml(result) : escapeText(result);
83
- }
84
-
85
- if (typeof result === "number" || typeof result === "bigint") {
86
- return String(result);
87
- }
88
-
89
- if (typeof result === "symbol") {
90
- return "";
91
- }
92
-
93
- if (Array.isArray(result)) {
94
- return result.map((item) => serializeValue(item, options, inAttribute)).join("");
95
- }
96
-
97
- // Handle Lit CSSResult
98
- if (typeof result === "object" && result.cssText) {
99
- return result.cssText;
100
- }
101
-
102
- // Handle Lit directives (unsafeHTML, guard, live)
103
- if (typeof result === "object" && result._$litDirective$) {
104
- // unsafeHTML(str) / unsafeSVG(str): inserted as markup, never escaped
105
- if (RAW_MARKUP_DIRECTIVES.has(result._$litDirective$.directiveName)) {
106
- const markup = result.values?.[0];
107
- return typeof markup === "string" ? markup : serializeValue(markup, options, inAttribute);
108
- }
109
- // guard([deps], () => fn)
110
- if (result.values && typeof result.values[1] === "function") {
111
- return serializeValue(result.values[1](), options, inAttribute);
112
- }
113
- // live(value) and other value-passing directives
114
- if (result.values && result.values.length > 0) {
115
- return serializeValue(result.values[0], options, inAttribute);
116
- }
117
- return "";
118
- }
119
-
120
- // Handle Lit TemplateResult
121
- if (typeof result === "object" && "strings" in result && "values" in result) {
122
- let out = "";
123
- let skipNextLeadingQuote = false;
124
- // Whether the parser position is inside a start tag (between `<` and `>`), so a binding is an attribute value
125
- let inTag = false;
126
-
127
- for (let i = 0; i < result.strings.length; i++) {
128
- let str = result.strings[i];
129
- const lastOpen = str.lastIndexOf("<");
130
- const lastClose = str.lastIndexOf(">");
131
- if (lastOpen !== lastClose) {
132
- inTag = lastOpen > lastClose;
133
- }
134
-
135
- // Strip matching quote from previous expression if skipped
136
- if (skipNextLeadingQuote) {
137
- if (str.startsWith('"') || str.startsWith("'")) {
138
- str = str.slice(1);
139
- }
140
- skipNextLeadingQuote = false;
141
- }
142
-
143
- if (i < result.values.length) {
144
- const val = result.values[i];
145
-
146
- // 1. Event bindings: e.g. @click="..." or @click=...
147
- const eventMatch = str.match(/@([\w.-]+)=(["']?)$/);
148
- if (eventMatch) {
149
- str = str.slice(0, eventMatch.index).trimEnd();
150
- if (eventMatch[2]) {
151
- skipNextLeadingQuote = true;
152
- }
153
- out += str;
154
- continue;
155
- }
156
-
157
- // 2. Property bindings: e.g. .config="..." or .config=...
158
- const propMatch = str.match(/\.([\w.-]+)=(["']?)$/);
159
- if (propMatch) {
160
- str = str.slice(0, propMatch.index).trimEnd();
161
- if (propMatch[2]) {
162
- skipNextLeadingQuote = true;
163
- }
164
- out += str;
165
- continue;
166
- }
167
-
168
- // 3. Boolean attributes: e.g. ?disabled="..." or ?disabled=...
169
- const boolMatch = str.match(/\?([\w.-]+)=(["']?)$/);
170
- if (boolMatch) {
171
- const attrName = boolMatch[1];
172
- const hasQuote = Boolean(boolMatch[2]);
173
- str = str.slice(0, boolMatch.index).trimEnd();
174
- if (hasQuote) {
175
- skipNextLeadingQuote = true;
176
- }
177
- const isTruthy = Boolean(val && val !== "false");
178
- out += str + (isTruthy ? ` ${attrName}` : "");
179
- continue;
180
- }
181
-
182
- // 4. Regular attribute or text node
183
- const attrMatch = inTag ? str.match(/\s[^\s"'>/=]+=(["']?)$/) : null;
184
- if (
185
- attrMatch &&
186
- typeof val === "symbol" &&
187
- (!attrMatch[1] || result.strings[i + 1]?.startsWith(attrMatch[1]))
188
- ) {
189
- // `nothing` (e.g. ifDefined(undefined)) as the whole attribute value removes the attribute, as Lit does
190
- out += str.slice(0, attrMatch.index);
191
- skipNextLeadingQuote = Boolean(attrMatch[1]);
192
- continue;
193
- }
194
- if (attrMatch && !attrMatch[1]) {
195
- // Unquoted attribute binding: quote it so an escaped value cannot break out into new attributes
196
- out += `${str}"${serializeValue(val, options, true)}"`;
197
- } else {
198
- out += str;
199
- out += serializeValue(val, options, inTag);
200
- }
201
- } else {
202
- out += str;
203
- }
204
- }
205
-
206
- if (options.stripComments) {
207
- out = out.replace(/<!--[\s\S]*?-->/g, "");
208
- }
209
-
210
- return out;
211
- }
212
-
213
- return "";
214
- }
215
-
216
- export default renderTemplateToString;
@@ -1,242 +0,0 @@
1
- /**
2
- * Single-file C# (.NET) MCP Server Generator
3
- * Uses ModelContextProtocol.NET and Semantic Kernel conventions.
4
- */
5
- import { toPascalCase, toCamelCase, toSnakeCase, estimateTokenCost } from "../common.js";
6
-
7
- export function generateCSharpMcpServer(metadata, tools, resources, workflows, options = {}) {
8
- const serverName = toPascalCase(options.serverName || metadata.title || "RapiDocMcpServer");
9
- const budget = estimateTokenCost(tools, resources, workflows);
10
- const defaultBaseUrl = metadata.servers?.[0]?.url || "http://localhost:3000";
11
-
12
- const toolMethods = tools
13
- .map((t) => {
14
- const csharpArgs = t.parameters.map((p) => {
15
- let csharpType = "string";
16
- if (p.type === "integer") csharpType = "int";
17
- else if (p.type === "number") csharpType = "double";
18
- else if (p.type === "boolean") csharpType = "bool";
19
- else if (p.type === "array") csharpType = "IEnumerable<string>";
20
- else if (p.type === "object") csharpType = "Dictionary<string, object>";
21
- const defaultVal = p.required ? "" : " = default";
22
- return `[Description(${JSON.stringify(p.description || "")})] ${csharpType} ${toCamelCase(p.name)}${defaultVal}`;
23
- });
24
-
25
- csharpArgs.push(
26
- '[Description("Optional Bearer API key override for multi-tenant sessions")] string apiKey = default',
27
- );
28
-
29
- const pathParams = t.parameters.filter((p) => p.in === "path");
30
- const safePathCode =
31
- pathParams.length > 0
32
- ? pathParams
33
- .map(
34
- (p) =>
35
- ` var safe${toPascalCase(p.name)} = Uri.EscapeDataString(${toCamelCase(p.name)}?.ToString() ?? "");`,
36
- )
37
- .join("\n") + "\n"
38
- : "";
39
-
40
- const pathSub = t.path.replace(/\{([^}]+)\}/g, (match, p1) => `{safe${toPascalCase(p1)}}`);
41
- const queryParams = t.parameters.filter((p) => p.in === "query");
42
- const bodyParams = t.parameters.filter((p) => p.in === "body");
43
- const hasBody = ["POST", "PUT", "PATCH"].includes(t.method) && bodyParams.length > 0;
44
-
45
- const queryCode =
46
- queryParams.length > 0
47
- ? ` var queryParams = new List<string>();
48
- ${queryParams.map((p) => ` if (${toCamelCase(p.name)} != default) queryParams.Add("${p.name}=" + Uri.EscapeDataString(${toCamelCase(p.name)}.ToString()));`).join("\n")}
49
- var queryString = queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : "";\n`
50
- : "";
51
-
52
- const urlExpr = queryParams.length > 0 ? `$"${pathSub}" + queryString` : `$"${pathSub}"`;
53
-
54
- const bodyCode = hasBody
55
- ? ` var bodyPayload = new Dictionary<string, object>();
56
- ${bodyParams.map((p) => ` if (${toCamelCase(p.name)} != default) bodyPayload["${p.name}"] = ${toCamelCase(p.name)};`).join("\n")}
57
- if (bodyPayload.Count > 0)
58
- {
59
- request.Content = new StringContent(JsonSerializer.Serialize(bodyPayload), System.Text.Encoding.UTF8, "application/json");
60
- }\n`
61
- : "";
62
-
63
- return `
64
- [McpTool("${t.name}", ${JSON.stringify(t.description)})]
65
- public static async Task<string> ${toPascalCase(t.name)}(${csharpArgs.join(", ")})
66
- {
67
- using var client = new HttpClient { BaseAddress = new Uri(BaseUrl.TrimEnd('/') + "/") };
68
- var resolvedKey = !string.IsNullOrEmpty(apiKey) ? apiKey : ApiKey;
69
- if (!string.IsNullOrEmpty(resolvedKey))
70
- {
71
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolvedKey);
72
- }
73
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
74
- ${safePathCode}${queryCode} var request = new HttpRequestMessage(HttpMethod.${toPascalCase(t.method)}, ${urlExpr});
75
- ${t.parameters
76
- .filter((p) => p.in === "header")
77
- .map(
78
- (p) =>
79
- ` if (!string.IsNullOrEmpty(${toCamelCase(p.name)})) request.Headers.TryAddWithoutValidation("${p.name}", ${toCamelCase(p.name)});`,
80
- )
81
- .join("\n")}
82
- ${bodyCode} var response = await client.SendAsync(request);
83
- return await response.Content.ReadAsStringAsync();
84
- }`;
85
- })
86
- .join("\n");
87
-
88
- const resourceMethods = resources
89
- .map((r) => {
90
- return `
91
- [McpResource("${r.uri}", "${r.name}", ${JSON.stringify(r.description)})]
92
- public static async Task<string> Get${toPascalCase(r.name)}(string uri = "")
93
- {
94
- using var client = new HttpClient { BaseAddress = new Uri(BaseUrl.TrimEnd('/') + "/") };
95
- var resolvedKey = ApiKey;
96
- var outboundQuery = HttpUtility.ParseQueryString(string.Empty);
97
-
98
- if (!string.IsNullOrEmpty(uri) && uri.Contains("?"))
99
- {
100
- var parsedUri = new Uri(uri);
101
- var incomingParams = HttpUtility.ParseQueryString(parsedUri.Query);
102
-
103
- var dynamicKey = incomingParams["apiKey"] ?? incomingParams["api_key"];
104
- if (!string.IsNullOrEmpty(dynamicKey)) resolvedKey = dynamicKey;
105
-
106
- foreach (string key in incomingParams)
107
- {
108
- if (key != null && !key.Equals("apikey", StringComparison.OrdinalIgnoreCase) && !key.Equals("api_key", StringComparison.OrdinalIgnoreCase))
109
- {
110
- outboundQuery[key] = incomingParams[key];
111
- }
112
- }
113
- }
114
-
115
- if (!string.IsNullOrEmpty(resolvedKey)) client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolvedKey);
116
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("${r.mimeType || "application/json"}"));
117
-
118
- var targetPath = "${r.path}" + (outboundQuery.Count > 0 ? "?" + outboundQuery.ToString() : "");
119
- return await client.GetStringAsync(targetPath.TrimStart('/'));
120
- }`;
121
- })
122
- .join("\n");
123
-
124
- const workflowMethods = workflows
125
- .map((w) => {
126
- const paramKeys = w.parameters ? Object.keys(w.parameters) : [];
127
- const args = paramKeys
128
- .map(
129
- (k) =>
130
- `[Description(${JSON.stringify(w.parameters[k].description || "")})] string ${toCamelCase(k)}`,
131
- )
132
- .concat([
133
- '[Description("Optional Bearer API key override for multi-tenant sessions")] string apiKey = default',
134
- ])
135
- .join(", ");
136
- const argKey = paramKeys[0] || "id";
137
-
138
- return `
139
- [McpTool("${w.name}", ${JSON.stringify(w.description || "Composite workflow")})]
140
- public static async Task<string> ${toPascalCase(w.name)}(${args})
141
- {
142
- using var client = new HttpClient { BaseAddress = new Uri(BaseUrl.TrimEnd('/') + "/") };
143
- var resolvedKey = !string.IsNullOrEmpty(apiKey) ? apiKey : ApiKey;
144
- if (!string.IsNullOrEmpty(resolvedKey)) client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", resolvedKey);
145
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
146
-
147
- var results = new Dictionary<string, object>();
148
- ${(w.steps || [])
149
- .map(
150
- (s, idx) => ` // Step ${idx + 1}: ${s.call}
151
- var resp_${idx} = await client.GetStringAsync($"api/{Uri.EscapeDataString(${toCamelCase(argKey)} ?? "")}");
152
- results["${s.call}"] = JsonSerializer.Deserialize<object>(resp_${idx});`,
153
- )
154
- .join("\n")}
155
-
156
- return JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true });
157
- }`;
158
- })
159
- .join("\n");
160
-
161
- return `/*
162
- Auto-generated by RapiDoc Curated MCP Generator
163
- Spec: ${metadata.title} (v${metadata.version})
164
-
165
- [MULTI-TENANCY & AUTHENTICATION]
166
- - Local Stdio Mode: Claude Desktop, Cursor, and local agents use API_KEY from environment.
167
- - Multi-Tenant / Agent Mode: When running in a shared service, the calling AI agent can pass
168
- per-session credentials directly in the \`apiKey\` parameter of any tool.
169
-
170
- [TOKEN BUDGET DIAGNOSTIC]
171
- - Total Tools: ${budget.totalTools}
172
- - Total Resources: ${budget.totalResources}
173
- - Total Workflows: ${budget.totalWorkflows}
174
- - Estimated Prompt Context Overhead: ~${budget.estimatedTokens.toLocaleString()} tokens/turn
175
- ${budget.warnings.map((w) => `- ${w}`).join("\n")}
176
-
177
- [HOW TO RUN]
178
- 1. dotnet restore
179
- 2. dotnet run
180
- */
181
-
182
- using System;
183
- using System.Collections.Generic;
184
- using System.ComponentModel;
185
- using System.Linq;
186
- using System.Net.Http;
187
- using System.Net.Http.Headers;
188
- using System.Text.Json;
189
- using System.Threading.Tasks;
190
- using System.Web;
191
- using ModelContextProtocol.NET.Server;
192
-
193
- namespace ${serverName};
194
-
195
- public class Program
196
- {
197
- private static readonly string ApiKey = Environment.GetEnvironmentVariable("API_KEY") ?? "";
198
- private static readonly string BaseUrl = Environment.GetEnvironmentVariable("API_BASE_URL") ?? "${defaultBaseUrl}";
199
-
200
- public static async Task Main(string[] args)
201
- {
202
- var server = new McpServer(new McpServerOptions
203
- {
204
- ServerInfo = new() { Name = "${toSnakeCase(serverName)}", Version = "${metadata.version || "1.0.0"}" }
205
- });
206
-
207
- server.RegisterTools<Program>();
208
- server.RegisterResources<Program>();
209
-
210
- await server.StartAsync(Console.OpenStandardInput(), Console.OpenStandardOutput());
211
- }
212
-
213
- ${resourceMethods}
214
- ${toolMethods}
215
- ${workflowMethods}
216
- }
217
- `;
218
- }
219
-
220
- export function generateCSharpFiles(serverCode, metadata) {
221
- const csproj = `<Project Sdk="Microsoft.NET.Sdk">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n </PropertyGroup>\n <ItemGroup>\n <PackageReference Include="ModelContextProtocol.NET.Server" Version="1.0.0" />\n </ItemGroup>\n</Project>\n`;
222
- const readme = `# ${metadata.title} - MCP Server (C# / .NET)
223
-
224
- Auto-generated by RapiDoc Curated MCP Generator.
225
-
226
- ### Prerequisites
227
- - .NET 8.0 SDK or newer
228
-
229
- ### Building & Running
230
- \`\`\`bash
231
- dotnet restore
232
- export API_KEY="your-api-key"
233
- dotnet run
234
- \`\`\`
235
- `;
236
-
237
- return {
238
- "Program.cs": serverCode,
239
- "Server.csproj": csproj,
240
- "README.md": readme,
241
- };
242
- }