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,785 @@
1
+ /**
2
+ * Batch Convert Component - Convert multiple GitHub repos to MCP servers
3
+ * @copyright 2024-2026 nirholas
4
+ * @license MIT
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import { useState, useCallback, useRef, useMemo } from 'react';
10
+ import { motion, AnimatePresence, Reorder } from 'framer-motion';
11
+ import {
12
+ Plus,
13
+ Trash2,
14
+ Play,
15
+ Pause,
16
+ X,
17
+ Check,
18
+ Loader2,
19
+ AlertCircle,
20
+ Download,
21
+ Github,
22
+ Package,
23
+ GripVertical,
24
+ Upload,
25
+ FileText,
26
+ Archive,
27
+ ExternalLink,
28
+ RefreshCw,
29
+ ChevronDown,
30
+ ChevronUp,
31
+ Copy,
32
+ Zap,
33
+ Clock,
34
+ } from 'lucide-react';
35
+ import type { BatchConversionItem, BatchConversionState, ConversionResult, ApiError } from '@/types';
36
+
37
+ interface BatchConvertProps {
38
+ onBatchComplete?: (results: Array<{ url: string; result?: ConversionResult; error?: string }>) => void;
39
+ maxConcurrent?: number;
40
+ }
41
+
42
+ const EXAMPLE_REPOS = [
43
+ 'https://github.com/langchain-ai/langchain',
44
+ 'https://github.com/anthropics/anthropic-sdk-python',
45
+ 'https://github.com/openai/openai-python',
46
+ 'https://github.com/vercel/ai',
47
+ 'https://github.com/microsoft/autogen',
48
+ ];
49
+
50
+ const isValidGithubUrl = (url: string): boolean => {
51
+ const pattern = /^https?:\/\/(www\.)?github\.com\/[\w-]+\/[\w.-]+\/?$/i;
52
+ return pattern.test(url.trim());
53
+ };
54
+
55
+ const extractRepoInfo = (url: string): { owner: string; repo: string } | null => {
56
+ const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/i);
57
+ if (match) {
58
+ return { owner: match[1], repo: match[2].replace(/\.git$/, '') };
59
+ }
60
+ return null;
61
+ };
62
+
63
+ export default function BatchConvert({
64
+ onBatchComplete,
65
+ maxConcurrent = 3,
66
+ }: BatchConvertProps) {
67
+ const [items, setItems] = useState<BatchConversionItem[]>([]);
68
+ const [inputValue, setInputValue] = useState('');
69
+ const [state, setState] = useState<BatchConversionState>('idle');
70
+ const [showImport, setShowImport] = useState(false);
71
+ const [expandedItem, setExpandedItem] = useState<string | null>(null);
72
+ const [copiedUrl, setCopiedUrl] = useState<string | null>(null);
73
+
74
+ const abortControllerRef = useRef<AbortController | null>(null);
75
+ const fileInputRef = useRef<HTMLInputElement>(null);
76
+
77
+ // Computed stats
78
+ const stats = useMemo(() => {
79
+ const pending = items.filter(i => i.status === 'pending').length;
80
+ const converting = items.filter(i => i.status === 'converting').length;
81
+ const success = items.filter(i => i.status === 'success').length;
82
+ const error = items.filter(i => i.status === 'error').length;
83
+ const total = items.length;
84
+ const progress = total > 0 ? ((success + error) / total) * 100 : 0;
85
+ const totalTools = items.reduce((sum, item) => sum + (item.result?.tools.length || 0), 0);
86
+
87
+ return { pending, converting, success, error, total, progress, totalTools };
88
+ }, [items]);
89
+
90
+ // Add single URL
91
+ const addUrl = useCallback((url: string) => {
92
+ const trimmedUrl = url.trim();
93
+ if (!trimmedUrl || !isValidGithubUrl(trimmedUrl)) return false;
94
+
95
+ // Check for duplicates
96
+ if (items.some(item => item.url.toLowerCase() === trimmedUrl.toLowerCase())) {
97
+ return false;
98
+ }
99
+
100
+ const newItem: BatchConversionItem = {
101
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
102
+ url: trimmedUrl,
103
+ status: 'pending',
104
+ };
105
+
106
+ setItems(prev => [...prev, newItem]);
107
+ return true;
108
+ }, [items]);
109
+
110
+ // Handle input submission
111
+ const handleInputSubmit = useCallback((e: React.FormEvent) => {
112
+ e.preventDefault();
113
+ if (addUrl(inputValue)) {
114
+ setInputValue('');
115
+ }
116
+ }, [inputValue, addUrl]);
117
+
118
+ // Add multiple URLs from text
119
+ const importUrls = useCallback((text: string) => {
120
+ const urls = text
121
+ .split(/[\n,;]+/)
122
+ .map(u => u.trim())
123
+ .filter(u => isValidGithubUrl(u));
124
+
125
+ let added = 0;
126
+ urls.forEach(url => {
127
+ if (addUrl(url)) added++;
128
+ });
129
+
130
+ return added;
131
+ }, [addUrl]);
132
+
133
+ // Handle file import
134
+ const handleFileImport = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
135
+ const file = e.target.files?.[0];
136
+ if (!file) return;
137
+
138
+ const reader = new FileReader();
139
+ reader.onload = (event) => {
140
+ const text = event.target?.result as string;
141
+ importUrls(text);
142
+ };
143
+ reader.readAsText(file);
144
+
145
+ // Reset input
146
+ if (fileInputRef.current) {
147
+ fileInputRef.current.value = '';
148
+ }
149
+ }, [importUrls]);
150
+
151
+ // Remove item
152
+ const removeItem = useCallback((id: string) => {
153
+ setItems(prev => prev.filter(item => item.id !== id));
154
+ }, []);
155
+
156
+ // Clear all
157
+ const clearAll = useCallback(() => {
158
+ setItems([]);
159
+ setState('idle');
160
+ }, []);
161
+
162
+ // Clear completed
163
+ const clearCompleted = useCallback(() => {
164
+ setItems(prev => prev.filter(item => item.status !== 'success'));
165
+ }, []);
166
+
167
+ // Retry failed
168
+ const retryFailed = useCallback(() => {
169
+ setItems(prev => prev.map(item =>
170
+ item.status === 'error'
171
+ ? { ...item, status: 'pending', error: undefined, progress: 0 }
172
+ : item
173
+ ));
174
+ }, []);
175
+
176
+ // Convert single item
177
+ const convertItem = useCallback(async (item: BatchConversionItem, signal: AbortSignal) => {
178
+ // Update to converting status
179
+ setItems(prev => prev.map(i =>
180
+ i.id === item.id ? { ...i, status: 'converting', progress: 10 } : i
181
+ ));
182
+
183
+ try {
184
+ // Simulate progress updates (in real app, would use SSE)
185
+ const progressInterval = setInterval(() => {
186
+ setItems(prev => prev.map(i =>
187
+ i.id === item.id && i.status === 'converting'
188
+ ? { ...i, progress: Math.min((i.progress || 10) + Math.random() * 20, 90) }
189
+ : i
190
+ ));
191
+ }, 500);
192
+
193
+ // Make API call
194
+ const response = await fetch('/api/convert', {
195
+ method: 'POST',
196
+ headers: { 'Content-Type': 'application/json' },
197
+ body: JSON.stringify({ url: item.url }),
198
+ signal,
199
+ });
200
+
201
+ clearInterval(progressInterval);
202
+
203
+ if (!response.ok) {
204
+ const errorData = await response.json() as ApiError;
205
+ throw new Error(errorData.error || 'Conversion failed');
206
+ }
207
+
208
+ const result = await response.json() as ConversionResult;
209
+
210
+ setItems(prev => prev.map(i =>
211
+ i.id === item.id
212
+ ? { ...i, status: 'success', result, progress: 100 }
213
+ : i
214
+ ));
215
+
216
+ return result;
217
+ } catch (err: any) {
218
+ if (err.name === 'AbortError') {
219
+ setItems(prev => prev.map(i =>
220
+ i.id === item.id
221
+ ? { ...i, status: 'pending', progress: 0 }
222
+ : i
223
+ ));
224
+ return null;
225
+ }
226
+
227
+ setItems(prev => prev.map(i =>
228
+ i.id === item.id
229
+ ? { ...i, status: 'error' as const, error: { error: err.message, code: 'CONVERSION_FAILED' }, progress: 0 }
230
+ : i
231
+ ));
232
+ return null;
233
+ }
234
+ }, []);
235
+
236
+ // Start batch conversion
237
+ const startBatch = useCallback(async () => {
238
+ if (state === 'running') return;
239
+
240
+ setState('running');
241
+ abortControllerRef.current = new AbortController();
242
+ const signal = abortControllerRef.current.signal;
243
+
244
+ const pendingItems = items.filter(i => i.status === 'pending');
245
+ const queue = [...pendingItems];
246
+ const running: Promise<any>[] = [];
247
+
248
+ while ((queue.length > 0 || running.length > 0) && !signal.aborted) {
249
+ // Fill up to maxConcurrent
250
+ while (queue.length > 0 && running.length < maxConcurrent) {
251
+ const item = queue.shift()!;
252
+ const promise = convertItem(item, signal).finally(() => {
253
+ running.splice(running.indexOf(promise), 1);
254
+ });
255
+ running.push(promise);
256
+ }
257
+
258
+ // Wait for at least one to complete
259
+ if (running.length > 0) {
260
+ await Promise.race(running);
261
+ }
262
+ }
263
+
264
+ // Wait for remaining
265
+ await Promise.all(running);
266
+
267
+ if (!signal.aborted) {
268
+ setState('complete');
269
+ onBatchComplete?.(items.map(i => ({
270
+ url: i.url,
271
+ result: i.result,
272
+ error: i.error?.error,
273
+ })));
274
+ }
275
+ }, [items, state, maxConcurrent, convertItem, onBatchComplete]);
276
+
277
+ // Pause batch
278
+ const pauseBatch = useCallback(() => {
279
+ abortControllerRef.current?.abort();
280
+ setState('paused');
281
+ }, []);
282
+
283
+ // Resume batch
284
+ const resumeBatch = useCallback(() => {
285
+ startBatch();
286
+ }, [startBatch]);
287
+
288
+ // Download all results
289
+ const downloadAllResults = useCallback(() => {
290
+ const successItems = items.filter(i => i.status === 'success' && i.result);
291
+
292
+ const combined = {
293
+ timestamp: new Date().toISOString(),
294
+ totalRepos: successItems.length,
295
+ totalTools: stats.totalTools,
296
+ results: successItems.map(i => ({
297
+ url: i.url,
298
+ repository: i.result!.repository,
299
+ tools: i.result!.tools,
300
+ })),
301
+ };
302
+
303
+ const blob = new Blob([JSON.stringify(combined, null, 2)], { type: 'application/json' });
304
+ const url = URL.createObjectURL(blob);
305
+ const a = document.createElement('a');
306
+ a.href = url;
307
+ a.download = `batch-conversion-${Date.now()}.json`;
308
+ document.body.appendChild(a);
309
+ a.click();
310
+ document.body.removeChild(a);
311
+ URL.revokeObjectURL(url);
312
+ }, [items, stats.totalTools]);
313
+
314
+ const copyUrl = useCallback(async (url: string) => {
315
+ await navigator.clipboard.writeText(url);
316
+ setCopiedUrl(url);
317
+ setTimeout(() => setCopiedUrl(null), 2000);
318
+ }, []);
319
+
320
+ const getStatusColor = (status: BatchConversionItem['status']) => {
321
+ switch (status) {
322
+ case 'pending': return 'text-neutral-400';
323
+ case 'converting': return 'text-blue-400';
324
+ case 'success': return 'text-green-400';
325
+ case 'error': return 'text-red-400';
326
+ }
327
+ };
328
+
329
+ const getStatusIcon = (status: BatchConversionItem['status']) => {
330
+ switch (status) {
331
+ case 'pending': return <Clock className="w-4 h-4" />;
332
+ case 'converting': return <Loader2 className="w-4 h-4 animate-spin" />;
333
+ case 'success': return <Check className="w-4 h-4" />;
334
+ case 'error': return <AlertCircle className="w-4 h-4" />;
335
+ }
336
+ };
337
+
338
+ return (
339
+ <div className="space-y-6">
340
+ {/* Header */}
341
+ <motion.div
342
+ initial={{ opacity: 0, y: 20 }}
343
+ animate={{ opacity: 1, y: 0 }}
344
+ className="flex items-center justify-between"
345
+ >
346
+ <div className="flex items-center gap-3">
347
+ <div className="w-12 h-12 rounded-xl bg-purple-500/10 border border-purple-500/20 flex items-center justify-center">
348
+ <Archive className="w-6 h-6 text-purple-400" />
349
+ </div>
350
+ <div>
351
+ <h2 className="text-xl font-semibold text-white">Batch Convert</h2>
352
+ <p className="text-sm text-neutral-400">
353
+ Convert multiple repositories in parallel
354
+ </p>
355
+ </div>
356
+ </div>
357
+
358
+ {stats.total > 0 && (
359
+ <div className="flex items-center gap-4 text-sm">
360
+ <div className="flex items-center gap-1 text-neutral-400">
361
+ <Package className="w-4 h-4" />
362
+ <span>{stats.totalTools} tools</span>
363
+ </div>
364
+ <div className="flex items-center gap-1 text-green-400">
365
+ <Check className="w-4 h-4" />
366
+ <span>{stats.success}/{stats.total}</span>
367
+ </div>
368
+ {stats.error > 0 && (
369
+ <div className="flex items-center gap-1 text-red-400">
370
+ <AlertCircle className="w-4 h-4" />
371
+ <span>{stats.error} failed</span>
372
+ </div>
373
+ )}
374
+ </div>
375
+ )}
376
+ </motion.div>
377
+
378
+ {/* Input section */}
379
+ <motion.div
380
+ initial={{ opacity: 0, y: 20 }}
381
+ animate={{ opacity: 1, y: 0 }}
382
+ transition={{ delay: 0.1 }}
383
+ className="rounded-xl border border-neutral-800 bg-neutral-900/50 backdrop-blur-xl p-4"
384
+ >
385
+ <form onSubmit={handleInputSubmit} className="flex gap-3">
386
+ <div className="flex-1 relative">
387
+ <Github className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-neutral-500" />
388
+ <input
389
+ type="text"
390
+ value={inputValue}
391
+ onChange={(e) => setInputValue(e.target.value)}
392
+ placeholder="https://github.com/owner/repo"
393
+ className="w-full pl-12 pr-4 py-3 bg-black border border-neutral-700 rounded-xl text-white placeholder-neutral-500 focus:outline-none focus:border-white/50 transition-colors"
394
+ disabled={state === 'running'}
395
+ />
396
+ </div>
397
+ <button
398
+ type="submit"
399
+ disabled={!isValidGithubUrl(inputValue) || state === 'running'}
400
+ className="px-6 py-3 bg-white text-black rounded-xl font-medium hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
401
+ >
402
+ <Plus className="w-5 h-5" />
403
+ Add
404
+ </button>
405
+ </form>
406
+
407
+ {/* Import options */}
408
+ <div className="mt-3 flex items-center gap-3">
409
+ <button
410
+ onClick={() => setShowImport(!showImport)}
411
+ className="text-sm text-neutral-400 hover:text-white transition-colors flex items-center gap-1"
412
+ >
413
+ <Upload className="w-4 h-4" />
414
+ Import URLs
415
+ {showImport ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
416
+ </button>
417
+ <span className="text-neutral-600">|</span>
418
+ <button
419
+ onClick={() => EXAMPLE_REPOS.forEach(url => addUrl(url))}
420
+ className="text-sm text-neutral-400 hover:text-white transition-colors"
421
+ >
422
+ Add example repos
423
+ </button>
424
+ </div>
425
+
426
+ {/* Import panel */}
427
+ <AnimatePresence>
428
+ {showImport && (
429
+ <motion.div
430
+ initial={{ height: 0, opacity: 0 }}
431
+ animate={{ height: 'auto', opacity: 1 }}
432
+ exit={{ height: 0, opacity: 0 }}
433
+ className="mt-3 space-y-3 overflow-hidden"
434
+ >
435
+ <textarea
436
+ placeholder="Paste multiple GitHub URLs (one per line, comma, or semicolon separated)"
437
+ className="w-full h-32 p-3 bg-black border border-neutral-700 rounded-xl text-white placeholder-neutral-500 resize-none focus:outline-none focus:border-white/50"
438
+ onBlur={(e) => {
439
+ if (e.target.value.trim()) {
440
+ importUrls(e.target.value);
441
+ e.target.value = '';
442
+ }
443
+ }}
444
+ />
445
+ <div className="flex items-center gap-3">
446
+ <input
447
+ ref={fileInputRef}
448
+ type="file"
449
+ accept=".txt,.csv"
450
+ onChange={handleFileImport}
451
+ className="hidden"
452
+ />
453
+ <button
454
+ onClick={() => fileInputRef.current?.click()}
455
+ className="flex items-center gap-2 px-4 py-2 bg-white/10 text-white rounded-lg text-sm hover:bg-white/20 transition-colors"
456
+ >
457
+ <FileText className="w-4 h-4" />
458
+ Import from file
459
+ </button>
460
+ </div>
461
+ </motion.div>
462
+ )}
463
+ </AnimatePresence>
464
+ </motion.div>
465
+
466
+ {/* Progress bar (when running) */}
467
+ <AnimatePresence>
468
+ {state === 'running' && stats.total > 0 && (
469
+ <motion.div
470
+ initial={{ opacity: 0, height: 0 }}
471
+ animate={{ opacity: 1, height: 'auto' }}
472
+ exit={{ opacity: 0, height: 0 }}
473
+ className="rounded-xl border border-neutral-800 bg-neutral-900/50 p-4"
474
+ >
475
+ <div className="flex items-center justify-between mb-2">
476
+ <span className="text-sm text-neutral-400">Converting...</span>
477
+ <span className="text-sm text-white">{Math.round(stats.progress)}%</span>
478
+ </div>
479
+ <div className="h-2 bg-black/50 rounded-full overflow-hidden">
480
+ <motion.div
481
+ className="h-full bg-white rounded-full"
482
+ initial={{ width: 0 }}
483
+ animate={{ width: `${stats.progress}%` }}
484
+ transition={{ duration: 0.3 }}
485
+ />
486
+ </div>
487
+ <div className="flex items-center gap-4 mt-2 text-xs text-neutral-500">
488
+ <span>{stats.converting} converting</span>
489
+ <span>{stats.pending} pending</span>
490
+ <span>{stats.success} complete</span>
491
+ </div>
492
+ </motion.div>
493
+ )}
494
+ </AnimatePresence>
495
+
496
+ {/* URL list */}
497
+ {items.length > 0 && (
498
+ <motion.div
499
+ initial={{ opacity: 0, y: 20 }}
500
+ animate={{ opacity: 1, y: 0 }}
501
+ transition={{ delay: 0.2 }}
502
+ className="rounded-xl border border-neutral-800 bg-neutral-900/50 backdrop-blur-xl overflow-hidden"
503
+ >
504
+ <div className="p-3 border-b border-neutral-800 flex items-center justify-between">
505
+ <span className="text-sm font-medium text-white">{stats.total} Repositories</span>
506
+ <div className="flex items-center gap-2">
507
+ {stats.error > 0 && state !== 'running' && (
508
+ <button
509
+ onClick={retryFailed}
510
+ className="text-xs text-red-400 hover:text-red-300 transition-colors flex items-center gap-1"
511
+ >
512
+ <RefreshCw className="w-3 h-3" />
513
+ Retry failed
514
+ </button>
515
+ )}
516
+ {stats.success > 0 && (
517
+ <button
518
+ onClick={clearCompleted}
519
+ className="text-xs text-neutral-400 hover:text-white transition-colors"
520
+ >
521
+ Clear completed
522
+ </button>
523
+ )}
524
+ <button
525
+ onClick={clearAll}
526
+ className="text-xs text-neutral-400 hover:text-red-400 transition-colors"
527
+ >
528
+ Clear all
529
+ </button>
530
+ </div>
531
+ </div>
532
+
533
+ <Reorder.Group
534
+ axis="y"
535
+ values={items}
536
+ onReorder={setItems}
537
+ className="divide-y divide-neutral-800"
538
+ >
539
+ <AnimatePresence mode="popLayout">
540
+ {items.map((item) => {
541
+ const repoInfo = extractRepoInfo(item.url);
542
+ const isExpanded = expandedItem === item.id;
543
+
544
+ return (
545
+ <Reorder.Item
546
+ key={item.id}
547
+ value={item}
548
+ initial={{ opacity: 0, height: 0 }}
549
+ animate={{ opacity: 1, height: 'auto' }}
550
+ exit={{ opacity: 0, height: 0 }}
551
+ className="bg-neutral-900/50"
552
+ >
553
+ <div className="p-3">
554
+ <div className="flex items-center gap-3">
555
+ <div className="cursor-grab active:cursor-grabbing text-neutral-600 hover:text-neutral-400">
556
+ <GripVertical className="w-4 h-4" />
557
+ </div>
558
+
559
+ <div className={`flex-shrink-0 ${getStatusColor(item.status)}`}>
560
+ {getStatusIcon(item.status)}
561
+ </div>
562
+
563
+ <div className="flex-1 min-w-0">
564
+ <div className="flex items-center gap-2">
565
+ <span className="font-medium text-white truncate">
566
+ {repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : item.url}
567
+ </span>
568
+ {item.result && (
569
+ <span className="text-xs px-2 py-0.5 rounded-full bg-green-500/10 text-green-400 border border-green-500/20">
570
+ {item.result.tools.length} tools
571
+ </span>
572
+ )}
573
+ </div>
574
+ {item.status === 'converting' && typeof item.progress === 'number' && (
575
+ <div className="mt-1 h-1 bg-neutral-800 rounded-full overflow-hidden">
576
+ <div
577
+ className="h-full bg-blue-500 rounded-full transition-all"
578
+ style={{ width: `${item.progress}%` }}
579
+ />
580
+ </div>
581
+ )}
582
+ {item.error && (
583
+ <p className="text-xs text-red-400 mt-1 truncate">
584
+ {item.error.error}
585
+ </p>
586
+ )}
587
+ </div>
588
+
589
+ <div className="flex items-center gap-1">
590
+ {item.result && (
591
+ <button
592
+ onClick={() => setExpandedItem(isExpanded ? null : item.id)}
593
+ className="p-2 rounded-lg hover:bg-white/5 text-neutral-400 hover:text-white transition-colors"
594
+ title="View details"
595
+ >
596
+ {isExpanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
597
+ </button>
598
+ )}
599
+ <button
600
+ onClick={() => copyUrl(item.url)}
601
+ className="p-2 rounded-lg hover:bg-white/5 text-neutral-400 hover:text-white transition-colors"
602
+ title="Copy URL"
603
+ >
604
+ {copiedUrl === item.url ? (
605
+ <Check className="w-4 h-4 text-green-400" />
606
+ ) : (
607
+ <Copy className="w-4 h-4" />
608
+ )}
609
+ </button>
610
+ <a
611
+ href={item.url}
612
+ target="_blank"
613
+ rel="noopener noreferrer"
614
+ className="p-2 rounded-lg hover:bg-white/5 text-neutral-400 hover:text-white transition-colors"
615
+ title="Open on GitHub"
616
+ >
617
+ <ExternalLink className="w-4 h-4" />
618
+ </a>
619
+ {state !== 'running' && (
620
+ <button
621
+ onClick={() => removeItem(item.id)}
622
+ className="p-2 rounded-lg hover:bg-red-500/10 text-neutral-400 hover:text-red-400 transition-colors"
623
+ title="Remove"
624
+ >
625
+ <Trash2 className="w-4 h-4" />
626
+ </button>
627
+ )}
628
+ </div>
629
+ </div>
630
+
631
+ {/* Expanded details */}
632
+ <AnimatePresence>
633
+ {isExpanded && item.result && (
634
+ <motion.div
635
+ initial={{ height: 0, opacity: 0 }}
636
+ animate={{ height: 'auto', opacity: 1 }}
637
+ exit={{ height: 0, opacity: 0 }}
638
+ className="mt-3 pt-3 border-t border-neutral-800 overflow-hidden"
639
+ >
640
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
641
+ <div>
642
+ <div className="text-neutral-500">Tools</div>
643
+ <div className="text-white font-medium">{item.result.tools.length}</div>
644
+ </div>
645
+ <div>
646
+ <div className="text-neutral-500">Language</div>
647
+ <div className="text-white font-medium">{item.result.repository.language || 'Unknown'}</div>
648
+ </div>
649
+ <div>
650
+ <div className="text-neutral-500">Stars</div>
651
+ <div className="text-white font-medium">{item.result.repository.stars?.toLocaleString() || '-'}</div>
652
+ </div>
653
+ <div>
654
+ <div className="text-neutral-500">Last Updated</div>
655
+ <div className="text-white font-medium">
656
+ {item.result.repository.lastUpdated
657
+ ? new Date(item.result.repository.lastUpdated).toLocaleDateString()
658
+ : '-'
659
+ }
660
+ </div>
661
+ </div>
662
+ </div>
663
+ <div className="mt-3 flex flex-wrap gap-2">
664
+ {item.result.tools.slice(0, 6).map((tool, idx) => (
665
+ <span
666
+ key={idx}
667
+ className="text-xs px-2 py-1 rounded bg-white/5 border border-neutral-700 text-neutral-300 font-mono"
668
+ >
669
+ {tool.name}
670
+ </span>
671
+ ))}
672
+ {item.result.tools.length > 6 && (
673
+ <span className="text-xs px-2 py-1 text-neutral-500">
674
+ +{item.result.tools.length - 6} more
675
+ </span>
676
+ )}
677
+ </div>
678
+ </motion.div>
679
+ )}
680
+ </AnimatePresence>
681
+ </div>
682
+ </Reorder.Item>
683
+ );
684
+ })}
685
+ </AnimatePresence>
686
+ </Reorder.Group>
687
+ </motion.div>
688
+ )}
689
+
690
+ {/* Empty state */}
691
+ {items.length === 0 && (
692
+ <motion.div
693
+ initial={{ opacity: 0 }}
694
+ animate={{ opacity: 1 }}
695
+ className="rounded-xl border border-dashed border-neutral-700 p-8 text-center"
696
+ >
697
+ <Archive className="w-12 h-12 text-neutral-600 mx-auto mb-4" />
698
+ <h3 className="text-lg font-medium text-white mb-2">No repositories added</h3>
699
+ <p className="text-neutral-400 text-sm mb-4">
700
+ Add GitHub repository URLs to convert them to MCP servers in batch
701
+ </p>
702
+ <button
703
+ onClick={() => EXAMPLE_REPOS.forEach(url => addUrl(url))}
704
+ className="text-sm text-white underline hover:no-underline"
705
+ >
706
+ Add example repositories
707
+ </button>
708
+ </motion.div>
709
+ )}
710
+
711
+ {/* Action buttons */}
712
+ {items.length > 0 && (
713
+ <motion.div
714
+ initial={{ opacity: 0, y: 20 }}
715
+ animate={{ opacity: 1, y: 0 }}
716
+ transition={{ delay: 0.3 }}
717
+ className="flex items-center justify-between"
718
+ >
719
+ <div className="flex items-center gap-3">
720
+ {state === 'idle' || state === 'complete' || state === 'paused' ? (
721
+ <button
722
+ onClick={startBatch}
723
+ disabled={stats.pending === 0}
724
+ className="flex items-center gap-2 px-6 py-3 bg-white text-black rounded-xl font-medium hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
725
+ >
726
+ <Zap className="w-5 h-5" />
727
+ {state === 'paused' ? 'Resume' : stats.pending < stats.total ? 'Continue' : 'Start Batch'}
728
+ </button>
729
+ ) : (
730
+ <button
731
+ onClick={pauseBatch}
732
+ className="flex items-center gap-2 px-6 py-3 bg-white/10 text-white rounded-xl font-medium hover:bg-white/20 transition-colors"
733
+ >
734
+ <Pause className="w-5 h-5" />
735
+ Pause
736
+ </button>
737
+ )}
738
+
739
+ {stats.success > 0 && (
740
+ <button
741
+ onClick={downloadAllResults}
742
+ className="flex items-center gap-2 px-4 py-3 bg-white/10 text-white rounded-xl font-medium hover:bg-white/20 transition-colors"
743
+ >
744
+ <Download className="w-5 h-5" />
745
+ Download All ({stats.success})
746
+ </button>
747
+ )}
748
+ </div>
749
+
750
+ <div className="text-sm text-neutral-500">
751
+ Max {maxConcurrent} concurrent conversions
752
+ </div>
753
+ </motion.div>
754
+ )}
755
+
756
+ {/* Completion message */}
757
+ <AnimatePresence>
758
+ {state === 'complete' && stats.success > 0 && (
759
+ <motion.div
760
+ initial={{ opacity: 0, scale: 0.95 }}
761
+ animate={{ opacity: 1, scale: 1 }}
762
+ exit={{ opacity: 0, scale: 0.95 }}
763
+ className="rounded-xl border border-green-500/30 bg-green-500/10 p-6 text-center"
764
+ >
765
+ <div className="w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center mx-auto mb-4">
766
+ <Check className="w-8 h-8 text-green-400" />
767
+ </div>
768
+ <h3 className="text-xl font-semibold text-white mb-2">Batch Conversion Complete!</h3>
769
+ <p className="text-neutral-400 mb-4">
770
+ Successfully converted {stats.success} repositories with {stats.totalTools} total tools.
771
+ {stats.error > 0 && ` (${stats.error} failed)`}
772
+ </p>
773
+ <button
774
+ onClick={downloadAllResults}
775
+ className="flex items-center gap-2 px-6 py-3 bg-white text-black rounded-xl font-medium hover:bg-neutral-200 transition-colors mx-auto"
776
+ >
777
+ <Download className="w-5 h-5" />
778
+ Download All Results
779
+ </button>
780
+ </motion.div>
781
+ )}
782
+ </AnimatePresence>
783
+ </div>
784
+ );
785
+ }