github-to-mcp-monorepo 1.0.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 (388) hide show
  1. package/.env.example +8 -0
  2. package/.github/CODEOWNERS +6 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +7 -0
  7. package/.vscode/settings.json +4 -0
  8. package/ARCHITECTURE.md +1429 -0
  9. package/CHANGELOG.md +167 -0
  10. package/CONTRIBUTING.md +327 -0
  11. package/LICENSE +201 -0
  12. package/README.md +1028 -0
  13. package/SECURITY.md +248 -0
  14. package/VISUAL_GUIDE.md +437 -0
  15. package/apps/vscode/IMPLEMENTATION.md +480 -0
  16. package/apps/vscode/README.md +248 -0
  17. package/apps/vscode/package.json +381 -0
  18. package/apps/vscode/resources/icon.png +0 -0
  19. package/apps/vscode/resources/icon.svg +5 -0
  20. package/apps/vscode/src/commands/browseRegistry.ts +211 -0
  21. package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
  22. package/apps/vscode/src/commands/convert.ts +82 -0
  23. package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
  24. package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
  25. package/apps/vscode/src/commands/index.ts +121 -0
  26. package/apps/vscode/src/commands/validate.ts +197 -0
  27. package/apps/vscode/src/extension.ts +464 -0
  28. package/apps/vscode/src/global.d.ts +36 -0
  29. package/apps/vscode/src/test/extension.test.ts +73 -0
  30. package/apps/vscode/src/utils/file-generator.ts +529 -0
  31. package/apps/vscode/src/utils/github-api.ts +335 -0
  32. package/apps/vscode/src/utils/index.ts +29 -0
  33. package/apps/vscode/src/utils/mcp-config.ts +334 -0
  34. package/apps/vscode/src/utils/storage.ts +87 -0
  35. package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
  36. package/apps/vscode/src/views/OutputChannelView.ts +195 -0
  37. package/apps/vscode/src/views/StatusBarItem.ts +251 -0
  38. package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
  39. package/apps/vscode/src/views/historyProvider.ts +75 -0
  40. package/apps/vscode/src/views/index.ts +12 -0
  41. package/apps/vscode/src/views/resultsPanel.ts +330 -0
  42. package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
  43. package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
  44. package/apps/vscode/src/webviews/index.ts +9 -0
  45. package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
  46. package/apps/vscode/tsconfig.json +20 -0
  47. package/apps/web/PLAYGROUND_GUIDE.md +499 -0
  48. package/apps/web/README.md +505 -0
  49. package/apps/web/app/api/convert/route.ts +100 -0
  50. package/apps/web/app/api/convert/stream/route.ts +198 -0
  51. package/apps/web/app/api/deploy/route.ts +157 -0
  52. package/apps/web/app/api/edge/route.ts +308 -0
  53. package/apps/web/app/api/export-docker/route.ts +284 -0
  54. package/apps/web/app/api/generate-openapi/route.ts +119 -0
  55. package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
  56. package/apps/web/app/api/playground/connect/route.ts +143 -0
  57. package/apps/web/app/api/playground/disconnect/route.ts +78 -0
  58. package/apps/web/app/api/playground/execute/route.ts +135 -0
  59. package/apps/web/app/api/playground/sessions/route.ts +103 -0
  60. package/apps/web/app/api/playground/tools/route.ts +117 -0
  61. package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
  62. package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
  63. package/apps/web/app/api/playground/v2/health/route.ts +80 -0
  64. package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
  65. package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
  66. package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
  67. package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
  68. package/apps/web/app/api/stream/route.ts +232 -0
  69. package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
  70. package/apps/web/app/batch/page.tsx +37 -0
  71. package/apps/web/app/convert/page.tsx +269 -0
  72. package/apps/web/app/dashboard/page.tsx +380 -0
  73. package/apps/web/app/globals.css +622 -0
  74. package/apps/web/app/layout.tsx +120 -0
  75. package/apps/web/app/manifest.ts +31 -0
  76. package/apps/web/app/opengraph-image.tsx +112 -0
  77. package/apps/web/app/page.old.tsx +924 -0
  78. package/apps/web/app/page.tsx +77 -0
  79. package/apps/web/app/playground/page.tsx +306 -0
  80. package/apps/web/app/playground/v2/error.tsx +163 -0
  81. package/apps/web/app/playground/v2/layout.tsx +58 -0
  82. package/apps/web/app/playground/v2/loading.tsx +152 -0
  83. package/apps/web/app/playground/v2/page.tsx +644 -0
  84. package/apps/web/app/playground/v2/providers.tsx +214 -0
  85. package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
  86. package/apps/web/app/playground/v2/use-url-state.ts +296 -0
  87. package/apps/web/app/providers.tsx +22 -0
  88. package/apps/web/app/sitemap.ts +32 -0
  89. package/apps/web/app/twitter-image.tsx +112 -0
  90. package/apps/web/components/BranchSelector.tsx +401 -0
  91. package/apps/web/components/ClaudeConfigExport.tsx +226 -0
  92. package/apps/web/components/Features.tsx +84 -0
  93. package/apps/web/components/Footer.tsx +119 -0
  94. package/apps/web/components/GenerationProgress.tsx +248 -0
  95. package/apps/web/components/GithubUrlInput.tsx +483 -0
  96. package/apps/web/components/Header.tsx +175 -0
  97. package/apps/web/components/Hero.tsx +117 -0
  98. package/apps/web/components/HowItWorks.tsx +119 -0
  99. package/apps/web/components/InstallBanner.tsx +158 -0
  100. package/apps/web/components/Logo.tsx +116 -0
  101. package/apps/web/components/ParticleBackground.tsx +105 -0
  102. package/apps/web/components/Playground.tsx +472 -0
  103. package/apps/web/components/PlaygroundToolTester.tsx +410 -0
  104. package/apps/web/components/ProductCards.tsx +179 -0
  105. package/apps/web/components/SplitView.tsx +194 -0
  106. package/apps/web/components/ToolFilter.tsx +260 -0
  107. package/apps/web/components/ToolList.tsx +325 -0
  108. package/apps/web/components/batch/BatchConvert.tsx +785 -0
  109. package/apps/web/components/batch/index.ts +7 -0
  110. package/apps/web/components/convert/ConfigTabs.tsx +230 -0
  111. package/apps/web/components/convert/ConversionResult.tsx +482 -0
  112. package/apps/web/components/convert/InlinePlayground.tsx +259 -0
  113. package/apps/web/components/convert/LoadingSteps.tsx +311 -0
  114. package/apps/web/components/convert/OneClickInstall.tsx +224 -0
  115. package/apps/web/components/convert/ToolCard.tsx +189 -0
  116. package/apps/web/components/convert/TryInPlayground.tsx +242 -0
  117. package/apps/web/components/convert/index.ts +12 -0
  118. package/apps/web/components/deploy/DeployButton.tsx +369 -0
  119. package/apps/web/components/deploy/index.ts +7 -0
  120. package/apps/web/components/docker/DockerExport.tsx +690 -0
  121. package/apps/web/components/docker/index.ts +7 -0
  122. package/apps/web/components/install/OneClickInstall.tsx +676 -0
  123. package/apps/web/components/install/index.ts +7 -0
  124. package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
  125. package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
  126. package/apps/web/components/playground/EmptyStates.tsx +305 -0
  127. package/apps/web/components/playground/ExecutionLog.tsx +260 -0
  128. package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
  129. package/apps/web/components/playground/JsonViewer.tsx +388 -0
  130. package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
  131. package/apps/web/components/playground/PromptsPanel.tsx +385 -0
  132. package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
  133. package/apps/web/components/playground/SchemaForm.tsx +477 -0
  134. package/apps/web/components/playground/ServerStatus.tsx +151 -0
  135. package/apps/web/components/playground/ShareButton.tsx +239 -0
  136. package/apps/web/components/playground/ToolsPanel.tsx +309 -0
  137. package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
  138. package/apps/web/components/playground/index.ts +74 -0
  139. package/apps/web/components/playground/types.ts +202 -0
  140. package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
  141. package/apps/web/components/streaming/index.ts +7 -0
  142. package/apps/web/components/ui/badge.tsx +42 -0
  143. package/apps/web/components/ui/button.tsx +88 -0
  144. package/apps/web/components/ui/card.tsx +75 -0
  145. package/apps/web/components/ui/code-block.tsx +122 -0
  146. package/apps/web/components/ui/index.ts +12 -0
  147. package/apps/web/components/ui/input.tsx +55 -0
  148. package/apps/web/components/ui/tabs.tsx +61 -0
  149. package/apps/web/hooks/index.ts +85 -0
  150. package/apps/web/hooks/types.ts +1173 -0
  151. package/apps/web/hooks/use-conversion.ts +133 -0
  152. package/apps/web/hooks/use-execution-history.ts +376 -0
  153. package/apps/web/hooks/use-generation-progress.ts +147 -0
  154. package/apps/web/hooks/use-local-storage.ts +88 -0
  155. package/apps/web/hooks/use-mcp-client.ts +623 -0
  156. package/apps/web/hooks/use-mcp-connection.ts +500 -0
  157. package/apps/web/hooks/use-mcp-execution.ts +282 -0
  158. package/apps/web/hooks/use-mcp-prompts.ts +441 -0
  159. package/apps/web/hooks/use-mcp-resources.ts +430 -0
  160. package/apps/web/hooks/use-mcp-tools.ts +540 -0
  161. package/apps/web/hooks/use-playground-store.ts +299 -0
  162. package/apps/web/hooks/use-playground.ts +184 -0
  163. package/apps/web/hooks/use-streaming-conversion.ts +227 -0
  164. package/apps/web/hooks/useBatchConversion.ts +271 -0
  165. package/apps/web/hooks/useDockerConfig.ts +161 -0
  166. package/apps/web/hooks/usePlatformDetection.ts +80 -0
  167. package/apps/web/hooks/useStreaming.ts +199 -0
  168. package/apps/web/lib/api/errors.ts +386 -0
  169. package/apps/web/lib/api/index.ts +137 -0
  170. package/apps/web/lib/api/logger.ts +187 -0
  171. package/apps/web/lib/api/middleware.ts +364 -0
  172. package/apps/web/lib/api/openapi.ts +977 -0
  173. package/apps/web/lib/api/session-manager.ts +594 -0
  174. package/apps/web/lib/api/types.ts +433 -0
  175. package/apps/web/lib/api/validation.ts +523 -0
  176. package/apps/web/lib/constants.ts +114 -0
  177. package/apps/web/lib/mcp/client.ts +1137 -0
  178. package/apps/web/lib/mcp/events.ts +651 -0
  179. package/apps/web/lib/mcp/index.ts +347 -0
  180. package/apps/web/lib/mcp/logger.ts +428 -0
  181. package/apps/web/lib/mcp/metrics.ts +703 -0
  182. package/apps/web/lib/mcp/retry.ts +616 -0
  183. package/apps/web/lib/mcp/session-manager.ts +779 -0
  184. package/apps/web/lib/mcp/transports.ts +988 -0
  185. package/apps/web/lib/mcp/types.ts +594 -0
  186. package/apps/web/lib/mcp-client-enhanced.ts +871 -0
  187. package/apps/web/lib/mcp-client.ts +778 -0
  188. package/apps/web/lib/mcp-errors.ts +489 -0
  189. package/apps/web/lib/mcp-sandbox.ts +593 -0
  190. package/apps/web/lib/mcp-testing.ts +428 -0
  191. package/apps/web/lib/mcp-types.ts +448 -0
  192. package/apps/web/lib/playground-store.tsx +1147 -0
  193. package/apps/web/lib/utils.ts +439 -0
  194. package/apps/web/next-env.d.ts +5 -0
  195. package/apps/web/next.config.js +23 -0
  196. package/apps/web/package.json +55 -0
  197. package/apps/web/postcss.config.js +6 -0
  198. package/apps/web/public/.well-known/ai-plugin.json +17 -0
  199. package/apps/web/public/logo.svg +6 -0
  200. package/apps/web/public/robots.txt +22 -0
  201. package/apps/web/public/schema.json +27 -0
  202. package/apps/web/tailwind.config.js +26 -0
  203. package/apps/web/tailwind.config.ts +123 -0
  204. package/apps/web/tsconfig.json +20 -0
  205. package/apps/web/types/deploy.ts +139 -0
  206. package/apps/web/types/index.ts +247 -0
  207. package/apps/web/vercel.json +39 -0
  208. package/eslint.config.mjs +23 -0
  209. package/llms.txt +102 -0
  210. package/mkdocs/docs/api/core.md +318 -0
  211. package/mkdocs/docs/api/index.md +128 -0
  212. package/mkdocs/docs/api/mcp-server.md +301 -0
  213. package/mkdocs/docs/api/openapi-parser.md +254 -0
  214. package/mkdocs/docs/assets/logo.svg +7 -0
  215. package/mkdocs/docs/changelog.md +118 -0
  216. package/mkdocs/docs/cli/generate.md +148 -0
  217. package/mkdocs/docs/cli/index.md +52 -0
  218. package/mkdocs/docs/cli/inspect.md +164 -0
  219. package/mkdocs/docs/cli/serve.md +136 -0
  220. package/mkdocs/docs/concepts/classification.md +254 -0
  221. package/mkdocs/docs/concepts/how-it-works.md +299 -0
  222. package/mkdocs/docs/concepts/index.md +77 -0
  223. package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
  224. package/mkdocs/docs/concepts/tool-types.md +382 -0
  225. package/mkdocs/docs/contributing/architecture.md +262 -0
  226. package/mkdocs/docs/contributing/development.md +245 -0
  227. package/mkdocs/docs/contributing/index.md +73 -0
  228. package/mkdocs/docs/contributing/testing.md +320 -0
  229. package/mkdocs/docs/getting-started/configuration.md +235 -0
  230. package/mkdocs/docs/getting-started/index.md +54 -0
  231. package/mkdocs/docs/getting-started/installation.md +145 -0
  232. package/mkdocs/docs/getting-started/quickstart.md +160 -0
  233. package/mkdocs/docs/guides/batch.md +375 -0
  234. package/mkdocs/docs/guides/claude-desktop.md +227 -0
  235. package/mkdocs/docs/guides/cursor.md +188 -0
  236. package/mkdocs/docs/guides/custom-tools.md +367 -0
  237. package/mkdocs/docs/guides/index.md +78 -0
  238. package/mkdocs/docs/guides/private-repos.md +221 -0
  239. package/mkdocs/docs/guides/vscode.md +247 -0
  240. package/mkdocs/docs/index.md +175 -0
  241. package/mkdocs/docs/reference/config.md +223 -0
  242. package/mkdocs/docs/reference/env.md +192 -0
  243. package/mkdocs/docs/reference/index.md +102 -0
  244. package/mkdocs/docs/reference/tools.md +309 -0
  245. package/mkdocs/docs/stylesheets/extra.css +231 -0
  246. package/mkdocs/mkdocs.yml +204 -0
  247. package/mkdocs/overrides/.gitkeep +1 -0
  248. package/mkdocs/overrides/main.html +7 -0
  249. package/mkdocs/python-deps.txt +7 -0
  250. package/mkdocs/vercel.json +11 -0
  251. package/package.json +63 -0
  252. package/packages/core/package.json +61 -0
  253. package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
  254. package/packages/core/src/__tests__/cli.test.ts +235 -0
  255. package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
  256. package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
  257. package/packages/core/src/__tests__/github-client.test.ts +390 -0
  258. package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
  259. package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
  260. package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
  261. package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
  262. package/packages/core/src/__tests__/plugins.test.ts +467 -0
  263. package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
  264. package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
  265. package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
  266. package/packages/core/src/__tests__/streaming.test.ts +251 -0
  267. package/packages/core/src/additional-extractors.ts +333 -0
  268. package/packages/core/src/cache/cache-interface.ts +179 -0
  269. package/packages/core/src/cache/index.ts +210 -0
  270. package/packages/core/src/cache/redis-cache.ts +291 -0
  271. package/packages/core/src/cache/upstash-cache.ts +379 -0
  272. package/packages/core/src/cache.ts +251 -0
  273. package/packages/core/src/cli.ts +822 -0
  274. package/packages/core/src/code-extractor.ts +696 -0
  275. package/packages/core/src/docker-generator.ts +470 -0
  276. package/packages/core/src/edge-compatible.ts +491 -0
  277. package/packages/core/src/extractors/go-extractor.ts +791 -0
  278. package/packages/core/src/extractors/index.ts +9 -0
  279. package/packages/core/src/extractors/java-extractor.ts +937 -0
  280. package/packages/core/src/extractors/rust-extractor.ts +744 -0
  281. package/packages/core/src/github-client.ts +319 -0
  282. package/packages/core/src/go-generator.ts +356 -0
  283. package/packages/core/src/graphql-extractor.ts +358 -0
  284. package/packages/core/src/index.ts +797 -0
  285. package/packages/core/src/langchain-exporter.ts +617 -0
  286. package/packages/core/src/language-parsers.ts +1114 -0
  287. package/packages/core/src/mcp-introspector.ts +279 -0
  288. package/packages/core/src/monorepo-detector.ts +378 -0
  289. package/packages/core/src/plugins/index.ts +370 -0
  290. package/packages/core/src/plugins/registry.ts +404 -0
  291. package/packages/core/src/plugins/types.ts +215 -0
  292. package/packages/core/src/providers/base-provider.ts +246 -0
  293. package/packages/core/src/providers/bitbucket-client.ts +464 -0
  294. package/packages/core/src/providers/gitlab-client.ts +388 -0
  295. package/packages/core/src/providers/index.ts +176 -0
  296. package/packages/core/src/python-generator.ts +260 -0
  297. package/packages/core/src/queue/index.ts +100 -0
  298. package/packages/core/src/queue/memory-queue.ts +445 -0
  299. package/packages/core/src/queue/redis-queue.ts +578 -0
  300. package/packages/core/src/queue/types.ts +251 -0
  301. package/packages/core/src/readme-extractor.ts +409 -0
  302. package/packages/core/src/schema-generator.ts +638 -0
  303. package/packages/core/src/streaming.ts +999 -0
  304. package/packages/core/src/types.ts +289 -0
  305. package/packages/core/tsconfig.json +9 -0
  306. package/packages/core/tsup.config.ts +25 -0
  307. package/packages/mcp-server/README.md +297 -0
  308. package/packages/mcp-server/package.json +55 -0
  309. package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
  310. package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
  311. package/packages/mcp-server/src/index.ts +1206 -0
  312. package/packages/mcp-server/src/prompts/index.ts +601 -0
  313. package/packages/mcp-server/src/tools/export-docker.ts +362 -0
  314. package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
  315. package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
  316. package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
  317. package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
  318. package/packages/mcp-server/tsconfig.json +12 -0
  319. package/packages/mcp-server/tsup.config.ts +14 -0
  320. package/packages/openapi-parser/package-lock.json +3028 -0
  321. package/packages/openapi-parser/package.json +41 -0
  322. package/packages/openapi-parser/src/analyzer.ts +700 -0
  323. package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
  324. package/packages/openapi-parser/src/cli.ts +302 -0
  325. package/packages/openapi-parser/src/generator.ts +570 -0
  326. package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
  327. package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
  328. package/packages/openapi-parser/src/generators/index.ts +200 -0
  329. package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
  330. package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
  331. package/packages/openapi-parser/src/generators/types.ts +298 -0
  332. package/packages/openapi-parser/src/graphql-parser.ts +462 -0
  333. package/packages/openapi-parser/src/grpc-parser.ts +649 -0
  334. package/packages/openapi-parser/src/har-parser.ts +723 -0
  335. package/packages/openapi-parser/src/index.ts +635 -0
  336. package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
  337. package/packages/openapi-parser/src/parser.ts +231 -0
  338. package/packages/openapi-parser/src/postman-parser.ts +611 -0
  339. package/packages/openapi-parser/src/ref-resolver.ts +313 -0
  340. package/packages/openapi-parser/src/transformer.ts +459 -0
  341. package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
  342. package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
  343. package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
  344. package/packages/openapi-parser/tests/parsers.test.ts +847 -0
  345. package/packages/openapi-parser/tsconfig.json +9 -0
  346. package/packages/openapi-parser/tsup.config.ts +11 -0
  347. package/packages/registry/package.json +59 -0
  348. package/packages/registry/src/cli.ts +456 -0
  349. package/packages/registry/src/index.ts +44 -0
  350. package/packages/registry/src/popular/github.json +47 -0
  351. package/packages/registry/src/popular/index.ts +55 -0
  352. package/packages/registry/src/popular/linear.json +42 -0
  353. package/packages/registry/src/popular/notion.json +42 -0
  354. package/packages/registry/src/popular/openai.json +40 -0
  355. package/packages/registry/src/popular/resend.json +38 -0
  356. package/packages/registry/src/popular/slack.json +42 -0
  357. package/packages/registry/src/popular/stripe.json +163 -0
  358. package/packages/registry/src/popular/supabase.json +42 -0
  359. package/packages/registry/src/popular/twilio.json +40 -0
  360. package/packages/registry/src/popular/vercel.json +40 -0
  361. package/packages/registry/src/registry.ts +492 -0
  362. package/packages/registry/src/storage.ts +334 -0
  363. package/packages/registry/src/types.ts +275 -0
  364. package/packages/registry/src/updater.ts +208 -0
  365. package/packages/registry/tsconfig.json +10 -0
  366. package/packages/registry/tsup.config.ts +11 -0
  367. package/pnpm-workspace.yaml +3 -0
  368. package/scripts/build-docs.sh +16 -0
  369. package/server.json +9 -0
  370. package/templates/Dockerfile.python.template +60 -0
  371. package/templates/Dockerfile.typescript.template +60 -0
  372. package/templates/docker-compose.template.yml +68 -0
  373. package/tests/fixtures/express-app/index.js +34 -0
  374. package/tests/fixtures/express-app/routes/posts.js +43 -0
  375. package/tests/fixtures/express-app/routes/users.js +58 -0
  376. package/tests/fixtures/fastapi-app/main.py +125 -0
  377. package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
  378. package/tests/fixtures/graphql/simple-schema.graphql +65 -0
  379. package/tests/fixtures/mocks/github-api-responses.json +63 -0
  380. package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
  381. package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
  382. package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
  383. package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
  384. package/tests/fixtures/openapi/petstore.yaml +179 -0
  385. package/tests/integration/langchain-export.test.ts +405 -0
  386. package/tests/integration/openapi-conversion.test.ts +221 -0
  387. package/tsconfig.json +18 -0
  388. package/vitest.config.ts +32 -0
