@brainfish-ai/devdoc 0.1.21
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 +33 -0
- package/README.md +415 -0
- package/bin/devdoc.js +13 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/check.d.ts +1 -0
- package/dist/cli/commands/check.js +143 -0
- package/dist/cli/commands/create.d.ts +24 -0
- package/dist/cli/commands/create.js +387 -0
- package/dist/cli/commands/deploy.d.ts +9 -0
- package/dist/cli/commands/deploy.js +433 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +139 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.js +238 -0
- package/dist/cli/commands/keys.d.ts +12 -0
- package/dist/cli/commands/keys.js +165 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.js +56 -0
- package/dist/cli/commands/upload.d.ts +13 -0
- package/dist/cli/commands/upload.js +238 -0
- package/dist/cli/commands/whoami.d.ts +8 -0
- package/dist/cli/commands/whoami.js +91 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/config/index.d.ts +80 -0
- package/dist/config/index.js +133 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.js +50 -0
- package/package.json +51 -0
- package/renderer/app/api/assets/[...path]/route.ts +123 -0
- package/renderer/app/api/assets/route.ts +124 -0
- package/renderer/app/api/assets/upload/route.ts +177 -0
- package/renderer/app/api/auth-schemes/route.ts +77 -0
- package/renderer/app/api/chat/route.ts +858 -0
- package/renderer/app/api/codegen/route.ts +72 -0
- package/renderer/app/api/collections/route.ts +1016 -0
- package/renderer/app/api/debug/route.ts +53 -0
- package/renderer/app/api/deploy/route.ts +234 -0
- package/renderer/app/api/device/route.ts +42 -0
- package/renderer/app/api/docs/route.ts +187 -0
- package/renderer/app/api/keys/regenerate/route.ts +80 -0
- package/renderer/app/api/openapi-spec/route.ts +151 -0
- package/renderer/app/api/projects/[slug]/route.ts +153 -0
- package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
- package/renderer/app/api/projects/register/route.ts +152 -0
- package/renderer/app/api/proxy/route.ts +149 -0
- package/renderer/app/api/proxy-stream/route.ts +168 -0
- package/renderer/app/api/redirects/route.ts +47 -0
- package/renderer/app/api/schema/route.ts +65 -0
- package/renderer/app/api/subdomains/check/route.ts +172 -0
- package/renderer/app/api/suggestions/route.ts +144 -0
- package/renderer/app/favicon.ico +0 -0
- package/renderer/app/globals.css +1103 -0
- package/renderer/app/layout.tsx +47 -0
- package/renderer/app/llms-full.txt/route.ts +346 -0
- package/renderer/app/llms.txt/route.ts +279 -0
- package/renderer/app/page.tsx +14 -0
- package/renderer/app/robots.txt/route.ts +84 -0
- package/renderer/app/sitemap.xml/route.ts +199 -0
- package/renderer/components/docs/index.ts +12 -0
- package/renderer/components/docs/mdx/accordion.tsx +169 -0
- package/renderer/components/docs/mdx/badge.tsx +132 -0
- package/renderer/components/docs/mdx/callouts.tsx +154 -0
- package/renderer/components/docs/mdx/cards.tsx +213 -0
- package/renderer/components/docs/mdx/changelog.tsx +120 -0
- package/renderer/components/docs/mdx/code-block.tsx +186 -0
- package/renderer/components/docs/mdx/code-group.tsx +421 -0
- package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
- package/renderer/components/docs/mdx/frame.tsx +112 -0
- package/renderer/components/docs/mdx/highlight.tsx +151 -0
- package/renderer/components/docs/mdx/iframe.tsx +134 -0
- package/renderer/components/docs/mdx/image.tsx +235 -0
- package/renderer/components/docs/mdx/index.ts +204 -0
- package/renderer/components/docs/mdx/mermaid.tsx +240 -0
- package/renderer/components/docs/mdx/param-field.tsx +200 -0
- package/renderer/components/docs/mdx/steps.tsx +113 -0
- package/renderer/components/docs/mdx/tabs.tsx +86 -0
- package/renderer/components/docs/mdx-renderer.tsx +100 -0
- package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
- package/renderer/components/docs/navigation/index.ts +8 -0
- package/renderer/components/docs/navigation/page-nav.tsx +64 -0
- package/renderer/components/docs/navigation/sidebar.tsx +515 -0
- package/renderer/components/docs/navigation/toc.tsx +113 -0
- package/renderer/components/docs/notice.tsx +105 -0
- package/renderer/components/docs-header.tsx +274 -0
- package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
- package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
- package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
- package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
- package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
- package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
- package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
- package/renderer/components/docs-viewer/agent/index.tsx +6 -0
- package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
- package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
- package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
- package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
- package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
- package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
- package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
- package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
- package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
- package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
- package/renderer/components/docs-viewer/content/index.tsx +29 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
- package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
- package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
- package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
- package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
- package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
- package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
- package/renderer/components/docs-viewer/index.tsx +1466 -0
- package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
- package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
- package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
- package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
- package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
- package/renderer/components/docs-viewer/playground/index.tsx +671 -0
- package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
- package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
- package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
- package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
- package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
- package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
- package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
- package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
- package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
- package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
- package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
- package/renderer/components/docs-viewer/search/index.ts +2 -0
- package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
- package/renderer/components/docs-viewer/search/use-search.ts +117 -0
- package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
- package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
- package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
- package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
- package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
- package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
- package/renderer/components/theme-provider.tsx +11 -0
- package/renderer/components/theme-toggle.tsx +76 -0
- package/renderer/components/ui/badge.tsx +46 -0
- package/renderer/components/ui/button.tsx +59 -0
- package/renderer/components/ui/dialog.tsx +118 -0
- package/renderer/components/ui/dropdown-menu.tsx +257 -0
- package/renderer/components/ui/input.tsx +21 -0
- package/renderer/components/ui/label.tsx +24 -0
- package/renderer/components/ui/navigation-menu.tsx +168 -0
- package/renderer/components/ui/select.tsx +190 -0
- package/renderer/components/ui/spinner.tsx +114 -0
- package/renderer/components/ui/tabs.tsx +66 -0
- package/renderer/components/ui/tooltip.tsx +61 -0
- package/renderer/hooks/use-code-copy.ts +88 -0
- package/renderer/hooks/use-openapi-title.ts +44 -0
- package/renderer/lib/api-docs/agent/index.ts +6 -0
- package/renderer/lib/api-docs/agent/indexer.ts +323 -0
- package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
- package/renderer/lib/api-docs/agent/types.ts +116 -0
- package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
- package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
- package/renderer/lib/api-docs/auth/crypto.ts +89 -0
- package/renderer/lib/api-docs/auth/index.ts +4 -0
- package/renderer/lib/api-docs/code-editor/db.ts +164 -0
- package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
- package/renderer/lib/api-docs/code-editor/index.ts +6 -0
- package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
- package/renderer/lib/api-docs/code-editor/types.ts +105 -0
- package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
- package/renderer/lib/api-docs/codegen/har.ts +251 -0
- package/renderer/lib/api-docs/codegen/index.ts +159 -0
- package/renderer/lib/api-docs/factories.ts +151 -0
- package/renderer/lib/api-docs/index.ts +17 -0
- package/renderer/lib/api-docs/mobile-context.tsx +112 -0
- package/renderer/lib/api-docs/navigation-context.tsx +88 -0
- package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
- package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
- package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
- package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
- package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
- package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
- package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
- package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
- package/renderer/lib/api-docs/playground/context.tsx +107 -0
- package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
- package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
- package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
- package/renderer/lib/api-docs/playground/types.ts +35 -0
- package/renderer/lib/api-docs/types.ts +269 -0
- package/renderer/lib/api-docs/utils.ts +311 -0
- package/renderer/lib/cache.ts +193 -0
- package/renderer/lib/docs/config/index.ts +29 -0
- package/renderer/lib/docs/config/loader.ts +142 -0
- package/renderer/lib/docs/config/schema.ts +298 -0
- package/renderer/lib/docs/index.ts +12 -0
- package/renderer/lib/docs/mdx/compiler.ts +176 -0
- package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
- package/renderer/lib/docs/mdx/index.ts +26 -0
- package/renderer/lib/docs/navigation/generator.ts +348 -0
- package/renderer/lib/docs/navigation/index.ts +12 -0
- package/renderer/lib/docs/navigation/types.ts +123 -0
- package/renderer/lib/docs-navigation-context.tsx +80 -0
- package/renderer/lib/multi-tenant/context.ts +105 -0
- package/renderer/lib/storage/blob.ts +845 -0
- package/renderer/lib/utils.ts +6 -0
- package/renderer/next.config.ts +76 -0
- package/renderer/package.json +66 -0
- package/renderer/postcss.config.mjs +5 -0
- package/renderer/public/assets/images/screenshot.png +0 -0
- package/renderer/public/assets/logo/dark.svg +9 -0
- package/renderer/public/assets/logo/light.svg +9 -0
- package/renderer/public/assets/logo.svg +9 -0
- package/renderer/public/file.svg +1 -0
- package/renderer/public/globe.svg +1 -0
- package/renderer/public/icon.png +0 -0
- package/renderer/public/logo.svg +9 -0
- package/renderer/public/window.svg +1 -0
- package/renderer/tsconfig.json +28 -0
- package/templates/basic/README.md +139 -0
- package/templates/basic/assets/favicon.svg +4 -0
- package/templates/basic/assets/logo.svg +9 -0
- package/templates/basic/docs.json +47 -0
- package/templates/basic/guides/configuration.mdx +149 -0
- package/templates/basic/guides/overview.mdx +96 -0
- package/templates/basic/index.mdx +39 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/quickstart.mdx +92 -0
- package/templates/basic/vercel.json +6 -0
- package/templates/graphql/README.md +139 -0
- package/templates/graphql/api-reference/schema.graphql +305 -0
- package/templates/graphql/assets/favicon.svg +4 -0
- package/templates/graphql/assets/logo.svg +9 -0
- package/templates/graphql/docs.json +54 -0
- package/templates/graphql/guides/configuration.mdx +149 -0
- package/templates/graphql/guides/overview.mdx +96 -0
- package/templates/graphql/index.mdx +39 -0
- package/templates/graphql/package.json +14 -0
- package/templates/graphql/quickstart.mdx +92 -0
- package/templates/graphql/vercel.json +6 -0
- package/templates/openapi/README.md +139 -0
- package/templates/openapi/api-reference/openapi.json +419 -0
- package/templates/openapi/assets/favicon.svg +4 -0
- package/templates/openapi/assets/logo.svg +9 -0
- package/templates/openapi/docs.json +61 -0
- package/templates/openapi/guides/configuration.mdx +149 -0
- package/templates/openapi/guides/overview.mdx +96 -0
- package/templates/openapi/index.mdx +39 -0
- package/templates/openapi/package.json +14 -0
- package/templates/openapi/quickstart.mdx +92 -0
- package/templates/openapi/vercel.json +6 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection Indexer
|
|
3
|
+
* Builds a searchable index of all API endpoints for the AI agent
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BrainfishCollection, BrainfishRESTRequest, FormDataKeyValue } from '../types'
|
|
7
|
+
import type { EndpointIndex } from './types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Find a request by ID in the collection (recursive)
|
|
11
|
+
*/
|
|
12
|
+
export function findRequestById(
|
|
13
|
+
collection: BrainfishCollection,
|
|
14
|
+
id: string
|
|
15
|
+
): BrainfishRESTRequest | null {
|
|
16
|
+
// Check direct requests
|
|
17
|
+
const found = collection.requests.find(r => r.id === id)
|
|
18
|
+
if (found) return found
|
|
19
|
+
|
|
20
|
+
// Check folders recursively
|
|
21
|
+
for (const folder of collection.folders) {
|
|
22
|
+
const request = findRequestById(folder, id)
|
|
23
|
+
if (request) return request
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format full request details for AI context
|
|
30
|
+
* Returns a structured object with all the information the AI needs
|
|
31
|
+
*/
|
|
32
|
+
export function formatRequestForAI(request: BrainfishRESTRequest): {
|
|
33
|
+
id: string
|
|
34
|
+
name: string
|
|
35
|
+
method: string
|
|
36
|
+
path: string
|
|
37
|
+
description: string | null
|
|
38
|
+
parameters: Array<{ name: string; description: string; required: boolean }>
|
|
39
|
+
headers: Array<{ name: string; description: string; required: boolean }>
|
|
40
|
+
auth: { type: string; details?: Record<string, unknown> }
|
|
41
|
+
body: {
|
|
42
|
+
required: boolean
|
|
43
|
+
contentType: string | null
|
|
44
|
+
schema: string | Array<{ key: string; isFile: boolean }> | null
|
|
45
|
+
example?: string
|
|
46
|
+
}
|
|
47
|
+
responses: Array<{
|
|
48
|
+
code: number
|
|
49
|
+
status: string
|
|
50
|
+
description?: string
|
|
51
|
+
example?: string
|
|
52
|
+
}>
|
|
53
|
+
} {
|
|
54
|
+
// Format parameters
|
|
55
|
+
const parameters = request.params.map(p => ({
|
|
56
|
+
name: p.key,
|
|
57
|
+
description: p.description || '',
|
|
58
|
+
required: p.active,
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
// Format headers
|
|
62
|
+
const headers = request.headers
|
|
63
|
+
.filter(h => !['Content-Type', 'Authorization'].includes(h.key)) // Filter out auto-managed headers
|
|
64
|
+
.map(h => ({
|
|
65
|
+
name: h.key,
|
|
66
|
+
description: h.description || '',
|
|
67
|
+
required: h.active,
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
// Format auth
|
|
71
|
+
const auth: { type: string; details?: Record<string, unknown> } = {
|
|
72
|
+
type: request.auth.authType,
|
|
73
|
+
}
|
|
74
|
+
if (request.auth.authType === 'api-key') {
|
|
75
|
+
auth.details = {
|
|
76
|
+
keyName: request.auth.key,
|
|
77
|
+
location: request.auth.addTo === 'HEADERS' ? 'header' : 'query',
|
|
78
|
+
}
|
|
79
|
+
} else if (request.auth.authType === 'bearer') {
|
|
80
|
+
auth.details = { headerFormat: 'Bearer <token>' }
|
|
81
|
+
} else if (request.auth.authType === 'basic') {
|
|
82
|
+
auth.details = { format: 'Basic base64(username:password)' }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Format body
|
|
86
|
+
const body: {
|
|
87
|
+
required: boolean
|
|
88
|
+
contentType: string | null
|
|
89
|
+
schema: string | Array<{ key: string; isFile: boolean }> | null
|
|
90
|
+
example?: string
|
|
91
|
+
} = {
|
|
92
|
+
required: request.body.body !== null,
|
|
93
|
+
contentType: request.body.contentType,
|
|
94
|
+
schema: null,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (request.body.body !== null) {
|
|
98
|
+
if (Array.isArray(request.body.body)) {
|
|
99
|
+
// Form data
|
|
100
|
+
body.schema = (request.body.body as FormDataKeyValue[]).map(item => ({
|
|
101
|
+
key: item.key,
|
|
102
|
+
isFile: item.isFile,
|
|
103
|
+
}))
|
|
104
|
+
} else if (typeof request.body.body === 'string') {
|
|
105
|
+
// JSON or raw body - try to parse and extract schema info
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(request.body.body)
|
|
108
|
+
body.example = request.body.body
|
|
109
|
+
// Extract field names and types from example
|
|
110
|
+
body.schema = JSON.stringify(
|
|
111
|
+
extractSchemaFromExample(parsed),
|
|
112
|
+
null,
|
|
113
|
+
2
|
|
114
|
+
)
|
|
115
|
+
} catch {
|
|
116
|
+
// Raw string body
|
|
117
|
+
body.example = request.body.body.slice(0, 500)
|
|
118
|
+
body.schema = 'raw text'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Format responses
|
|
124
|
+
const responses = Object.entries(request.responses).map(([name, resp]) => ({
|
|
125
|
+
code: resp.code,
|
|
126
|
+
status: resp.status,
|
|
127
|
+
description: name !== resp.status ? name : undefined,
|
|
128
|
+
example: resp.body ? resp.body.slice(0, 500) : undefined,
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
id: request.id,
|
|
133
|
+
name: request.name,
|
|
134
|
+
method: request.method,
|
|
135
|
+
path: request.endpoint,
|
|
136
|
+
description: request.description,
|
|
137
|
+
parameters,
|
|
138
|
+
headers,
|
|
139
|
+
auth,
|
|
140
|
+
body,
|
|
141
|
+
responses,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extract a simple schema from a JSON example
|
|
147
|
+
*/
|
|
148
|
+
function extractSchemaFromExample(obj: unknown, depth = 0): unknown {
|
|
149
|
+
if (depth > 3) return '...' // Prevent deep recursion
|
|
150
|
+
|
|
151
|
+
if (obj === null) return 'null'
|
|
152
|
+
if (typeof obj === 'string') return 'string'
|
|
153
|
+
if (typeof obj === 'number') return 'number'
|
|
154
|
+
if (typeof obj === 'boolean') return 'boolean'
|
|
155
|
+
|
|
156
|
+
if (Array.isArray(obj)) {
|
|
157
|
+
if (obj.length === 0) return '[]'
|
|
158
|
+
return [extractSchemaFromExample(obj[0], depth + 1)]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (typeof obj === 'object') {
|
|
162
|
+
const result: Record<string, unknown> = {}
|
|
163
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
164
|
+
result[key] = extractSchemaFromExample(value, depth + 1)
|
|
165
|
+
}
|
|
166
|
+
return result
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return 'unknown'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Recursively extracts all requests from a collection and its nested folders
|
|
174
|
+
*/
|
|
175
|
+
function extractRequests(
|
|
176
|
+
collection: BrainfishCollection,
|
|
177
|
+
parentTags: string[] = []
|
|
178
|
+
): { request: BrainfishRESTRequest; tags: string[] }[] {
|
|
179
|
+
const results: { request: BrainfishRESTRequest; tags: string[] }[] = []
|
|
180
|
+
|
|
181
|
+
// Add requests from this level
|
|
182
|
+
for (const request of collection.requests) {
|
|
183
|
+
results.push({
|
|
184
|
+
request,
|
|
185
|
+
tags: [...parentTags, collection.name].filter(t => t !== 'root'),
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Recursively process folders
|
|
190
|
+
for (const folder of collection.folders) {
|
|
191
|
+
const folderResults = extractRequests(folder, [...parentTags, collection.name])
|
|
192
|
+
results.push(...folderResults)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return results
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Builds an endpoint index for AI context
|
|
200
|
+
*/
|
|
201
|
+
export function buildEndpointIndex(collection: BrainfishCollection): EndpointIndex[] {
|
|
202
|
+
const allRequests = extractRequests(collection)
|
|
203
|
+
|
|
204
|
+
return allRequests.map(({ request, tags }) => ({
|
|
205
|
+
id: request.id,
|
|
206
|
+
name: request.name,
|
|
207
|
+
method: request.method,
|
|
208
|
+
path: request.endpoint,
|
|
209
|
+
description: request.description,
|
|
210
|
+
parameters: request.params.map(p => p.key),
|
|
211
|
+
hasBody: request.body.body !== null,
|
|
212
|
+
tags: [...new Set([...tags, ...request.tags])].filter(Boolean),
|
|
213
|
+
}))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Formats the endpoint index as a string for the AI system prompt
|
|
218
|
+
*/
|
|
219
|
+
export function formatEndpointsForContext(endpoints: EndpointIndex[]): string {
|
|
220
|
+
return endpoints
|
|
221
|
+
.map((ep, i) => {
|
|
222
|
+
const params = ep.parameters.length > 0
|
|
223
|
+
? `\n Parameters: ${ep.parameters.join(', ')}`
|
|
224
|
+
: ''
|
|
225
|
+
const tags = ep.tags.length > 0
|
|
226
|
+
? `\n Tags: ${ep.tags.join(', ')}`
|
|
227
|
+
: ''
|
|
228
|
+
|
|
229
|
+
return `${i + 1}. [${ep.method}] ${ep.path}
|
|
230
|
+
ID: ${ep.id}
|
|
231
|
+
Name: ${ep.name}${ep.description ? `\n Description: ${ep.description.slice(0, 200)}${ep.description.length > 200 ? '...' : ''}` : ''}${params}${tags}`
|
|
232
|
+
})
|
|
233
|
+
.join('\n\n')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Fuzzy/BM25-like search over endpoints
|
|
238
|
+
*/
|
|
239
|
+
export function searchEndpoints(
|
|
240
|
+
endpoints: EndpointIndex[],
|
|
241
|
+
query: string,
|
|
242
|
+
method?: string
|
|
243
|
+
): EndpointIndex[] {
|
|
244
|
+
// Tokenize query
|
|
245
|
+
const queryTokens = query.toLowerCase().split(/\s+/).filter(t => t.length > 1)
|
|
246
|
+
const queryLower = query.toLowerCase()
|
|
247
|
+
|
|
248
|
+
// Score each endpoint
|
|
249
|
+
const scoredEndpoints = endpoints
|
|
250
|
+
.filter(ep => !method || ep.method === method)
|
|
251
|
+
.map(ep => {
|
|
252
|
+
let score = 0
|
|
253
|
+
const nameLower = ep.name.toLowerCase()
|
|
254
|
+
const pathLower = ep.path.toLowerCase()
|
|
255
|
+
const descLower = (ep.description || '').toLowerCase()
|
|
256
|
+
const tagsLower = ep.tags.map(t => t.toLowerCase())
|
|
257
|
+
const paramsLower = ep.parameters.map(p => p.toLowerCase())
|
|
258
|
+
|
|
259
|
+
// Exact phrase match (highest priority)
|
|
260
|
+
if (nameLower.includes(queryLower)) score += 100
|
|
261
|
+
if (pathLower.includes(queryLower)) score += 80
|
|
262
|
+
if (descLower.includes(queryLower)) score += 60
|
|
263
|
+
|
|
264
|
+
// Token matching
|
|
265
|
+
for (const token of queryTokens) {
|
|
266
|
+
// Name matches (most important)
|
|
267
|
+
if (nameLower === token) score += 50
|
|
268
|
+
else if (nameLower.startsWith(token)) score += 30
|
|
269
|
+
else if (nameLower.includes(token)) score += 20
|
|
270
|
+
|
|
271
|
+
// Path segment matches
|
|
272
|
+
const pathParts = pathLower.split('/').filter(Boolean)
|
|
273
|
+
if (pathParts.some(part => part === token)) score += 40
|
|
274
|
+
if (pathParts.some(part => part.startsWith(token))) score += 25
|
|
275
|
+
if (pathParts.some(part => part.includes(token))) score += 15
|
|
276
|
+
|
|
277
|
+
// Description matches
|
|
278
|
+
if (descLower.includes(token)) score += 15
|
|
279
|
+
|
|
280
|
+
// Tag matches
|
|
281
|
+
if (tagsLower.some(tag => tag === token)) score += 30
|
|
282
|
+
if (tagsLower.some(tag => tag.includes(token))) score += 15
|
|
283
|
+
|
|
284
|
+
// Parameter matches
|
|
285
|
+
if (paramsLower.some(param => param === token)) score += 25
|
|
286
|
+
if (paramsLower.some(param => param.includes(token))) score += 10
|
|
287
|
+
|
|
288
|
+
// HTTP method relevance
|
|
289
|
+
if (ep.method.toLowerCase() === token) score += 20
|
|
290
|
+
|
|
291
|
+
// Fuzzy matching for similar words
|
|
292
|
+
const allWords = [nameLower, ...pathParts, ...tagsLower, ...paramsLower]
|
|
293
|
+
for (const word of allWords) {
|
|
294
|
+
if (word.length > 2 && token.length > 2) {
|
|
295
|
+
const minLen = Math.min(word.length, token.length)
|
|
296
|
+
let matches = 0
|
|
297
|
+
for (let i = 0; i < minLen; i++) {
|
|
298
|
+
if (word[i] === token[i]) matches++
|
|
299
|
+
}
|
|
300
|
+
if (matches / minLen > 0.7) score += 5
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return { ...ep, score }
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// Filter and sort by score
|
|
309
|
+
return scoredEndpoints
|
|
310
|
+
.filter(ep => ep.score > 0)
|
|
311
|
+
.sort((a, b) => b.score - a.score)
|
|
312
|
+
.slice(0, 10)
|
|
313
|
+
.map(ep => ({
|
|
314
|
+
id: ep.id,
|
|
315
|
+
name: ep.name,
|
|
316
|
+
method: ep.method,
|
|
317
|
+
path: ep.path,
|
|
318
|
+
description: ep.description,
|
|
319
|
+
parameters: ep.parameters,
|
|
320
|
+
hasBody: ep.hasBody,
|
|
321
|
+
tags: ep.tags,
|
|
322
|
+
}))
|
|
323
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import type { BrainfishCollection, BrainfishRESTRequest, HTTPMethod } from '../types'
|
|
2
|
+
|
|
3
|
+
interface EndpointSummary {
|
|
4
|
+
name: string
|
|
5
|
+
method: HTTPMethod
|
|
6
|
+
path: string
|
|
7
|
+
description: string | null
|
|
8
|
+
tags: string[]
|
|
9
|
+
hasAuth: boolean
|
|
10
|
+
hasBody: boolean
|
|
11
|
+
paramCount: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ResourceGroup {
|
|
15
|
+
name: string
|
|
16
|
+
endpoints: EndpointSummary[]
|
|
17
|
+
operations: {
|
|
18
|
+
create: number
|
|
19
|
+
read: number
|
|
20
|
+
update: number
|
|
21
|
+
delete: number
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface AuthSummary {
|
|
26
|
+
types: string[]
|
|
27
|
+
primaryMethod: string | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface APISummary {
|
|
31
|
+
name: string
|
|
32
|
+
description: string | null
|
|
33
|
+
baseUrl: string | null
|
|
34
|
+
totalEndpoints: number
|
|
35
|
+
authSummary: AuthSummary
|
|
36
|
+
resourceGroups: ResourceGroup[]
|
|
37
|
+
commonPatterns: string[]
|
|
38
|
+
suggestedUseCases: string[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Recursively collect all requests from a collection
|
|
43
|
+
*/
|
|
44
|
+
function collectAllRequests(collection: BrainfishCollection): BrainfishRESTRequest[] {
|
|
45
|
+
const requests: BrainfishRESTRequest[] = [...collection.requests]
|
|
46
|
+
|
|
47
|
+
for (const folder of collection.folders) {
|
|
48
|
+
requests.push(...collectAllRequests(folder))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return requests
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract base URL from endpoints
|
|
56
|
+
*/
|
|
57
|
+
function extractBaseUrl(requests: BrainfishRESTRequest[]): string | null {
|
|
58
|
+
if (requests.length === 0) return null
|
|
59
|
+
|
|
60
|
+
const firstEndpoint = requests[0].endpoint
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(firstEndpoint)
|
|
63
|
+
return `${url.protocol}//${url.host}`
|
|
64
|
+
} catch {
|
|
65
|
+
// Try to extract from path pattern
|
|
66
|
+
const match = firstEndpoint.match(/^(https?:\/\/[^\/]+)/)
|
|
67
|
+
return match ? match[1] : null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Analyze authentication patterns across endpoints
|
|
73
|
+
*/
|
|
74
|
+
function analyzeAuth(requests: BrainfishRESTRequest[]): AuthSummary {
|
|
75
|
+
const authTypes = new Set<string>()
|
|
76
|
+
const authCounts: Record<string, number> = {}
|
|
77
|
+
|
|
78
|
+
for (const req of requests) {
|
|
79
|
+
const type = req.auth.authType
|
|
80
|
+
if (type !== 'none' && type !== 'inherit') {
|
|
81
|
+
authTypes.add(type)
|
|
82
|
+
authCounts[type] = (authCounts[type] || 0) + 1
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Find primary auth method (most common)
|
|
87
|
+
let primaryMethod: string | null = null
|
|
88
|
+
let maxCount = 0
|
|
89
|
+
for (const [type, count] of Object.entries(authCounts)) {
|
|
90
|
+
if (count > maxCount) {
|
|
91
|
+
maxCount = count
|
|
92
|
+
primaryMethod = type
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
types: Array.from(authTypes),
|
|
98
|
+
primaryMethod,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Group endpoints by resource/tag
|
|
104
|
+
*/
|
|
105
|
+
function groupByResource(requests: BrainfishRESTRequest[]): ResourceGroup[] {
|
|
106
|
+
const groups: Map<string, EndpointSummary[]> = new Map()
|
|
107
|
+
|
|
108
|
+
for (const req of requests) {
|
|
109
|
+
// Use first tag, or extract from path, or use "Other"
|
|
110
|
+
let groupName = req.tags[0] || extractResourceFromPath(req.endpoint) || 'Other'
|
|
111
|
+
groupName = groupName.charAt(0).toUpperCase() + groupName.slice(1)
|
|
112
|
+
|
|
113
|
+
const summary: EndpointSummary = {
|
|
114
|
+
name: req.name,
|
|
115
|
+
method: req.method,
|
|
116
|
+
path: req.endpoint,
|
|
117
|
+
description: req.description,
|
|
118
|
+
tags: req.tags,
|
|
119
|
+
hasAuth: req.auth.authType !== 'none',
|
|
120
|
+
hasBody: req.body.body !== null,
|
|
121
|
+
paramCount: req.params.filter(p => p.active).length,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!groups.has(groupName)) {
|
|
125
|
+
groups.set(groupName, [])
|
|
126
|
+
}
|
|
127
|
+
groups.get(groupName)!.push(summary)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Convert to array and calculate operations
|
|
131
|
+
return Array.from(groups.entries()).map(([name, endpoints]) => ({
|
|
132
|
+
name,
|
|
133
|
+
endpoints,
|
|
134
|
+
operations: {
|
|
135
|
+
create: endpoints.filter(e => e.method === 'POST').length,
|
|
136
|
+
read: endpoints.filter(e => e.method === 'GET').length,
|
|
137
|
+
update: endpoints.filter(e => e.method === 'PUT' || e.method === 'PATCH').length,
|
|
138
|
+
delete: endpoints.filter(e => e.method === 'DELETE').length,
|
|
139
|
+
},
|
|
140
|
+
}))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extract resource name from path
|
|
145
|
+
*/
|
|
146
|
+
function extractResourceFromPath(path: string): string | null {
|
|
147
|
+
// Remove base URL and extract first path segment
|
|
148
|
+
const pathMatch = path.match(/\/api\/v?\d*\/?([^\/]+)/) || path.match(/\/([^\/]+)/)
|
|
149
|
+
if (pathMatch) {
|
|
150
|
+
return pathMatch[1].replace(/[-_]/g, ' ')
|
|
151
|
+
}
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Detect common API patterns
|
|
157
|
+
*/
|
|
158
|
+
function detectPatterns(groups: ResourceGroup[]): string[] {
|
|
159
|
+
const patterns: string[] = []
|
|
160
|
+
|
|
161
|
+
// Check for RESTful CRUD patterns
|
|
162
|
+
const hasCRUD = groups.some(g =>
|
|
163
|
+
g.operations.create > 0 &&
|
|
164
|
+
g.operations.read > 0 &&
|
|
165
|
+
(g.operations.update > 0 || g.operations.delete > 0)
|
|
166
|
+
)
|
|
167
|
+
if (hasCRUD) {
|
|
168
|
+
patterns.push('RESTful CRUD operations')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check for authentication endpoints
|
|
172
|
+
const hasAuthEndpoints = groups.some(g =>
|
|
173
|
+
g.name.toLowerCase().includes('auth') ||
|
|
174
|
+
g.endpoints.some(e =>
|
|
175
|
+
e.name.toLowerCase().includes('login') ||
|
|
176
|
+
e.name.toLowerCase().includes('token') ||
|
|
177
|
+
e.name.toLowerCase().includes('session')
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
if (hasAuthEndpoints) {
|
|
181
|
+
patterns.push('Authentication/Authorization flows')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check for search/filter patterns
|
|
185
|
+
const hasSearch = groups.some(g =>
|
|
186
|
+
g.endpoints.some(e =>
|
|
187
|
+
e.name.toLowerCase().includes('search') ||
|
|
188
|
+
e.name.toLowerCase().includes('filter') ||
|
|
189
|
+
e.paramCount > 2
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
if (hasSearch) {
|
|
193
|
+
patterns.push('Search and filtering capabilities')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check for batch operations
|
|
197
|
+
const hasBatch = groups.some(g =>
|
|
198
|
+
g.endpoints.some(e =>
|
|
199
|
+
e.name.toLowerCase().includes('batch') ||
|
|
200
|
+
e.name.toLowerCase().includes('bulk')
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
if (hasBatch) {
|
|
204
|
+
patterns.push('Batch/bulk operations')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check for webhooks
|
|
208
|
+
const hasWebhooks = groups.some(g =>
|
|
209
|
+
g.name.toLowerCase().includes('webhook') ||
|
|
210
|
+
g.endpoints.some(e => e.name.toLowerCase().includes('webhook'))
|
|
211
|
+
)
|
|
212
|
+
if (hasWebhooks) {
|
|
213
|
+
patterns.push('Webhook integrations')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return patterns
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate suggested use cases based on resources
|
|
221
|
+
*/
|
|
222
|
+
function generateUseCases(groups: ResourceGroup[], _apiName: string): string[] {
|
|
223
|
+
const useCases: string[] = []
|
|
224
|
+
|
|
225
|
+
for (const group of groups.slice(0, 5)) { // Top 5 groups
|
|
226
|
+
if (group.operations.create > 0) {
|
|
227
|
+
useCases.push(`Create and manage ${group.name.toLowerCase()}`)
|
|
228
|
+
}
|
|
229
|
+
if (group.operations.read > 0 && group.endpoints.length > 1) {
|
|
230
|
+
useCases.push(`List and retrieve ${group.name.toLowerCase()} data`)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Add generic use cases based on patterns
|
|
235
|
+
const hasAuth = groups.some(g => g.name.toLowerCase().includes('auth'))
|
|
236
|
+
if (hasAuth) {
|
|
237
|
+
useCases.push('Implement user authentication flow')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return useCases.slice(0, 6) // Limit to 6 use cases
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Generate a comprehensive API summary from a collection
|
|
245
|
+
*/
|
|
246
|
+
export function generateAPISummary(collection: BrainfishCollection): APISummary {
|
|
247
|
+
const requests = collectAllRequests(collection)
|
|
248
|
+
const groups = groupByResource(requests)
|
|
249
|
+
const patterns = detectPatterns(groups)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
name: collection.name,
|
|
253
|
+
description: collection.description,
|
|
254
|
+
baseUrl: extractBaseUrl(requests),
|
|
255
|
+
totalEndpoints: requests.length,
|
|
256
|
+
authSummary: analyzeAuth(requests),
|
|
257
|
+
resourceGroups: groups,
|
|
258
|
+
commonPatterns: patterns,
|
|
259
|
+
suggestedUseCases: generateUseCases(groups, collection.name),
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Format API summary as a string for the LLM system prompt
|
|
265
|
+
*/
|
|
266
|
+
export function formatAPISummaryForPrompt(summary: APISummary): string {
|
|
267
|
+
const sections: string[] = []
|
|
268
|
+
|
|
269
|
+
// Header
|
|
270
|
+
sections.push(`# API Overview: ${summary.name}`)
|
|
271
|
+
if (summary.description) {
|
|
272
|
+
sections.push(summary.description)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Stats
|
|
276
|
+
sections.push(`\n## Quick Stats`)
|
|
277
|
+
sections.push(`- Total Endpoints: ${summary.totalEndpoints}`)
|
|
278
|
+
if (summary.baseUrl) {
|
|
279
|
+
sections.push(`- Base URL: ${summary.baseUrl}`)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Auth
|
|
283
|
+
if (summary.authSummary.types.length > 0) {
|
|
284
|
+
sections.push(`- Authentication: ${summary.authSummary.types.join(', ')}`)
|
|
285
|
+
if (summary.authSummary.primaryMethod) {
|
|
286
|
+
sections.push(`- Primary Auth Method: ${summary.authSummary.primaryMethod}`)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Resources
|
|
291
|
+
sections.push(`\n## API Resources (${summary.resourceGroups.length} groups)`)
|
|
292
|
+
for (const group of summary.resourceGroups) {
|
|
293
|
+
const ops = []
|
|
294
|
+
if (group.operations.create) ops.push(`${group.operations.create} create`)
|
|
295
|
+
if (group.operations.read) ops.push(`${group.operations.read} read`)
|
|
296
|
+
if (group.operations.update) ops.push(`${group.operations.update} update`)
|
|
297
|
+
if (group.operations.delete) ops.push(`${group.operations.delete} delete`)
|
|
298
|
+
|
|
299
|
+
sections.push(`\n### ${group.name} (${group.endpoints.length} endpoints)`)
|
|
300
|
+
if (ops.length > 0) {
|
|
301
|
+
sections.push(`Operations: ${ops.join(', ')}`)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// List key endpoints (up to 5 per group)
|
|
305
|
+
const keyEndpoints = group.endpoints.slice(0, 5)
|
|
306
|
+
for (const ep of keyEndpoints) {
|
|
307
|
+
const authIndicator = ep.hasAuth ? '🔐' : ''
|
|
308
|
+
sections.push(`- [${ep.method}] ${ep.name} ${authIndicator}`)
|
|
309
|
+
if (ep.description) {
|
|
310
|
+
sections.push(` ${ep.description.slice(0, 100)}${ep.description.length > 100 ? '...' : ''}`)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (group.endpoints.length > 5) {
|
|
314
|
+
sections.push(` ... and ${group.endpoints.length - 5} more`)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Patterns
|
|
319
|
+
if (summary.commonPatterns.length > 0) {
|
|
320
|
+
sections.push(`\n## Common Patterns`)
|
|
321
|
+
for (const pattern of summary.commonPatterns) {
|
|
322
|
+
sections.push(`- ${pattern}`)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Use cases
|
|
327
|
+
if (summary.suggestedUseCases.length > 0) {
|
|
328
|
+
sections.push(`\n## Suggested Use Cases`)
|
|
329
|
+
for (const useCase of summary.suggestedUseCases) {
|
|
330
|
+
sections.push(`- ${useCase}`)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return sections.join('\n')
|
|
335
|
+
}
|