@@ -0,0 +1,388 @@
1
+ /**
2
+ * JsonViewer Component - Display JSON with syntax highlighting
3
+ * @copyright 2024-2026 nirholas
4
+ * @license MIT
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import * as React from 'react';
10
+ import { useState, useCallback, useMemo } from 'react';
11
+ import { motion, AnimatePresence } from 'framer-motion';
12
+ import {
13
+ ChevronRight,
14
+ ChevronDown,
15
+ Copy,
16
+ Check,
17
+ Search,
18
+ Maximize2,
19
+ Minimize2,
20
+ } from 'lucide-react';
21
+ import { Button } from '@/components/ui/button';
22
+ import { Input } from '@/components/ui/input';
23
+ import { cn } from '@/lib/utils';
24
+
25
+ export interface JsonViewerProps {
26
+ /** Data to display */
27
+ data: unknown;
28
+ /** Maximum height in pixels */
29
+ maxHeight?: number;
30
+ /** Start collapsed */
31
+ collapsed?: boolean;
32
+ /** Additional CSS classes */
33
+ className?: string;
34
+ }
35
+
36
+ interface JsonNodeProps {
37
+ keyName?: string;
38
+ value: unknown;
39
+ depth: number;
40
+ collapsed: boolean;
41
+ searchQuery: string;
42
+ path: string;
43
+ }
44
+
45
+ /**
46
+ * Determine the type of a value for display
47
+ */
48
+ function getValueType(
49
+ value: unknown
50
+ ): 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object' {
51
+ if (value === null) return 'null';
52
+ if (Array.isArray(value)) return 'array';
53
+ const type = typeof value;
54
+ if (type === 'object') return 'object';
55
+ if (type === 'string') return 'string';
56
+ if (type === 'number') return 'number';
57
+ if (type === 'boolean') return 'boolean';
58
+ return 'string';
59
+ }
60
+
61
+ /**
62
+ * Get color for value type
63
+ */
64
+ function getTypeColor(type: ReturnType<typeof getValueType>): string {
65
+ switch (type) {
66
+ case 'string':
67
+ return 'text-green-400';
68
+ case 'number':
69
+ return 'text-blue-400';
70
+ case 'boolean':
71
+ return 'text-yellow-400';
72
+ case 'null':
73
+ return 'text-neutral-500';
74
+ case 'array':
75
+ case 'object':
76
+ return 'text-neutral-300';
77
+ default:
78
+ return 'text-neutral-400';
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Format a primitive value for display
84
+ */
85
+ function formatValue(value: unknown, type: ReturnType<typeof getValueType>): string {
86
+ if (type === 'string') {
87
+ // Truncate long strings
88
+ const str = value as string;
89
+ if (str.length > 100) {
90
+ return `"${str.substring(0, 100)}..."`;
91
+ }
92
+ return `"${str}"`;
93
+ }
94
+ if (type === 'null') return 'null';
95
+ return String(value);
96
+ }
97
+
98
+ /**
99
+ * Check if a string matches the search query
100
+ */
101
+ function matchesSearch(text: string, query: string): boolean {
102
+ if (!query) return false;
103
+ return text.toLowerCase().includes(query.toLowerCase());
104
+ }
105
+
106
+ /**
107
+ * Recursive JSON node renderer
108
+ */
109
+ function JsonNode({
110
+ keyName,
111
+ value,
112
+ depth,
113
+ collapsed: initialCollapsed,
114
+ searchQuery,
115
+ path,
116
+ }: JsonNodeProps) {
117
+ const [isCollapsed, setIsCollapsed] = useState(initialCollapsed && depth > 0);
118
+ const type = getValueType(value);
119
+ const isExpandable = type === 'object' || type === 'array';
120
+ const indent = depth * 16;
121
+
122
+ // Highlight matching text
123
+ const highlightMatch = (text: string): React.ReactNode => {
124
+ if (!searchQuery) return text;
125
+ const index = text.toLowerCase().indexOf(searchQuery.toLowerCase());
126
+ if (index === -1) return text;
127
+ return (
128
+ <>
129
+ {text.substring(0, index)}
130
+ <span className="bg-yellow-500/30 text-yellow-300">
131
+ {text.substring(index, index + searchQuery.length)}
132
+ </span>
133
+ {text.substring(index + searchQuery.length)}
134
+ </>
135
+ );
136
+ };
137
+
138
+ // Check if this node or its children match the search
139
+ const hasMatch = useMemo(() => {
140
+ if (!searchQuery) return false;
141
+ if (keyName && matchesSearch(keyName, searchQuery)) return true;
142
+ if (type === 'string' && matchesSearch(value as string, searchQuery)) return true;
143
+ if (type === 'object' || type === 'array') {
144
+ const check = (v: unknown): boolean => {
145
+ if (typeof v === 'string' && matchesSearch(v, searchQuery)) return true;
146
+ if (Array.isArray(v)) return v.some(check);
147
+ if (typeof v === 'object' && v !== null) {
148
+ return Object.entries(v).some(
149
+ ([k, val]) => matchesSearch(k, searchQuery) || check(val)
150
+ );
151
+ }
152
+ return false;
153
+ };
154
+ return check(value);
155
+ }
156
+ return false;
157
+ }, [keyName, value, type, searchQuery]);
158
+
159
+ // Auto-expand if matches search
160
+ React.useEffect(() => {
161
+ if (hasMatch && isCollapsed) {
162
+ setIsCollapsed(false);
163
+ }
164
+ }, [hasMatch, searchQuery]);
165
+
166
+ // Render primitive value
167
+ if (!isExpandable) {
168
+ const formattedValue = formatValue(value, type);
169
+ const keyMatches = keyName && matchesSearch(keyName, searchQuery);
170
+ const valueMatches = type === 'string' && matchesSearch(value as string, searchQuery);
171
+
172
+ return (
173
+ <div
174
+ style={{ paddingLeft: indent }}
175
+ className={cn(
176
+ 'flex items-baseline gap-1 py-0.5 hover:bg-white/5 transition-colors',
177
+ (keyMatches || valueMatches) && 'bg-yellow-500/10'
178
+ )}
179
+ >
180
+ {keyName && (
181
+ <>
182
+ <span className="text-purple-400">
183
+ {searchQuery ? highlightMatch(keyName) : keyName}
184
+ </span>
185
+ <span className="text-neutral-500">:</span>
186
+ </>
187
+ )}
188
+ <span className={getTypeColor(type)}>
189
+ {type === 'string' && searchQuery
190
+ ? highlightMatch(formattedValue)
191
+ : formattedValue}
192
+ </span>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ // Render object/array
198
+ const entries = type === 'array' ? (value as unknown[]) : Object.entries(value as object);
199
+ const bracketOpen = type === 'array' ? '[' : '{';
200
+ const bracketClose = type === 'array' ? ']' : '}';
201
+ const isEmpty = entries.length === 0;
202
+ const keyMatches = keyName && matchesSearch(keyName, searchQuery);
203
+
204
+ return (
205
+ <div>
206
+ <div
207
+ style={{ paddingLeft: indent }}
208
+ className={cn(
209
+ 'flex items-baseline gap-1 py-0.5 hover:bg-white/5 transition-colors cursor-pointer',
210
+ keyMatches && 'bg-yellow-500/10'
211
+ )}
212
+ onClick={() => setIsCollapsed(!isCollapsed)}
213
+ >
214
+ {/* Expand/collapse icon */}
215
+ <span className="text-neutral-500 w-4 flex-shrink-0">
216
+ {!isEmpty &&
217
+ (isCollapsed ? (
218
+ <ChevronRight className="w-3 h-3" />
219
+ ) : (
220
+ <ChevronDown className="w-3 h-3" />
221
+ ))}
222
+ </span>
223
+
224
+ {/* Key name */}
225
+ {keyName && (
226
+ <>
227
+ <span className="text-purple-400">
228
+ {searchQuery ? highlightMatch(keyName) : keyName}
229
+ </span>
230
+ <span className="text-neutral-500">:</span>
231
+ </>
232
+ )}
233
+
234
+ {/* Opening bracket */}
235
+ <span className="text-neutral-300">{bracketOpen}</span>
236
+
237
+ {/* Collapsed preview */}
238
+ {isCollapsed && !isEmpty && (
239
+ <span className="text-neutral-500 text-xs">
240
+ {type === 'array' ? `${entries.length} items` : `${entries.length} keys`}
241
+ </span>
242
+ )}
243
+
244
+ {/* Closing bracket for empty or collapsed */}
245
+ {(isEmpty || isCollapsed) && (
246
+ <span className="text-neutral-300">{bracketClose}</span>
247
+ )}
248
+ </div>
249
+
250
+ {/* Children */}
251
+ {!isCollapsed && !isEmpty && (
252
+ <>
253
+ {type === 'array'
254
+ ? (entries as unknown[]).map((item, index) => (
255
+ <JsonNode
256
+ key={index}
257
+ keyName={String(index)}
258
+ value={item}
259
+ depth={depth + 1}
260
+ collapsed={initialCollapsed}
261
+ searchQuery={searchQuery}
262
+ path={`${path}[${index}]`}
263
+ />
264
+ ))
265
+ : (entries as [string, unknown][]).map(([key, val]) => (
266
+ <JsonNode
267
+ key={key}
268
+ keyName={key}
269
+ value={val}
270
+ depth={depth + 1}
271
+ collapsed={initialCollapsed}
272
+ searchQuery={searchQuery}
273
+ path={`${path}.${key}`}
274
+ />
275
+ ))}
276
+ <div
277
+ style={{ paddingLeft: indent }}
278
+ className="text-neutral-300 py-0.5"
279
+ >
280
+ {bracketClose}
281
+ </div>
282
+ </>
283
+ )}
284
+ </div>
285
+ );
286
+ }
287
+
288
+ /**
289
+ * JsonViewer - Display JSON with syntax highlighting and interactive features
290
+ */
291
+ export default function JsonViewer({
292
+ data,
293
+ maxHeight = 400,
294
+ collapsed = false,
295
+ className = '',
296
+ }: JsonViewerProps) {
297
+ const [searchQuery, setSearchQuery] = useState('');
298
+ const [copied, setCopied] = useState(false);
299
+ const [isExpanded, setIsExpanded] = useState(false);
300
+
301
+ // Copy to clipboard
302
+ const handleCopy = useCallback(async () => {
303
+ try {
304
+ await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
305
+ setCopied(true);
306
+ setTimeout(() => setCopied(false), 2000);
307
+ } catch {
308
+ // Ignore copy errors
309
+ }
310
+ }, [data]);
311
+
312
+ // Handle null/undefined
313
+ if (data === undefined) {
314
+ return (
315
+ <div className={cn('text-sm text-neutral-500 italic', className)}>
316
+ undefined
317
+ </div>
318
+ );
319
+ }
320
+
321
+ const effectiveMaxHeight = isExpanded ? undefined : maxHeight;
322
+
323
+ return (
324
+ <div
325
+ className={cn(
326
+ 'rounded-lg border border-neutral-800 bg-neutral-950 overflow-hidden',
327
+ className
328
+ )}
329
+ >
330
+ {/* Toolbar */}
331
+ <div className="flex items-center justify-between px-3 py-2 border-b border-neutral-800 bg-neutral-900/50">
332
+ {/* Search */}
333
+ <div className="relative flex-1 max-w-xs">
334
+ <Input
335
+ type="text"
336
+ placeholder="Search..."
337
+ value={searchQuery}
338
+ onChange={(e) => setSearchQuery(e.target.value)}
339
+ leftIcon={<Search className="w-3.5 h-3.5" />}
340
+ className="h-7 text-xs"
341
+ />
342
+ </div>
343
+
344
+ {/* Actions */}
345
+ <div className="flex items-center gap-1 ml-2">
346
+ <Button
347
+ variant="ghost"
348
+ size="icon-sm"
349
+ onClick={() => setIsExpanded(!isExpanded)}
350
+ title={isExpanded ? 'Collapse' : 'Expand'}
351
+ >
352
+ {isExpanded ? (
353
+ <Minimize2 className="w-3.5 h-3.5" />
354
+ ) : (
355
+ <Maximize2 className="w-3.5 h-3.5" />
356
+ )}
357
+ </Button>
358
+ <Button
359
+ variant="ghost"
360
+ size="icon-sm"
361
+ onClick={handleCopy}
362
+ title="Copy to clipboard"
363
+ >
364
+ {copied ? (
365
+ <Check className="w-3.5 h-3.5 text-green-400" />
366
+ ) : (
367
+ <Copy className="w-3.5 h-3.5" />
368
+ )}
369
+ </Button>
370
+ </div>
371
+ </div>
372
+
373
+ {/* Content */}
374
+ <div
375
+ style={{ maxHeight: effectiveMaxHeight }}
376
+ className="overflow-auto p-3 font-mono text-xs"
377
+ >
378
+ <JsonNode
379
+ value={data}
380
+ depth={0}
381
+ collapsed={collapsed}
382
+ searchQuery={searchQuery}
383
+ path="$"
384
+ />
385
+ </div>
386
+ </div>
387
+ );
388
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * PlaygroundLayout Component - Main layout structure for playground
3
+ * @copyright 2024-2026 nirholas
4
+ * @license MIT
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import * as React from 'react';
10
+ import { useState, useRef, useEffect, useCallback } from 'react';
11
+ import { motion, AnimatePresence } from 'framer-motion';
12
+ import { PanelLeftClose, PanelLeftOpen, GripVertical } from 'lucide-react';
13
+ import { cn } from '@/lib/utils';
14
+
15
+ export interface PlaygroundLayoutProps {
16
+ /** Header content (connection status, etc.) */
17
+ header: React.ReactNode;
18
+ /** Sidebar content (transport config, etc.) */
19
+ sidebar: React.ReactNode;
20
+ /** Main content area (tools/resources/prompts panels) */
21
+ main: React.ReactNode;
22
+ /** Footer content (execution logs, etc.) */
23
+ footer?: React.ReactNode;
24
+ /** Default sidebar width in pixels */
25
+ defaultSidebarWidth?: number;
26
+ /** Minimum sidebar width in pixels */
27
+ minSidebarWidth?: number;
28
+ /** Maximum sidebar width in pixels */
29
+ maxSidebarWidth?: number;
30
+ /** Default footer height in pixels */
31
+ defaultFooterHeight?: number;
32
+ /** Minimum footer height in pixels */
33
+ minFooterHeight?: number;
34
+ /** Maximum footer height in pixels */
35
+ maxFooterHeight?: number;
36
+ /** Additional CSS classes */
37
+ className?: string;
38
+ }
39
+
40
+ /**
41
+ * PlaygroundLayout - Responsive layout with resizable panels
42
+ */
43
+ export default function PlaygroundLayout({
44
+ header,
45
+ sidebar,
46
+ main,
47
+ footer,
48
+ defaultSidebarWidth = 320,
49
+ minSidebarWidth = 280,
50
+ maxSidebarWidth = 500,
51
+ defaultFooterHeight = 200,
52
+ minFooterHeight = 100,
53
+ maxFooterHeight = 400,
54
+ className = '',
55
+ }: PlaygroundLayoutProps) {
56
+ const [sidebarWidth, setSidebarWidth] = useState(defaultSidebarWidth);
57
+ const [footerHeight, setFooterHeight] = useState(defaultFooterHeight);
58
+ const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
59
+ const [isFooterCollapsed, setIsFooterCollapsed] = useState(false);
60
+ const [isDraggingSidebar, setIsDraggingSidebar] = useState(false);
61
+ const [isDraggingFooter, setIsDraggingFooter] = useState(false);
62
+
63
+ const containerRef = useRef<HTMLDivElement>(null);
64
+
65
+ // Handle sidebar resize
66
+ useEffect(() => {
67
+ if (!isDraggingSidebar) return;
68
+
69
+ const handleMouseMove = (e: MouseEvent) => {
70
+ if (!containerRef.current) return;
71
+ const rect = containerRef.current.getBoundingClientRect();
72
+ const newWidth = e.clientX - rect.left;
73
+ setSidebarWidth(
74
+ Math.max(minSidebarWidth, Math.min(maxSidebarWidth, newWidth))
75
+ );
76
+ };
77
+
78
+ const handleMouseUp = () => {
79
+ setIsDraggingSidebar(false);
80
+ document.body.style.cursor = '';
81
+ document.body.style.userSelect = '';
82
+ };
83
+
84
+ document.body.style.cursor = 'col-resize';
85
+ document.body.style.userSelect = 'none';
86
+
87
+ document.addEventListener('mousemove', handleMouseMove);
88
+ document.addEventListener('mouseup', handleMouseUp);
89
+
90
+ return () => {
91
+ document.removeEventListener('mousemove', handleMouseMove);
92
+ document.removeEventListener('mouseup', handleMouseUp);
93
+ document.body.style.cursor = '';
94
+ document.body.style.userSelect = '';
95
+ };
96
+ }, [isDraggingSidebar, minSidebarWidth, maxSidebarWidth]);
97
+
98
+ // Handle footer resize
99
+ useEffect(() => {
100
+ if (!isDraggingFooter) return;
101
+
102
+ const handleMouseMove = (e: MouseEvent) => {
103
+ if (!containerRef.current) return;
104
+ const rect = containerRef.current.getBoundingClientRect();
105
+ const newHeight = rect.bottom - e.clientY;
106
+ setFooterHeight(
107
+ Math.max(minFooterHeight, Math.min(maxFooterHeight, newHeight))
108
+ );
109
+ };
110
+
111
+ const handleMouseUp = () => {
112
+ setIsDraggingFooter(false);
113
+ document.body.style.cursor = '';
114
+ document.body.style.userSelect = '';
115
+ };
116
+
117
+ document.body.style.cursor = 'row-resize';
118
+ document.body.style.userSelect = 'none';
119
+
120
+ document.addEventListener('mousemove', handleMouseMove);
121
+ document.addEventListener('mouseup', handleMouseUp);
122
+
123
+ return () => {
124
+ document.removeEventListener('mousemove', handleMouseMove);
125
+ document.removeEventListener('mouseup', handleMouseUp);
126
+ document.body.style.cursor = '';
127
+ document.body.style.userSelect = '';
128
+ };
129
+ }, [isDraggingFooter, minFooterHeight, maxFooterHeight]);
130
+
131
+ // Toggle sidebar
132
+ const toggleSidebar = useCallback(() => {
133
+ setIsSidebarCollapsed((prev) => !prev);
134
+ }, []);
135
+
136
+ // Toggle footer
137
+ const toggleFooter = useCallback(() => {
138
+ setIsFooterCollapsed((prev) => !prev);
139
+ }, []);
140
+
141
+ return (
142
+ <div
143
+ ref={containerRef}
144
+ className={cn('flex flex-col h-full overflow-hidden', className)}
145
+ >
146
+ {/* Header */}
147
+ <div className="flex-shrink-0">{header}</div>
148
+
149
+ {/* Main area with sidebar */}
150
+ <div className="flex-1 flex min-h-0">
151
+ {/* Sidebar */}
152
+ <AnimatePresence initial={false}>
153
+ {!isSidebarCollapsed && (
154
+ <motion.div
155
+ initial={{ width: 0, opacity: 0 }}
156
+ animate={{ width: sidebarWidth, opacity: 1 }}
157
+ exit={{ width: 0, opacity: 0 }}
158
+ transition={{ duration: 0.2 }}
159
+ className="flex-shrink-0 border-r border-neutral-800 overflow-hidden"
160
+ style={{ width: sidebarWidth }}
161
+ >
162
+ <div className="h-full overflow-y-auto">{sidebar}</div>
163
+ </motion.div>
164
+ )}
165
+ </AnimatePresence>
166
+
167
+ {/* Sidebar resize handle */}
168
+ {!isSidebarCollapsed && (
169
+ <div
170
+ onMouseDown={() => setIsDraggingSidebar(true)}
171
+ className={cn(
172
+ 'w-1 flex-shrink-0 bg-transparent hover:bg-white/10 cursor-col-resize transition-colors',
173
+ isDraggingSidebar && 'bg-white/20'
174
+ )}
175
+ />
176
+ )}
177
+
178
+ {/* Sidebar toggle + Main content */}
179
+ <div className="flex-1 flex flex-col min-w-0">
180
+ {/* Sidebar toggle button */}
181
+ <div className="absolute top-[70px] left-0 z-10">
182
+ <button
183
+ onClick={toggleSidebar}
184
+ className="p-1.5 rounded-r-lg bg-neutral-800/80 hover:bg-neutral-700/80 text-neutral-400 hover:text-white transition-colors"
185
+ title={isSidebarCollapsed ? 'Show sidebar' : 'Hide sidebar'}
186
+ >
187
+ {isSidebarCollapsed ? (
188
+ <PanelLeftOpen className="w-4 h-4" />
189
+ ) : (
190
+ <PanelLeftClose className="w-4 h-4" />
191
+ )}
192
+ </button>
193
+ </div>
194
+
195
+ {/* Main content */}
196
+ <div className="flex-1 min-h-0 overflow-hidden">{main}</div>
197
+ </div>
198
+ </div>
199
+
200
+ {/* Footer resize handle */}
201
+ {footer && !isFooterCollapsed && (
202
+ <div
203
+ onMouseDown={() => setIsDraggingFooter(true)}
204
+ className={cn(
205
+ 'h-1 flex-shrink-0 bg-transparent hover:bg-white/10 cursor-row-resize transition-colors flex items-center justify-center',
206
+ isDraggingFooter && 'bg-white/20'
207
+ )}
208
+ >
209
+ <GripVertical className="w-4 h-4 text-neutral-600 rotate-90" />
210
+ </div>
211
+ )}
212
+
213
+ {/* Footer */}
214
+ {footer && (
215
+ <AnimatePresence initial={false}>
216
+ {!isFooterCollapsed && (
217
+ <motion.div
218
+ initial={{ height: 0, opacity: 0 }}
219
+ animate={{ height: footerHeight, opacity: 1 }}
220
+ exit={{ height: 0, opacity: 0 }}
221
+ transition={{ duration: 0.2 }}
222
+ className="flex-shrink-0 border-t border-neutral-800 overflow-hidden"
223
+ style={{ height: footerHeight }}
224
+ >
225
+ <div className="h-full overflow-hidden">{footer}</div>
226
+ </motion.div>
227
+ )}
228
+ </AnimatePresence>
229
+ )}
230
+
231
+ {/* Footer toggle (when collapsed) */}
232
+ {footer && isFooterCollapsed && (
233
+ <div className="flex-shrink-0 border-t border-neutral-800">
234
+ <button
235
+ onClick={toggleFooter}
236
+ className="w-full py-1.5 text-xs text-neutral-500 hover:text-neutral-400 hover:bg-white/5 transition-colors"
237
+ >
238
+ Show execution log
239
+ </button>
240
+ </div>
241
+ )}
242
+ </div>
243
+ );
244
+ }