crca 1.4.0__py3-none-any.whl

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 (501) hide show
  1. .github/ISSUE_TEMPLATE/bug_report.md +65 -0
  2. .github/ISSUE_TEMPLATE/feature_request.md +41 -0
  3. .github/PULL_REQUEST_TEMPLATE.md +20 -0
  4. .github/workflows/publish-manual.yml +61 -0
  5. .github/workflows/publish.yml +64 -0
  6. .gitignore +214 -0
  7. CRCA.py +4156 -0
  8. LICENSE +201 -0
  9. MANIFEST.in +43 -0
  10. PKG-INFO +5035 -0
  11. README.md +4959 -0
  12. __init__.py +17 -0
  13. branches/CRCA-Q.py +2728 -0
  14. branches/crca_cg/corposwarm.py +9065 -0
  15. branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
  16. branches/crca_cg/package.json +5 -0
  17. branches/crca_cg/test_bolt_integration.py +446 -0
  18. branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
  19. branches/crca_cg/test_new_features.py +163 -0
  20. branches/crca_sd/__init__.py +149 -0
  21. branches/crca_sd/crca_sd_core.py +770 -0
  22. branches/crca_sd/crca_sd_governance.py +1325 -0
  23. branches/crca_sd/crca_sd_mpc.py +1130 -0
  24. branches/crca_sd/crca_sd_realtime.py +1844 -0
  25. branches/crca_sd/crca_sd_tui.py +1133 -0
  26. crca-1.4.0.dist-info/METADATA +5035 -0
  27. crca-1.4.0.dist-info/RECORD +501 -0
  28. crca-1.4.0.dist-info/WHEEL +4 -0
  29. crca-1.4.0.dist-info/licenses/LICENSE +201 -0
  30. docs/CRCA-Q.md +2333 -0
  31. examples/config.yaml.example +25 -0
  32. examples/crca_sd_example.py +513 -0
  33. examples/data_broker_example.py +294 -0
  34. examples/logistics_corporation.py +861 -0
  35. examples/palantir_example.py +299 -0
  36. examples/policy_bench.py +934 -0
  37. examples/pridnestrovia-sd.py +705 -0
  38. examples/pridnestrovia_realtime.py +1902 -0
  39. prompts/__init__.py +10 -0
  40. prompts/default_crca.py +101 -0
  41. pyproject.toml +151 -0
  42. requirements.txt +76 -0
  43. schemas/__init__.py +43 -0
  44. schemas/mcpSchemas.py +51 -0
  45. schemas/policy.py +458 -0
  46. templates/__init__.py +38 -0
  47. templates/base_specialized_agent.py +195 -0
  48. templates/drift_detection.py +325 -0
  49. templates/examples/causal_agent_template.py +309 -0
  50. templates/examples/drag_drop_example.py +213 -0
  51. templates/examples/logistics_agent_template.py +207 -0
  52. templates/examples/trading_agent_template.py +206 -0
  53. templates/feature_mixins.py +253 -0
  54. templates/graph_management.py +442 -0
  55. templates/llm_integration.py +194 -0
  56. templates/module_registry.py +276 -0
  57. templates/mpc_planner.py +280 -0
  58. templates/policy_loop.py +1168 -0
  59. templates/prediction_framework.py +448 -0
  60. templates/statistical_methods.py +778 -0
  61. tests/sanity.yml +31 -0
  62. tests/sanity_check +406 -0
  63. tests/test_core.py +47 -0
  64. tests/test_crca_excel.py +166 -0
  65. tests/test_crca_sd.py +780 -0
  66. tests/test_data_broker.py +424 -0
  67. tests/test_palantir.py +349 -0
  68. tools/__init__.py +38 -0
  69. tools/actuators.py +437 -0
  70. tools/bolt.diy/Dockerfile +103 -0
  71. tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
  72. tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
  73. tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
  74. tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
  75. tools/bolt.diy/app/components/@settings/index.ts +12 -0
  76. tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
  77. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
  78. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
  79. tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
  80. tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
  81. tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
  82. tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
  83. tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
  84. tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
  85. tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
  86. tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
  87. tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
  88. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
  89. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
  90. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
  91. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
  92. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
  93. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
  94. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
  95. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
  96. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
  97. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
  98. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
  99. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
  100. tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
  101. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
  102. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
  103. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
  104. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
  105. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
  106. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
  107. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
  108. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
  109. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
  110. tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
  111. tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
  112. tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
  113. tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
  114. tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
  115. tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
  116. tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
  117. tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
  118. tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
  119. tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
  120. tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
  121. tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
  122. tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
  123. tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
  124. tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
  125. tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
  126. tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
  127. tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
  128. tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
  129. tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
  130. tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
  131. tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
  132. tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
  133. tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
  134. tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
  135. tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
  136. tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
  137. tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
  138. tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
  139. tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
  140. tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
  141. tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
  142. tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
  143. tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
  144. tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
  145. tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
  146. tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
  147. tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
  148. tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
  149. tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
  150. tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
  151. tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
  152. tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
  153. tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
  154. tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
  155. tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
  156. tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
  157. tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
  158. tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
  159. tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
  160. tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
  161. tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
  162. tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
  163. tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
  164. tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
  165. tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
  166. tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
  167. tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
  168. tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
  169. tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
  170. tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
  171. tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
  172. tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
  173. tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
  174. tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
  175. tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
  176. tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
  177. tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
  178. tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
  179. tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
  180. tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
  181. tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
  182. tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
  183. tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
  184. tools/bolt.diy/app/components/header/Header.tsx +42 -0
  185. tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
  186. tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
  187. tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
  188. tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
  189. tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
  190. tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
  191. tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
  192. tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
  193. tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
  194. tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
  195. tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
  196. tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
  197. tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
  198. tools/bolt.diy/app/components/txt +1 -0
  199. tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
  200. tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
  201. tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
  202. tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
  203. tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
  204. tools/bolt.diy/app/components/ui/Button.tsx +46 -0
  205. tools/bolt.diy/app/components/ui/Card.tsx +55 -0
  206. tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
  207. tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
  208. tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
  209. tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
  210. tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
  211. tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
  212. tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
  213. tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
  214. tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
  215. tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
  216. tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
  217. tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
  218. tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
  219. tools/bolt.diy/app/components/ui/Input.tsx +22 -0
  220. tools/bolt.diy/app/components/ui/Label.tsx +20 -0
  221. tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
  222. tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
  223. tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
  224. tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
  225. tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
  226. tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
  227. tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
  228. tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
  229. tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
  230. tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
  231. tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
  232. tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
  233. tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
  234. tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
  235. tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
  236. tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
  237. tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
  238. tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
  239. tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
  240. tools/bolt.diy/app/components/ui/index.ts +38 -0
  241. tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
  242. tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
  243. tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
  244. tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
  245. tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
  246. tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
  247. tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
  248. tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
  249. tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
  250. tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
  251. tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
  252. tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
  253. tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
  254. tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
  255. tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
  256. tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
  257. tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
  258. tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
  259. tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
  260. tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
  261. tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
  262. tools/bolt.diy/app/entry.client.tsx +7 -0
  263. tools/bolt.diy/app/entry.server.tsx +80 -0
  264. tools/bolt.diy/app/root.tsx +156 -0
  265. tools/bolt.diy/app/routes/_index.tsx +175 -0
  266. tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
  267. tools/bolt.diy/app/routes/api.chat.ts +463 -0
  268. tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
  269. tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
  270. tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
  271. tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
  272. tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
  273. tools/bolt.diy/app/routes/api.git-info.ts +69 -0
  274. tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
  275. tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
  276. tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
  277. tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
  278. tools/bolt.diy/app/routes/api.github-template.ts +242 -0
  279. tools/bolt.diy/app/routes/api.github-user.ts +287 -0
  280. tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
  281. tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
  282. tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
  283. tools/bolt.diy/app/routes/api.health.ts +8 -0
  284. tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
  285. tools/bolt.diy/app/routes/api.mandate.ts +351 -0
  286. tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
  287. tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
  288. tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
  289. tools/bolt.diy/app/routes/api.models.ts +90 -0
  290. tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
  291. tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
  292. tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
  293. tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
  294. tools/bolt.diy/app/routes/api.supabase.ts +56 -0
  295. tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
  296. tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
  297. tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
  298. tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
  299. tools/bolt.diy/app/routes/api.update.ts +21 -0
  300. tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
  301. tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
  302. tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
  303. tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
  304. tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
  305. tools/bolt.diy/app/routes/git.tsx +25 -0
  306. tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
  307. tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
  308. tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
  309. tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
  310. tools/bolt.diy/app/styles/animations.scss +49 -0
  311. tools/bolt.diy/app/styles/components/code.scss +9 -0
  312. tools/bolt.diy/app/styles/components/editor.scss +135 -0
  313. tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
  314. tools/bolt.diy/app/styles/components/terminal.scss +3 -0
  315. tools/bolt.diy/app/styles/components/toast.scss +23 -0
  316. tools/bolt.diy/app/styles/diff-view.css +72 -0
  317. tools/bolt.diy/app/styles/index.scss +73 -0
  318. tools/bolt.diy/app/styles/variables.scss +255 -0
  319. tools/bolt.diy/app/styles/z-index.scss +37 -0
  320. tools/bolt.diy/app/types/GitHub.ts +182 -0
  321. tools/bolt.diy/app/types/GitLab.ts +103 -0
  322. tools/bolt.diy/app/types/actions.ts +85 -0
  323. tools/bolt.diy/app/types/artifact.ts +5 -0
  324. tools/bolt.diy/app/types/context.ts +26 -0
  325. tools/bolt.diy/app/types/design-scheme.ts +93 -0
  326. tools/bolt.diy/app/types/global.d.ts +13 -0
  327. tools/bolt.diy/app/types/mandate.ts +333 -0
  328. tools/bolt.diy/app/types/model.ts +25 -0
  329. tools/bolt.diy/app/types/netlify.ts +94 -0
  330. tools/bolt.diy/app/types/supabase.ts +54 -0
  331. tools/bolt.diy/app/types/template.ts +8 -0
  332. tools/bolt.diy/app/types/terminal.ts +9 -0
  333. tools/bolt.diy/app/types/theme.ts +1 -0
  334. tools/bolt.diy/app/types/vercel.ts +67 -0
  335. tools/bolt.diy/app/utils/buffer.ts +29 -0
  336. tools/bolt.diy/app/utils/classNames.ts +65 -0
  337. tools/bolt.diy/app/utils/constants.ts +147 -0
  338. tools/bolt.diy/app/utils/debounce.ts +13 -0
  339. tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
  340. tools/bolt.diy/app/utils/diff.spec.ts +11 -0
  341. tools/bolt.diy/app/utils/diff.ts +117 -0
  342. tools/bolt.diy/app/utils/easings.ts +3 -0
  343. tools/bolt.diy/app/utils/fileLocks.ts +96 -0
  344. tools/bolt.diy/app/utils/fileUtils.ts +121 -0
  345. tools/bolt.diy/app/utils/folderImport.ts +73 -0
  346. tools/bolt.diy/app/utils/formatSize.ts +12 -0
  347. tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
  348. tools/bolt.diy/app/utils/githubStats.ts +9 -0
  349. tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
  350. tools/bolt.diy/app/utils/logger.ts +162 -0
  351. tools/bolt.diy/app/utils/markdown.ts +155 -0
  352. tools/bolt.diy/app/utils/mobile.ts +4 -0
  353. tools/bolt.diy/app/utils/os.ts +4 -0
  354. tools/bolt.diy/app/utils/path.ts +19 -0
  355. tools/bolt.diy/app/utils/projectCommands.ts +197 -0
  356. tools/bolt.diy/app/utils/promises.ts +19 -0
  357. tools/bolt.diy/app/utils/react.ts +6 -0
  358. tools/bolt.diy/app/utils/sampler.ts +49 -0
  359. tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
  360. tools/bolt.diy/app/utils/shell.ts +384 -0
  361. tools/bolt.diy/app/utils/stacktrace.ts +27 -0
  362. tools/bolt.diy/app/utils/stripIndent.ts +23 -0
  363. tools/bolt.diy/app/utils/terminal.ts +11 -0
  364. tools/bolt.diy/app/utils/unreachable.ts +3 -0
  365. tools/bolt.diy/app/vite-env.d.ts +2 -0
  366. tools/bolt.diy/assets/entitlements.mac.plist +25 -0
  367. tools/bolt.diy/assets/icons/icon.icns +0 -0
  368. tools/bolt.diy/assets/icons/icon.ico +0 -0
  369. tools/bolt.diy/assets/icons/icon.png +0 -0
  370. tools/bolt.diy/bindings.js +78 -0
  371. tools/bolt.diy/bindings.sh +33 -0
  372. tools/bolt.diy/docker-compose.yaml +145 -0
  373. tools/bolt.diy/electron/main/index.ts +201 -0
  374. tools/bolt.diy/electron/main/tsconfig.json +30 -0
  375. tools/bolt.diy/electron/main/ui/menu.ts +29 -0
  376. tools/bolt.diy/electron/main/ui/window.ts +54 -0
  377. tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
  378. tools/bolt.diy/electron/main/utils/constants.ts +4 -0
  379. tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
  380. tools/bolt.diy/electron/main/utils/reload.ts +35 -0
  381. tools/bolt.diy/electron/main/utils/serve.ts +71 -0
  382. tools/bolt.diy/electron/main/utils/store.ts +3 -0
  383. tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
  384. tools/bolt.diy/electron/main/vite.config.ts +44 -0
  385. tools/bolt.diy/electron/preload/index.ts +22 -0
  386. tools/bolt.diy/electron/preload/tsconfig.json +7 -0
  387. tools/bolt.diy/electron/preload/vite.config.ts +31 -0
  388. tools/bolt.diy/electron-builder.yml +64 -0
  389. tools/bolt.diy/electron-update.yml +4 -0
  390. tools/bolt.diy/eslint.config.mjs +57 -0
  391. tools/bolt.diy/functions/[[path]].ts +12 -0
  392. tools/bolt.diy/icons/angular.svg +1 -0
  393. tools/bolt.diy/icons/astro.svg +8 -0
  394. tools/bolt.diy/icons/chat.svg +1 -0
  395. tools/bolt.diy/icons/expo-brand.svg +1 -0
  396. tools/bolt.diy/icons/expo.svg +4 -0
  397. tools/bolt.diy/icons/logo-text.svg +1 -0
  398. tools/bolt.diy/icons/logo.svg +4 -0
  399. tools/bolt.diy/icons/mcp.svg +1 -0
  400. tools/bolt.diy/icons/nativescript.svg +1 -0
  401. tools/bolt.diy/icons/netlify.svg +10 -0
  402. tools/bolt.diy/icons/nextjs.svg +1 -0
  403. tools/bolt.diy/icons/nuxt.svg +1 -0
  404. tools/bolt.diy/icons/qwik.svg +1 -0
  405. tools/bolt.diy/icons/react.svg +1 -0
  406. tools/bolt.diy/icons/remix.svg +24 -0
  407. tools/bolt.diy/icons/remotion.svg +1 -0
  408. tools/bolt.diy/icons/shadcn.svg +21 -0
  409. tools/bolt.diy/icons/slidev.svg +60 -0
  410. tools/bolt.diy/icons/solidjs.svg +1 -0
  411. tools/bolt.diy/icons/stars.svg +1 -0
  412. tools/bolt.diy/icons/svelte.svg +1 -0
  413. tools/bolt.diy/icons/typescript.svg +1 -0
  414. tools/bolt.diy/icons/vite.svg +1 -0
  415. tools/bolt.diy/icons/vue.svg +1 -0
  416. tools/bolt.diy/load-context.ts +9 -0
  417. tools/bolt.diy/notarize.cjs +31 -0
  418. tools/bolt.diy/package.json +218 -0
  419. tools/bolt.diy/playwright.config.preview.ts +35 -0
  420. tools/bolt.diy/pre-start.cjs +26 -0
  421. tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
  422. tools/bolt.diy/public/apple-touch-icon.png +0 -0
  423. tools/bolt.diy/public/favicon.ico +0 -0
  424. tools/bolt.diy/public/favicon.svg +4 -0
  425. tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
  426. tools/bolt.diy/public/icons/Anthropic.svg +4 -0
  427. tools/bolt.diy/public/icons/Cohere.svg +4 -0
  428. tools/bolt.diy/public/icons/Deepseek.svg +5 -0
  429. tools/bolt.diy/public/icons/Default.svg +4 -0
  430. tools/bolt.diy/public/icons/Google.svg +4 -0
  431. tools/bolt.diy/public/icons/Groq.svg +4 -0
  432. tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
  433. tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
  434. tools/bolt.diy/public/icons/LMStudio.svg +5 -0
  435. tools/bolt.diy/public/icons/Mistral.svg +4 -0
  436. tools/bolt.diy/public/icons/Ollama.svg +4 -0
  437. tools/bolt.diy/public/icons/OpenAI.svg +4 -0
  438. tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
  439. tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
  440. tools/bolt.diy/public/icons/Perplexity.svg +4 -0
  441. tools/bolt.diy/public/icons/Together.svg +4 -0
  442. tools/bolt.diy/public/icons/xAI.svg +5 -0
  443. tools/bolt.diy/public/inspector-script.js +292 -0
  444. tools/bolt.diy/public/logo-dark-styled.png +0 -0
  445. tools/bolt.diy/public/logo-dark.png +0 -0
  446. tools/bolt.diy/public/logo-light-styled.png +0 -0
  447. tools/bolt.diy/public/logo-light.png +0 -0
  448. tools/bolt.diy/public/logo.svg +15 -0
  449. tools/bolt.diy/public/social_preview_index.jpg +0 -0
  450. tools/bolt.diy/scripts/clean.js +45 -0
  451. tools/bolt.diy/scripts/electron-dev.mjs +181 -0
  452. tools/bolt.diy/scripts/setup-env.sh +41 -0
  453. tools/bolt.diy/scripts/update-imports.sh +7 -0
  454. tools/bolt.diy/scripts/update.sh +52 -0
  455. tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
  456. tools/bolt.diy/services/execution-governor/config.ts +42 -0
  457. tools/bolt.diy/services/execution-governor/index.ts +683 -0
  458. tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
  459. tools/bolt.diy/services/execution-governor/package.json +31 -0
  460. tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
  461. tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
  462. tools/bolt.diy/services/execution-governor/types.ts +145 -0
  463. tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
  464. tools/bolt.diy/services/headless-executor/executor.ts +210 -0
  465. tools/bolt.diy/services/headless-executor/index.ts +323 -0
  466. tools/bolt.diy/services/headless-executor/package.json +27 -0
  467. tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
  468. tools/bolt.diy/services/headless-executor/types.ts +38 -0
  469. tools/bolt.diy/test-workflows.sh +240 -0
  470. tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
  471. tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
  472. tools/bolt.diy/tests/mandates/complex.json +53 -0
  473. tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
  474. tools/bolt.diy/tests/mandates/simple.json +35 -0
  475. tools/bolt.diy/tsconfig.json +37 -0
  476. tools/bolt.diy/types/istextorbinary.d.ts +15 -0
  477. tools/bolt.diy/uno.config.ts +279 -0
  478. tools/bolt.diy/vite-electron.config.ts +76 -0
  479. tools/bolt.diy/vite.config.ts +112 -0
  480. tools/bolt.diy/worker-configuration.d.ts +22 -0
  481. tools/bolt.diy/wrangler.toml +6 -0
  482. tools/code_generator.py +461 -0
  483. tools/file_operations.py +465 -0
  484. tools/mandate_generator.py +337 -0
  485. tools/mcpClientUtils.py +1216 -0
  486. tools/sensors.py +285 -0
  487. utils/Agent_types.py +15 -0
  488. utils/AnyToStr.py +0 -0
  489. utils/HHCS.py +277 -0
  490. utils/__init__.py +30 -0
  491. utils/agent.py +3627 -0
  492. utils/aop.py +2948 -0
  493. utils/canonical.py +143 -0
  494. utils/conversation.py +1195 -0
  495. utils/doctrine_versioning +230 -0
  496. utils/formatter.py +474 -0
  497. utils/ledger.py +311 -0
  498. utils/out_types.py +16 -0
  499. utils/rollback.py +339 -0
  500. utils/router.py +929 -0
  501. utils/tui.py +1908 -0
@@ -0,0 +1,1393 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { toast } from 'react-toastify';
4
+ import { classNames } from '~/utils/classNames';
5
+ import { useStore } from '@nanostores/react';
6
+ import { netlifyConnection, updateNetlifyConnection, initializeNetlifyConnection } from '~/lib/stores/netlify';
7
+ import type { NetlifySite, NetlifyDeploy, NetlifyBuild, NetlifyUser } from '~/types/netlify';
8
+ import { Button } from '~/components/ui/Button';
9
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '~/components/ui/Collapsible';
10
+ import { formatDistanceToNow } from 'date-fns';
11
+ import { Badge } from '~/components/ui/Badge';
12
+
13
+ interface ConnectionTestResult {
14
+ status: 'success' | 'error' | 'testing';
15
+ message: string;
16
+ timestamp?: number;
17
+ }
18
+
19
+ interface SiteAction {
20
+ name: string;
21
+ icon: string;
22
+ action: (siteId: string) => Promise<void>;
23
+ requiresConfirmation?: boolean;
24
+ variant?: 'default' | 'destructive' | 'outline';
25
+ }
26
+
27
+ // Netlify logo SVG component
28
+ const NetlifyLogo = () => (
29
+ <svg viewBox="0 0 40 40" className="w-5 h-5">
30
+ <path
31
+ fill="currentColor"
32
+ d="M28.589 14.135l-.014-.006c-.008-.003-.016-.006-.023-.013a.11.11 0 0 1-.028-.093l.773-4.726 3.625 3.626-3.77 1.604a.083.083 0 0 1-.033.006h-.015c-.005-.003-.01-.007-.02-.017a1.716 1.716 0 0 0-.495-.381zm5.258-.288l3.876 3.876c.805.806 1.208 1.208 1.674 1.355a2 2 0 0 1 1.206 0c.466-.148.869-.55 1.674-1.356L8.73 28.73l2.349-3.643c.011-.018.022-.034.04-.047.025-.018.061-.01.091 0a2.434 2.434 0 0 0 1.638-.083c.027-.01.054-.017.075.002a.19.19 0 0 1 .028.032L21.95 38.05zM7.863 27.863L5.8 25.8l4.074-1.738a.084.084 0 0 1 .033-.007c.034 0 .054.034.072.065a2.91 2.91 0 0 0 .13.184l.013.016c.012.017.004.034-.008.05l-2.25 3.493zm-2.976-2.976l-2.61-2.61c-.444-.444-.766-.766-.99-1.043l7.936 1.646a.84.84 0 0 0 .03.005c.049.008.103.017.103.063 0 .05-.059.073-.109.092l-.023.01-4.337 1.837zM.831 19.892a2 2 0 0 1 .09-.495c.148-.466.55-.868 1.356-1.674l3.34-3.34a2175.525 2175.525 0 0 0 4.626 6.687c.027.036.057.076.026.106-.146.161-.292.337-.395.528a.16.16 0 0 1-.05.062c-.013.008-.027.005-.042.002H9.78L.831 19.892zm5.68-6.403l4.491-4.491c.422.185 1.958.834 3.332 1.414 1.04.44 1.988.84 2.286.97.03.012.057.024.07.054.008.018.004.041 0 .06a2.003 2.003 0 0 0 .523 1.828c.03.03 0 .073-.026.11l-.014.021-4.56 7.063c-.012.02-.023.037-.043.05-.024.015-.058.008-.086.001a2.274 2.274 0 0 0-.543-.074c-.164 0-.342.03-.522.063h-.001c-.02.003-.038.007-.054-.005a.21.21 0 0 1-.045-.051l-4.808-7.013zm5.398-5.398l5.814-5.814c.805-.805 1.208-1.208 1.674-1.355a2 2 0 0 1 1.206 0c.466.147.869.55 1.674 1.355l1.26 1.26-4.135 6.404a.155.155 0 0 1-.041.048c-.025.017-.06.01-.09 0a2.097 2.097 0 0 0-1.92.37c-.027.028-.067.012-.101-.003-.54-.235-4.74-2.01-5.341-2.265zm12.506-3.676l3.818 3.818-.92 5.698v.015a.135.135 0 0 1-.008.038c-.01.02-.03.024-.05.03a1.83 1.83 0 0 0-.548.273.154.154 0 0 0-.02.017c-.011.012-.022.023-.04.025a.114.114 0 0 1-.043-.007l-5.818-2.472-.011-.005c-.037-.015-.081-.033-.081-.071a2.198 2.198 0 0 0-.31-.915c-.028-.046-.059-.094-.035-.141l4.066-6.303zm-3.932 8.606l5.454 2.31c.03.014.063.027.076.058a.106.106 0 0 1 0 .057c-.016.08-.03.171-.03.263v.153c0 .038-.039.054-.075.069l-.011.004c-.864.369-12.13 5.173-12.147 5.173-.017 0-.035 0-.052-.017-.03-.03 0-.072.027-.11a.76.76 0 0 0 .014-.02l4.482-6.94.008-.012c.026-.042.056-.089.104-.089l.045.007c.102.014.192.027.283.027.68 0 1.31-.331 1.69-.897a.16.16 0 0 1 .034-.04c.027-.02.067-.01.098.004zm-6.246 9.185l12.28-5.237s.018 0 .035.017c.067.067.124.112.179.154l.027.017c.025.014.05.03.052.056 0 .01 0 .016-.002.025L25.756 23.7l-.004.026c-.007.05-.014.107-.061.107a1.729 1.729 0 0 0-1.373.847l-.005.008c-.014.023-.027.045-.05.057-.021.01-.048.006-.07.001l-9.793-2.02c-.01-.002-.152-.519-.163-.52z"
33
+ />
34
+ </svg>
35
+ );
36
+
37
+ export default function NetlifyTab() {
38
+ const connection = useStore(netlifyConnection);
39
+ const [tokenInput, setTokenInput] = useState('');
40
+ const [fetchingStats, setFetchingStats] = useState(false);
41
+ const [sites, setSites] = useState<NetlifySite[]>([]);
42
+ const [deploys, setDeploys] = useState<NetlifyDeploy[]>([]);
43
+ const [deploymentCount, setDeploymentCount] = useState(0);
44
+ const [lastUpdated, setLastUpdated] = useState('');
45
+ const [isStatsOpen, setIsStatsOpen] = useState(false);
46
+ const [activeSiteIndex, setActiveSiteIndex] = useState(0);
47
+ const [isSitesExpanded, setIsSitesExpanded] = useState(false);
48
+ const [isDeploysExpanded, setIsDeploysExpanded] = useState(false);
49
+ const [isActionLoading, setIsActionLoading] = useState(false);
50
+ const [isConnecting, setIsConnecting] = useState(false);
51
+ const [connectionTest, setConnectionTest] = useState<ConnectionTestResult | null>(null);
52
+
53
+ // Connection testing function
54
+ const testConnection = async () => {
55
+ if (!connection.token) {
56
+ setConnectionTest({
57
+ status: 'error',
58
+ message: 'No token provided',
59
+ timestamp: Date.now(),
60
+ });
61
+ return;
62
+ }
63
+
64
+ setConnectionTest({
65
+ status: 'testing',
66
+ message: 'Testing connection...',
67
+ });
68
+
69
+ try {
70
+ const response = await fetch('https://api.netlify.com/api/v1/user', {
71
+ headers: {
72
+ Authorization: `Bearer ${connection.token}`,
73
+ },
74
+ });
75
+
76
+ if (response.ok) {
77
+ const data = (await response.json()) as any;
78
+ setConnectionTest({
79
+ status: 'success',
80
+ message: `Connected successfully as ${data.email}`,
81
+ timestamp: Date.now(),
82
+ });
83
+ } else {
84
+ setConnectionTest({
85
+ status: 'error',
86
+ message: `Connection failed: ${response.status} ${response.statusText}`,
87
+ timestamp: Date.now(),
88
+ });
89
+ }
90
+ } catch (error) {
91
+ setConnectionTest({
92
+ status: 'error',
93
+ message: `Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
94
+ timestamp: Date.now(),
95
+ });
96
+ }
97
+ };
98
+
99
+ // Site actions
100
+ const siteActions: SiteAction[] = [
101
+ {
102
+ name: 'Clear Cache',
103
+ icon: 'i-ph:arrows-clockwise',
104
+ action: async (siteId: string) => {
105
+ try {
106
+ setIsActionLoading(true);
107
+
108
+ // Try to get site details first to check for build hooks
109
+ const siteResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}`, {
110
+ headers: {
111
+ Authorization: `Bearer ${connection.token}`,
112
+ },
113
+ });
114
+
115
+ if (!siteResponse.ok) {
116
+ const errorText = await siteResponse.text();
117
+
118
+ if (siteResponse.status === 404) {
119
+ toast.error('Site not found. This may be a free account limitation.');
120
+ return;
121
+ }
122
+
123
+ throw new Error(`Failed to get site details: ${errorText}`);
124
+ }
125
+
126
+ const siteData = (await siteResponse.json()) as any;
127
+
128
+ // Check if this looks like a free account (limited features)
129
+ const isFreeAccount = !siteData.plan || siteData.plan === 'free' || siteData.plan === 'starter';
130
+
131
+ // If site has build hooks, try triggering a build instead
132
+ if (siteData.build_settings && siteData.build_settings.repo_url) {
133
+ // Try to trigger a build by making a POST to the site's build endpoint
134
+ const buildResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/builds`, {
135
+ method: 'POST',
136
+ headers: {
137
+ Authorization: `Bearer ${connection.token}`,
138
+ 'Content-Type': 'application/json',
139
+ },
140
+ body: JSON.stringify({
141
+ clear_cache: true,
142
+ }),
143
+ });
144
+
145
+ if (buildResponse.ok) {
146
+ toast.success('Build triggered with cache clear');
147
+ return;
148
+ } else if (buildResponse.status === 422) {
149
+ // Often indicates free account limitation
150
+ toast.warning('Build trigger failed. This feature may not be available on free accounts.');
151
+ return;
152
+ }
153
+ }
154
+
155
+ // Fallback: Try the standard cache purge endpoint
156
+ const cacheResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/purge_cache`, {
157
+ method: 'POST',
158
+ headers: {
159
+ Authorization: `Bearer ${connection.token}`,
160
+ },
161
+ });
162
+
163
+ if (!cacheResponse.ok) {
164
+ if (cacheResponse.status === 404) {
165
+ if (isFreeAccount) {
166
+ toast.warning('Cache purge not available on free accounts. Try triggering a build instead.');
167
+ } else {
168
+ toast.error('Cache purge endpoint not found. This feature may not be available.');
169
+ }
170
+
171
+ return;
172
+ }
173
+
174
+ const errorText = await cacheResponse.text();
175
+ throw new Error(`Cache purge failed: ${errorText}`);
176
+ }
177
+
178
+ toast.success('Site cache cleared successfully');
179
+ } catch (err: unknown) {
180
+ const error = err instanceof Error ? err.message : 'Unknown error';
181
+ toast.error(`Failed to clear site cache: ${error}`);
182
+ } finally {
183
+ setIsActionLoading(false);
184
+ }
185
+ },
186
+ },
187
+ {
188
+ name: 'Manage Environment',
189
+ icon: 'i-ph:gear',
190
+ action: async (siteId: string) => {
191
+ try {
192
+ setIsActionLoading(true);
193
+
194
+ // Get site info first to check account type
195
+ const siteResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}`, {
196
+ headers: {
197
+ Authorization: `Bearer ${connection.token}`,
198
+ },
199
+ });
200
+
201
+ if (!siteResponse.ok) {
202
+ throw new Error('Failed to get site details');
203
+ }
204
+
205
+ const siteData = (await siteResponse.json()) as any;
206
+ const isFreeAccount = !siteData.plan || siteData.plan === 'free' || siteData.plan === 'starter';
207
+
208
+ // Get environment variables
209
+ const envResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/env`, {
210
+ headers: {
211
+ Authorization: `Bearer ${connection.token}`,
212
+ },
213
+ });
214
+
215
+ if (envResponse.ok) {
216
+ const envVars = (await envResponse.json()) as any[];
217
+ toast.success(`Environment variables loaded: ${envVars.length} variables`);
218
+ } else if (envResponse.status === 404) {
219
+ if (isFreeAccount) {
220
+ toast.info('Environment variables management is limited on free accounts');
221
+ } else {
222
+ toast.info('Site has no environment variables configured');
223
+ }
224
+ } else {
225
+ const errorText = await envResponse.text();
226
+ toast.error(`Failed to load environment variables: ${errorText}`);
227
+ }
228
+ } catch (err: unknown) {
229
+ const error = err instanceof Error ? err.message : 'Unknown error';
230
+ toast.error(`Failed to load environment variables: ${error}`);
231
+ } finally {
232
+ setIsActionLoading(false);
233
+ }
234
+ },
235
+ },
236
+ {
237
+ name: 'Trigger Build',
238
+ icon: 'i-ph:rocket-launch',
239
+ action: async (siteId: string) => {
240
+ try {
241
+ setIsActionLoading(true);
242
+
243
+ const buildResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/builds`, {
244
+ method: 'POST',
245
+ headers: {
246
+ Authorization: `Bearer ${connection.token}`,
247
+ 'Content-Type': 'application/json',
248
+ },
249
+ });
250
+
251
+ if (!buildResponse.ok) {
252
+ throw new Error('Failed to trigger build');
253
+ }
254
+
255
+ const buildData = (await buildResponse.json()) as any;
256
+ toast.success(`Build triggered successfully! ID: ${buildData.id}`);
257
+ } catch (err: unknown) {
258
+ const error = err instanceof Error ? err.message : 'Unknown error';
259
+ toast.error(`Failed to trigger build: ${error}`);
260
+ } finally {
261
+ setIsActionLoading(false);
262
+ }
263
+ },
264
+ },
265
+ {
266
+ name: 'View Functions',
267
+ icon: 'i-ph:code',
268
+ action: async (siteId: string) => {
269
+ try {
270
+ setIsActionLoading(true);
271
+
272
+ // Get site info first to check account type
273
+ const siteResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}`, {
274
+ headers: {
275
+ Authorization: `Bearer ${connection.token}`,
276
+ },
277
+ });
278
+
279
+ if (!siteResponse.ok) {
280
+ throw new Error('Failed to get site details');
281
+ }
282
+
283
+ const siteData = (await siteResponse.json()) as any;
284
+ const isFreeAccount = !siteData.plan || siteData.plan === 'free' || siteData.plan === 'starter';
285
+
286
+ const functionsResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/functions`, {
287
+ headers: {
288
+ Authorization: `Bearer ${connection.token}`,
289
+ },
290
+ });
291
+
292
+ if (functionsResponse.ok) {
293
+ const functions = (await functionsResponse.json()) as any[];
294
+ toast.success(`Site has ${functions.length} serverless functions`);
295
+ } else if (functionsResponse.status === 404) {
296
+ if (isFreeAccount) {
297
+ toast.info('Functions may be limited or unavailable on free accounts');
298
+ } else {
299
+ toast.info('Site has no serverless functions');
300
+ }
301
+ } else {
302
+ const errorText = await functionsResponse.text();
303
+ toast.error(`Failed to load functions: ${errorText}`);
304
+ }
305
+ } catch (err: unknown) {
306
+ const error = err instanceof Error ? err.message : 'Unknown error';
307
+ toast.error(`Failed to load functions: ${error}`);
308
+ } finally {
309
+ setIsActionLoading(false);
310
+ }
311
+ },
312
+ },
313
+ {
314
+ name: 'Site Analytics',
315
+ icon: 'i-ph:chart-bar',
316
+ action: async (siteId: string) => {
317
+ try {
318
+ setIsActionLoading(true);
319
+
320
+ // Get site info first to check account type
321
+ const siteResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}`, {
322
+ headers: {
323
+ Authorization: `Bearer ${connection.token}`,
324
+ },
325
+ });
326
+
327
+ if (!siteResponse.ok) {
328
+ throw new Error('Failed to get site details');
329
+ }
330
+
331
+ const siteData = (await siteResponse.json()) as any;
332
+ const isFreeAccount = !siteData.plan || siteData.plan === 'free' || siteData.plan === 'starter';
333
+
334
+ // Get site traffic data (if available)
335
+ const analyticsResponse = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/traffic`, {
336
+ headers: {
337
+ Authorization: `Bearer ${connection.token}`,
338
+ },
339
+ });
340
+
341
+ if (analyticsResponse.ok) {
342
+ await analyticsResponse.json(); // Analytics data received
343
+ toast.success('Site analytics loaded successfully');
344
+ } else if (analyticsResponse.status === 404) {
345
+ if (isFreeAccount) {
346
+ toast.info('Analytics not available on free accounts. Showing basic site info instead.');
347
+ }
348
+
349
+ // Fallback to basic site info
350
+ toast.info(`Site: ${siteData.name} - Status: ${siteData.state || 'Unknown'}`);
351
+ } else {
352
+ const errorText = await analyticsResponse.text();
353
+
354
+ if (isFreeAccount) {
355
+ toast.info(
356
+ 'Analytics unavailable on free accounts. Site info: ' +
357
+ `${siteData.name} (${siteData.state || 'Unknown'})`,
358
+ );
359
+ } else {
360
+ toast.error(`Failed to load analytics: ${errorText}`);
361
+ }
362
+ }
363
+ } catch (err: unknown) {
364
+ const error = err instanceof Error ? err.message : 'Unknown error';
365
+ toast.error(`Failed to load site analytics: ${error}`);
366
+ } finally {
367
+ setIsActionLoading(false);
368
+ }
369
+ },
370
+ },
371
+ {
372
+ name: 'Delete Site',
373
+ icon: 'i-ph:trash',
374
+ action: async (siteId: string) => {
375
+ try {
376
+ const response = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}`, {
377
+ method: 'DELETE',
378
+ headers: {
379
+ Authorization: `Bearer ${connection.token}`,
380
+ },
381
+ });
382
+
383
+ if (!response.ok) {
384
+ throw new Error('Failed to delete site');
385
+ }
386
+
387
+ toast.success('Site deleted successfully');
388
+ fetchNetlifyStats(connection.token);
389
+ } catch (err: unknown) {
390
+ const error = err instanceof Error ? err.message : 'Unknown error';
391
+ toast.error(`Failed to delete site: ${error}`);
392
+ }
393
+ },
394
+ requiresConfirmation: true,
395
+ variant: 'destructive',
396
+ },
397
+ ];
398
+
399
+ // Deploy management functions
400
+ const handleDeploy = async (siteId: string, deployId: string, action: 'lock' | 'unlock' | 'publish') => {
401
+ try {
402
+ setIsActionLoading(true);
403
+
404
+ const endpoint =
405
+ action === 'publish'
406
+ ? `https://api.netlify.com/api/v1/sites/${siteId}/deploys/${deployId}/restore`
407
+ : `https://api.netlify.com/api/v1/deploys/${deployId}/${action}`;
408
+
409
+ const response = await fetch(endpoint, {
410
+ method: 'POST',
411
+ headers: {
412
+ Authorization: `Bearer ${connection.token}`,
413
+ },
414
+ });
415
+
416
+ if (!response.ok) {
417
+ throw new Error(`Failed to ${action} deploy`);
418
+ }
419
+
420
+ toast.success(`Deploy ${action}ed successfully`);
421
+ fetchNetlifyStats(connection.token);
422
+ } catch (err: unknown) {
423
+ const error = err instanceof Error ? err.message : 'Unknown error';
424
+ toast.error(`Failed to ${action} deploy: ${error}`);
425
+ } finally {
426
+ setIsActionLoading(false);
427
+ }
428
+ };
429
+
430
+ useEffect(() => {
431
+ // Initialize connection with environment token if available
432
+ initializeNetlifyConnection();
433
+ }, []);
434
+
435
+ useEffect(() => {
436
+ // Check if we have a connection with a token but no stats
437
+ if (connection.user && connection.token && (!connection.stats || !connection.stats.sites)) {
438
+ fetchNetlifyStats(connection.token);
439
+ }
440
+
441
+ // Update local state from connection
442
+ if (connection.stats) {
443
+ setSites(connection.stats.sites || []);
444
+ setDeploys(connection.stats.deploys || []);
445
+ setDeploymentCount(connection.stats.deploys?.length || 0);
446
+ setLastUpdated(connection.stats.lastDeployTime || '');
447
+ }
448
+ }, [connection]);
449
+
450
+ const handleConnect = async () => {
451
+ if (!tokenInput) {
452
+ toast.error('Please enter a Netlify API token');
453
+ return;
454
+ }
455
+
456
+ setIsConnecting(true);
457
+
458
+ try {
459
+ const response = await fetch('https://api.netlify.com/api/v1/user', {
460
+ headers: {
461
+ Authorization: `Bearer ${tokenInput}`,
462
+ },
463
+ });
464
+
465
+ if (!response.ok) {
466
+ throw new Error(`HTTP error! Status: ${response.status}`);
467
+ }
468
+
469
+ const userData = (await response.json()) as NetlifyUser;
470
+
471
+ // Update the connection store
472
+ updateNetlifyConnection({
473
+ user: userData,
474
+ token: tokenInput,
475
+ });
476
+
477
+ toast.success('Connected to Netlify successfully');
478
+
479
+ // Fetch stats after successful connection
480
+ fetchNetlifyStats(tokenInput);
481
+ } catch (error) {
482
+ console.error('Error connecting to Netlify:', error);
483
+ toast.error(`Failed to connect to Netlify: ${error instanceof Error ? error.message : 'Unknown error'}`);
484
+ } finally {
485
+ setIsConnecting(false);
486
+ setTokenInput('');
487
+ }
488
+ };
489
+
490
+ const handleDisconnect = () => {
491
+ // Clear from localStorage
492
+ localStorage.removeItem('netlify_connection');
493
+
494
+ // Remove cookies
495
+ document.cookie = 'netlifyToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
496
+
497
+ // Update the store
498
+ updateNetlifyConnection({ user: null, token: '' });
499
+ setConnectionTest(null);
500
+ toast.success('Disconnected from Netlify');
501
+ };
502
+
503
+ const fetchNetlifyStats = async (token: string) => {
504
+ setFetchingStats(true);
505
+
506
+ try {
507
+ // Fetch sites
508
+ const sitesResponse = await fetch('https://api.netlify.com/api/v1/sites', {
509
+ headers: {
510
+ Authorization: `Bearer ${token}`,
511
+ },
512
+ });
513
+
514
+ if (!sitesResponse.ok) {
515
+ throw new Error(`Failed to fetch sites: ${sitesResponse.statusText}`);
516
+ }
517
+
518
+ const sitesData = (await sitesResponse.json()) as NetlifySite[];
519
+ setSites(sitesData);
520
+
521
+ // Fetch deploys and builds for ALL sites
522
+ const allDeploysData: NetlifyDeploy[] = [];
523
+ const allBuildsData: NetlifyBuild[] = [];
524
+ let lastDeployTime = '';
525
+ let totalDeploymentCount = 0;
526
+
527
+ if (sitesData && sitesData.length > 0) {
528
+ // Process sites in batches to avoid overwhelming the API
529
+ const batchSize = 3;
530
+ const siteBatches = [];
531
+
532
+ for (let i = 0; i < sitesData.length; i += batchSize) {
533
+ siteBatches.push(sitesData.slice(i, i + batchSize));
534
+ }
535
+
536
+ for (const batch of siteBatches) {
537
+ const batchPromises = batch.map(async (site) => {
538
+ try {
539
+ // Fetch deploys for this site
540
+ const deploysResponse = await fetch(
541
+ `https://api.netlify.com/api/v1/sites/${site.id}/deploys?per_page=20`,
542
+ {
543
+ headers: {
544
+ Authorization: `Bearer ${token}`,
545
+ },
546
+ },
547
+ );
548
+
549
+ let siteDeploys: NetlifyDeploy[] = [];
550
+
551
+ if (deploysResponse.ok) {
552
+ siteDeploys = (await deploysResponse.json()) as NetlifyDeploy[];
553
+ }
554
+
555
+ // Fetch builds for this site
556
+ const buildsResponse = await fetch(`https://api.netlify.com/api/v1/sites/${site.id}/builds?per_page=10`, {
557
+ headers: {
558
+ Authorization: `Bearer ${token}`,
559
+ },
560
+ });
561
+
562
+ let siteBuilds: NetlifyBuild[] = [];
563
+
564
+ if (buildsResponse.ok) {
565
+ siteBuilds = (await buildsResponse.json()) as NetlifyBuild[];
566
+ }
567
+
568
+ return { site, deploys: siteDeploys, builds: siteBuilds };
569
+ } catch (error) {
570
+ console.error(`Failed to fetch data for site ${site.name}:`, error);
571
+ return { site, deploys: [], builds: [] };
572
+ }
573
+ });
574
+
575
+ const batchResults = await Promise.all(batchPromises);
576
+
577
+ for (const result of batchResults) {
578
+ allDeploysData.push(...result.deploys);
579
+ allBuildsData.push(...result.builds);
580
+ totalDeploymentCount += result.deploys.length;
581
+ }
582
+
583
+ // Small delay between batches
584
+ if (batch !== siteBatches[siteBatches.length - 1]) {
585
+ await new Promise((resolve) => setTimeout(resolve, 200));
586
+ }
587
+ }
588
+
589
+ // Sort deploys by creation date (newest first)
590
+ allDeploysData.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
591
+
592
+ // Set the most recent deploy time
593
+ if (allDeploysData.length > 0) {
594
+ lastDeployTime = allDeploysData[0].created_at;
595
+ setLastUpdated(lastDeployTime);
596
+ }
597
+
598
+ setDeploys(allDeploysData);
599
+ setDeploymentCount(totalDeploymentCount);
600
+ }
601
+
602
+ // Update the stats in the store
603
+ updateNetlifyConnection({
604
+ stats: {
605
+ sites: sitesData,
606
+ deploys: allDeploysData,
607
+ builds: allBuildsData,
608
+ lastDeployTime,
609
+ totalSites: sitesData.length,
610
+ totalDeploys: totalDeploymentCount,
611
+ totalBuilds: allBuildsData.length,
612
+ },
613
+ });
614
+
615
+ toast.success('Netlify stats updated');
616
+ } catch (error) {
617
+ console.error('Error fetching Netlify stats:', error);
618
+ toast.error(`Failed to fetch Netlify stats: ${error instanceof Error ? error.message : 'Unknown error'}`);
619
+ } finally {
620
+ setFetchingStats(false);
621
+ }
622
+ };
623
+
624
+ const renderStats = () => {
625
+ if (!connection.user || !connection.stats) {
626
+ return null;
627
+ }
628
+
629
+ return (
630
+ <div className="mt-6">
631
+ <Collapsible open={isStatsOpen} onOpenChange={setIsStatsOpen}>
632
+ <CollapsibleTrigger asChild>
633
+ <div className="flex items-center justify-between p-4 rounded-lg bg-bolt-elements-background dark:bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive/70 dark:hover:border-bolt-elements-borderColorActive/70 transition-all duration-200 cursor-pointer">
634
+ <div className="flex items-center gap-2">
635
+ <div className="i-ph:chart-bar w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
636
+ <span className="text-sm font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
637
+ Netlify Stats
638
+ </span>
639
+ </div>
640
+ <div
641
+ className={classNames(
642
+ 'i-ph:caret-down w-4 h-4 transform transition-transform duration-200 text-bolt-elements-textSecondary',
643
+ isStatsOpen ? 'rotate-180' : '',
644
+ )}
645
+ />
646
+ </div>
647
+ </CollapsibleTrigger>
648
+ <CollapsibleContent className="overflow-hidden">
649
+ <div className="space-y-4 mt-4">
650
+ {/* Netlify Overview Dashboard */}
651
+ <div className="mb-6 p-4 bg-bolt-elements-background-depth-1 rounded-lg border border-bolt-elements-borderColor">
652
+ <h4 className="text-sm font-medium text-bolt-elements-textPrimary mb-3">Netlify Overview</h4>
653
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
654
+ <div className="text-center">
655
+ <div className="text-2xl font-bold text-bolt-elements-textPrimary">
656
+ {connection.stats.totalSites}
657
+ </div>
658
+ <div className="text-xs text-bolt-elements-textSecondary">Total Sites</div>
659
+ </div>
660
+ <div className="text-center">
661
+ <div className="text-2xl font-bold text-bolt-elements-textPrimary">
662
+ {connection.stats.totalDeploys || deploymentCount}
663
+ </div>
664
+ <div className="text-xs text-bolt-elements-textSecondary">Total Deployments</div>
665
+ </div>
666
+ <div className="text-center">
667
+ <div className="text-2xl font-bold text-bolt-elements-textPrimary">
668
+ {connection.stats.totalBuilds || 0}
669
+ </div>
670
+ <div className="text-xs text-bolt-elements-textSecondary">Total Builds</div>
671
+ </div>
672
+ <div className="text-center">
673
+ <div className="text-2xl font-bold text-bolt-elements-textPrimary">
674
+ {sites.filter((site) => site.published_deploy?.state === 'ready').length}
675
+ </div>
676
+ <div className="text-xs text-bolt-elements-textSecondary">Live Sites</div>
677
+ </div>
678
+ </div>
679
+ </div>
680
+
681
+ {/* Advanced Analytics */}
682
+ <div className="mb-6 space-y-4">
683
+ <h4 className="text-sm font-medium text-bolt-elements-textPrimary">Deployment Analytics</h4>
684
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
685
+ <div className="bg-bolt-elements-background-depth-2 p-3 rounded-lg border border-bolt-elements-borderColor">
686
+ <h6 className="text-xs font-medium text-bolt-elements-textPrimary flex items-center gap-2 mb-2">
687
+ <div className="i-ph:chart-pie w-4 h-4 text-bolt-elements-item-contentAccent" />
688
+ Success Rate
689
+ </h6>
690
+ <div className="space-y-1">
691
+ {(() => {
692
+ const successfulDeploys = deploys.filter((deploy) => deploy.state === 'ready').length;
693
+ const failedDeploys = deploys.filter((deploy) => deploy.state === 'error').length;
694
+ const successRate =
695
+ deploys.length > 0 ? Math.round((successfulDeploys / deploys.length) * 100) : 0;
696
+
697
+ return [
698
+ { label: 'Success Rate', value: `${successRate}%` },
699
+ { label: 'Successful', value: successfulDeploys },
700
+ { label: 'Failed', value: failedDeploys },
701
+ ];
702
+ })().map((item, idx) => (
703
+ <div key={idx} className="flex justify-between text-xs">
704
+ <span className="text-bolt-elements-textSecondary">{item.label}:</span>
705
+ <span className="text-bolt-elements-textPrimary font-medium">{item.value}</span>
706
+ </div>
707
+ ))}
708
+ </div>
709
+ </div>
710
+
711
+ <div className="bg-bolt-elements-background-depth-2 p-3 rounded-lg border border-bolt-elements-borderColor">
712
+ <h6 className="text-xs font-medium text-bolt-elements-textPrimary flex items-center gap-2 mb-2">
713
+ <div className="i-ph:clock w-4 h-4 text-bolt-elements-item-contentAccent" />
714
+ Recent Activity
715
+ </h6>
716
+ <div className="space-y-1">
717
+ {(() => {
718
+ const now = Date.now();
719
+ const last24Hours = deploys.filter(
720
+ (deploy) => now - new Date(deploy.created_at).getTime() < 24 * 60 * 60 * 1000,
721
+ ).length;
722
+ const last7Days = deploys.filter(
723
+ (deploy) => now - new Date(deploy.created_at).getTime() < 7 * 24 * 60 * 60 * 1000,
724
+ ).length;
725
+ const activeSites = sites.filter((site) => {
726
+ const lastDeploy = site.published_deploy?.published_at;
727
+ return lastDeploy && now - new Date(lastDeploy).getTime() < 7 * 24 * 60 * 60 * 1000;
728
+ }).length;
729
+
730
+ return [
731
+ { label: 'Last 24 hours', value: last24Hours },
732
+ { label: 'Last 7 days', value: last7Days },
733
+ { label: 'Active sites', value: activeSites },
734
+ ];
735
+ })().map((item, idx) => (
736
+ <div key={idx} className="flex justify-between text-xs">
737
+ <span className="text-bolt-elements-textSecondary">{item.label}:</span>
738
+ <span className="text-bolt-elements-textPrimary font-medium">{item.value}</span>
739
+ </div>
740
+ ))}
741
+ </div>
742
+ </div>
743
+ </div>
744
+ </div>
745
+
746
+ {/* Site Health Metrics */}
747
+ <div className="mb-6">
748
+ <h4 className="text-sm font-medium text-bolt-elements-textPrimary mb-2">Site Health Overview</h4>
749
+ <div className="grid grid-cols-2 md:grid-cols-5 gap-4">
750
+ {(() => {
751
+ const healthySites = sites.filter(
752
+ (site) => site.published_deploy?.state === 'ready' && site.ssl_url,
753
+ ).length;
754
+ const sslEnabled = sites.filter((site) => !!site.ssl_url).length;
755
+ const customDomain = sites.filter((site) => !!site.custom_domain).length;
756
+ const needsAttention = sites.filter(
757
+ (site) => site.published_deploy?.state === 'error' || !site.published_deploy,
758
+ ).length;
759
+ const buildingSites = sites.filter(
760
+ (site) =>
761
+ site.published_deploy?.state === 'building' || site.published_deploy?.state === 'processing',
762
+ ).length;
763
+
764
+ return [
765
+ {
766
+ label: 'Healthy',
767
+ value: healthySites,
768
+ icon: 'i-ph:heart',
769
+ color: 'text-green-500',
770
+ bgColor: 'bg-green-100 dark:bg-green-900/20',
771
+ textColor: 'text-green-800 dark:text-green-400',
772
+ },
773
+ {
774
+ label: 'SSL Enabled',
775
+ value: sslEnabled,
776
+ icon: 'i-ph:lock',
777
+ color: 'text-blue-500',
778
+ bgColor: 'bg-blue-100 dark:bg-blue-900/20',
779
+ textColor: 'text-blue-800 dark:text-blue-400',
780
+ },
781
+ {
782
+ label: 'Custom Domain',
783
+ value: customDomain,
784
+ icon: 'i-ph:globe',
785
+ color: 'text-purple-500',
786
+ bgColor: 'bg-purple-100 dark:bg-purple-900/20',
787
+ textColor: 'text-purple-800 dark:text-purple-400',
788
+ },
789
+ {
790
+ label: 'Building',
791
+ value: buildingSites,
792
+ icon: 'i-ph:gear',
793
+ color: 'text-yellow-500',
794
+ bgColor: 'bg-yellow-100 dark:bg-yellow-900/20',
795
+ textColor: 'text-yellow-800 dark:text-yellow-400',
796
+ },
797
+ {
798
+ label: 'Needs Attention',
799
+ value: needsAttention,
800
+ icon: 'i-ph:warning',
801
+ color: 'text-red-500',
802
+ bgColor: 'bg-red-100 dark:bg-red-900/20',
803
+ textColor: 'text-red-800 dark:text-red-400',
804
+ },
805
+ ];
806
+ })().map((metric, index) => (
807
+ <div
808
+ key={index}
809
+ className={`flex flex-col p-3 rounded-lg border border-bolt-elements-borderColor ${metric.bgColor}`}
810
+ >
811
+ <div className="flex items-center gap-2 mb-1">
812
+ <div className={`${metric.icon} w-4 h-4 ${metric.color}`} />
813
+ <span className="text-xs text-bolt-elements-textSecondary">{metric.label}</span>
814
+ </div>
815
+ <span className={`text-lg font-medium ${metric.textColor}`}>{metric.value}</span>
816
+ </div>
817
+ ))}
818
+ </div>
819
+ </div>
820
+
821
+ <div className="flex flex-wrap items-center gap-4">
822
+ <Badge
823
+ variant="outline"
824
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
825
+ >
826
+ <div className="i-ph:buildings w-4 h-4 text-bolt-elements-item-contentAccent" />
827
+ <span>{connection.stats.totalSites} Sites</span>
828
+ </Badge>
829
+ <Badge
830
+ variant="outline"
831
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
832
+ >
833
+ <div className="i-ph:rocket-launch w-4 h-4 text-bolt-elements-item-contentAccent" />
834
+ <span>{deploymentCount} Deployments</span>
835
+ </Badge>
836
+ <Badge
837
+ variant="outline"
838
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
839
+ >
840
+ <div className="i-ph:hammer w-4 h-4 text-bolt-elements-item-contentAccent" />
841
+ <span>{connection.stats.totalBuilds || 0} Builds</span>
842
+ </Badge>
843
+ {lastUpdated && (
844
+ <Badge
845
+ variant="outline"
846
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
847
+ >
848
+ <div className="i-ph:clock w-4 h-4 text-bolt-elements-item-contentAccent" />
849
+ <span>Updated {formatDistanceToNow(new Date(lastUpdated))} ago</span>
850
+ </Badge>
851
+ )}
852
+ </div>
853
+ {sites.length > 0 && (
854
+ <div className="mt-4 space-y-4">
855
+ <div className="bg-bolt-elements-background dark:bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor rounded-lg p-4">
856
+ <div className="flex items-center justify-between mb-4">
857
+ <div className="flex items-center gap-4">
858
+ <h4 className="text-sm font-medium flex items-center gap-2 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
859
+ <div className="i-ph:buildings w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
860
+ Your Sites ({sites.length})
861
+ </h4>
862
+ {sites.length > 8 && (
863
+ <button
864
+ onClick={() => setIsSitesExpanded(!isSitesExpanded)}
865
+ className="text-xs text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors"
866
+ >
867
+ {isSitesExpanded ? 'Show Less' : `Show All ${sites.length}`}
868
+ </button>
869
+ )}
870
+ </div>
871
+ <Button
872
+ variant="outline"
873
+ size="sm"
874
+ onClick={() => fetchNetlifyStats(connection.token)}
875
+ disabled={fetchingStats}
876
+ className="flex items-center gap-2 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive/10"
877
+ >
878
+ <div
879
+ className={classNames(
880
+ 'i-ph:arrows-clockwise w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent',
881
+ { 'animate-spin': fetchingStats },
882
+ )}
883
+ />
884
+ {fetchingStats ? 'Refreshing...' : 'Refresh'}
885
+ </Button>
886
+ </div>
887
+ <div className="space-y-3">
888
+ {(isSitesExpanded ? sites : sites.slice(0, 8)).map((site, index) => (
889
+ <div
890
+ key={site.id}
891
+ className={classNames(
892
+ 'bg-bolt-elements-background dark:bg-bolt-elements-background-depth-1 border rounded-lg p-4 transition-all cursor-pointer',
893
+ activeSiteIndex === index
894
+ ? 'border-bolt-elements-item-contentAccent bg-bolt-elements-item-backgroundActive/10'
895
+ : 'border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive/70',
896
+ )}
897
+ onClick={() => {
898
+ setActiveSiteIndex(index);
899
+ }}
900
+ >
901
+ <div className="flex items-center justify-between">
902
+ <div className="flex items-center gap-2">
903
+ <div className="i-ph:cloud w-5 h-5 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
904
+ <span className="font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
905
+ {site.name}
906
+ </span>
907
+ </div>
908
+ <div className="flex items-center gap-2">
909
+ <Badge
910
+ variant={site.published_deploy?.state === 'ready' ? 'default' : 'destructive'}
911
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
912
+ >
913
+ {site.published_deploy?.state === 'ready' ? (
914
+ <div className="i-ph:check-circle w-4 h-4 text-green-500" />
915
+ ) : (
916
+ <div className="i-ph:x-circle w-4 h-4 text-red-500" />
917
+ )}
918
+ <span className="text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
919
+ {site.published_deploy?.state || 'Unknown'}
920
+ </span>
921
+ </Badge>
922
+ </div>
923
+ </div>
924
+
925
+ <div className="mt-3 space-y-2">
926
+ <div className="flex items-center gap-2">
927
+ <a
928
+ href={site.ssl_url || site.url}
929
+ target="_blank"
930
+ rel="noopener noreferrer"
931
+ className="text-sm flex items-center gap-1 transition-colors text-bolt-elements-link-text hover:text-bolt-elements-link-textHover dark:text-white dark:hover:text-bolt-elements-link-textHover"
932
+ onClick={(e) => e.stopPropagation()}
933
+ >
934
+ <div className="i-ph:cloud w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
935
+ <span className="underline decoration-1 underline-offset-2">
936
+ {site.ssl_url || site.url}
937
+ </span>
938
+ </a>
939
+ </div>
940
+ <div className="flex items-center gap-4 text-xs text-bolt-elements-textSecondary">
941
+ {site.published_deploy?.framework && (
942
+ <div className="flex items-center gap-1">
943
+ <div className="i-ph:cube w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
944
+ <span>{site.published_deploy.framework}</span>
945
+ </div>
946
+ )}
947
+ {site.custom_domain && (
948
+ <div className="flex items-center gap-1">
949
+ <div className="i-ph:globe w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
950
+ <span>Custom Domain</span>
951
+ </div>
952
+ )}
953
+ {site.branch && (
954
+ <div className="flex items-center gap-1">
955
+ <div className="i-ph:code w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
956
+ <span>{site.branch}</span>
957
+ </div>
958
+ )}
959
+ </div>
960
+ </div>
961
+
962
+ {activeSiteIndex === index && (
963
+ <>
964
+ <div className="mt-4 pt-3 border-t border-bolt-elements-borderColor">
965
+ <div className="flex items-center gap-2">
966
+ {siteActions.map((action) => (
967
+ <Button
968
+ key={action.name}
969
+ variant={action.variant || 'outline'}
970
+ size="sm"
971
+ onClick={async (e) => {
972
+ e.stopPropagation();
973
+
974
+ if (action.requiresConfirmation) {
975
+ if (!confirm(`Are you sure you want to ${action.name.toLowerCase()}?`)) {
976
+ return;
977
+ }
978
+ }
979
+
980
+ setIsActionLoading(true);
981
+ await action.action(site.id);
982
+ setIsActionLoading(false);
983
+ }}
984
+ disabled={isActionLoading}
985
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
986
+ >
987
+ <div
988
+ className={`${action.icon} w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent`}
989
+ />
990
+ {action.name}
991
+ </Button>
992
+ ))}
993
+ </div>
994
+ </div>
995
+ {site.published_deploy && (
996
+ <div className="mt-3 text-sm">
997
+ <div className="flex items-center gap-1">
998
+ <div className="i-ph:clock w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
999
+ <span className="text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1000
+ Published {formatDistanceToNow(new Date(site.published_deploy.published_at))} ago
1001
+ </span>
1002
+ </div>
1003
+ {site.published_deploy.branch && (
1004
+ <div className="flex items-center gap-1 mt-1">
1005
+ <div className="i-ph:code w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1006
+ <span className="text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1007
+ Branch: {site.published_deploy.branch}
1008
+ </span>
1009
+ </div>
1010
+ )}
1011
+ </div>
1012
+ )}
1013
+ </>
1014
+ )}
1015
+ </div>
1016
+ ))}
1017
+ </div>
1018
+ </div>
1019
+ {deploys.length > 0 && (
1020
+ <div className="bg-bolt-elements-background dark:bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor rounded-lg p-4">
1021
+ <div className="flex items-center justify-between mb-3">
1022
+ <div className="flex items-center gap-4">
1023
+ <h4 className="text-sm font-medium flex items-center gap-2 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
1024
+ <div className="i-ph:buildings w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1025
+ All Deployments ({deploys.length})
1026
+ </h4>
1027
+ {deploys.length > 10 && (
1028
+ <button
1029
+ onClick={() => setIsDeploysExpanded(!isDeploysExpanded)}
1030
+ className="text-xs text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors"
1031
+ >
1032
+ {isDeploysExpanded ? 'Show Less' : `Show All ${deploys.length}`}
1033
+ </button>
1034
+ )}
1035
+ </div>
1036
+ </div>
1037
+ <div className="space-y-2">
1038
+ {(isDeploysExpanded ? deploys : deploys.slice(0, 10)).map((deploy) => (
1039
+ <div
1040
+ key={deploy.id}
1041
+ className="bg-bolt-elements-background dark:bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor rounded-lg p-3"
1042
+ >
1043
+ <div className="flex items-center justify-between">
1044
+ <div className="flex items-center gap-2">
1045
+ <Badge
1046
+ variant={
1047
+ deploy.state === 'ready'
1048
+ ? 'default'
1049
+ : deploy.state === 'error'
1050
+ ? 'destructive'
1051
+ : 'outline'
1052
+ }
1053
+ className="flex items-center gap-1"
1054
+ >
1055
+ {deploy.state === 'ready' ? (
1056
+ <div className="i-ph:check-circle w-4 h-4 text-green-500" />
1057
+ ) : deploy.state === 'error' ? (
1058
+ <div className="i-ph:x-circle w-4 h-4 text-red-500" />
1059
+ ) : (
1060
+ <div className="i-ph:buildings w-4 h-4 text-bolt-elements-item-contentAccent" />
1061
+ )}
1062
+ <span className="text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
1063
+ {deploy.state}
1064
+ </span>
1065
+ </Badge>
1066
+ </div>
1067
+ <span className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1068
+ {formatDistanceToNow(new Date(deploy.created_at))} ago
1069
+ </span>
1070
+ </div>
1071
+ {deploy.branch && (
1072
+ <div className="mt-2 text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary flex items-center gap-1">
1073
+ <div className="i-ph:code w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1074
+ <span className="text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1075
+ Branch: {deploy.branch}
1076
+ </span>
1077
+ </div>
1078
+ )}
1079
+ {deploy.deploy_url && (
1080
+ <div className="mt-2 text-xs">
1081
+ <a
1082
+ href={deploy.deploy_url}
1083
+ target="_blank"
1084
+ rel="noopener noreferrer"
1085
+ className="flex items-center gap-1 transition-colors text-bolt-elements-link-text hover:text-bolt-elements-link-textHover dark:text-white dark:hover:text-bolt-elements-link-textHover"
1086
+ onClick={(e) => e.stopPropagation()}
1087
+ >
1088
+ <div className="i-ph:cloud w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1089
+ <span className="underline decoration-1 underline-offset-2">{deploy.deploy_url}</span>
1090
+ </a>
1091
+ </div>
1092
+ )}
1093
+ <div className="flex items-center gap-2 mt-2">
1094
+ <Button
1095
+ variant="outline"
1096
+ size="sm"
1097
+ onClick={() => {
1098
+ const siteForDeploy = sites.find((site) => site.id === deploy.site_id);
1099
+
1100
+ if (siteForDeploy) {
1101
+ handleDeploy(siteForDeploy.id, deploy.id, 'publish');
1102
+ }
1103
+ }}
1104
+ disabled={isActionLoading}
1105
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
1106
+ >
1107
+ <div className="i-ph:buildings w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1108
+ Publish
1109
+ </Button>
1110
+ {deploy.state === 'ready' ? (
1111
+ <Button
1112
+ variant="outline"
1113
+ size="sm"
1114
+ onClick={() => {
1115
+ const siteForDeploy = sites.find((site) => site.id === deploy.site_id);
1116
+
1117
+ if (siteForDeploy) {
1118
+ handleDeploy(siteForDeploy.id, deploy.id, 'lock');
1119
+ }
1120
+ }}
1121
+ disabled={isActionLoading}
1122
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
1123
+ >
1124
+ <div className="i-ph:lock-closed w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1125
+ Lock
1126
+ </Button>
1127
+ ) : (
1128
+ <Button
1129
+ variant="outline"
1130
+ size="sm"
1131
+ onClick={() => {
1132
+ const siteForDeploy = sites.find((site) => site.id === deploy.site_id);
1133
+
1134
+ if (siteForDeploy) {
1135
+ handleDeploy(siteForDeploy.id, deploy.id, 'unlock');
1136
+ }
1137
+ }}
1138
+ disabled={isActionLoading}
1139
+ className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
1140
+ >
1141
+ <div className="i-ph:lock-open w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1142
+ Unlock
1143
+ </Button>
1144
+ )}
1145
+ </div>
1146
+ </div>
1147
+ ))}
1148
+ </div>
1149
+ </div>
1150
+ )}
1151
+
1152
+ {/* Builds Section */}
1153
+ {connection.stats.builds && connection.stats.builds.length > 0 && (
1154
+ <div className="bg-bolt-elements-background dark:bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor rounded-lg p-4">
1155
+ <div className="flex items-center justify-between mb-3">
1156
+ <h4 className="text-sm font-medium flex items-center gap-2 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
1157
+ <div className="i-ph:hammer w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1158
+ Recent Builds ({connection.stats.builds.length})
1159
+ </h4>
1160
+ </div>
1161
+ <div className="space-y-2">
1162
+ {connection.stats.builds.slice(0, 8).map((build: any) => (
1163
+ <div
1164
+ key={build.id}
1165
+ className="bg-bolt-elements-background dark:bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor rounded-lg p-3"
1166
+ >
1167
+ <div className="flex items-center justify-between">
1168
+ <div className="flex items-center gap-2">
1169
+ <Badge variant={build.done ? 'default' : 'outline'} className="flex items-center gap-1">
1170
+ {build.done ? (
1171
+ <div className="i-ph:check-circle w-4 h-4 text-green-500" />
1172
+ ) : (
1173
+ <div className="i-ph:buildings w-4 h-4 text-bolt-elements-item-contentAccent" />
1174
+ )}
1175
+ <span className="text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
1176
+ {build.done ? 'Completed' : 'Building'}
1177
+ </span>
1178
+ </Badge>
1179
+ </div>
1180
+ <span className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1181
+ {formatDistanceToNow(new Date(build.created_at))} ago
1182
+ </span>
1183
+ </div>
1184
+ {build.commit_ref && (
1185
+ <div className="mt-2 text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary flex items-center gap-1">
1186
+ <div className="i-ph:code w-3 h-3 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1187
+ <span className="text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1188
+ {build.commit_ref.substring(0, 7)}
1189
+ </span>
1190
+ </div>
1191
+ )}
1192
+ </div>
1193
+ ))}
1194
+ </div>
1195
+ </div>
1196
+ )}
1197
+ </div>
1198
+ )}
1199
+ </div>
1200
+ </CollapsibleContent>
1201
+ </Collapsible>
1202
+ </div>
1203
+ );
1204
+ };
1205
+
1206
+ return (
1207
+ <div className="space-y-6">
1208
+ {/* Header */}
1209
+ <motion.div
1210
+ className="flex items-center justify-between gap-2"
1211
+ initial={{ opacity: 0, y: 20 }}
1212
+ animate={{ opacity: 1, y: 0 }}
1213
+ transition={{ delay: 0.1 }}
1214
+ >
1215
+ <div className="flex items-center gap-2">
1216
+ <div className="text-[#00AD9F]">
1217
+ <NetlifyLogo />
1218
+ </div>
1219
+ <h2 className="text-lg font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
1220
+ Netlify Integration
1221
+ </h2>
1222
+ </div>
1223
+ <div className="flex items-center gap-2">
1224
+ {connection.user && (
1225
+ <Button
1226
+ onClick={testConnection}
1227
+ disabled={connectionTest?.status === 'testing'}
1228
+ variant="outline"
1229
+ className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors"
1230
+ >
1231
+ {connectionTest?.status === 'testing' ? (
1232
+ <>
1233
+ <div className="i-ph:spinner-gap w-4 h-4 animate-spin" />
1234
+ Testing...
1235
+ </>
1236
+ ) : (
1237
+ <>
1238
+ <div className="i-ph:plug-charging w-4 h-4" />
1239
+ Test Connection
1240
+ </>
1241
+ )}
1242
+ </Button>
1243
+ )}
1244
+ </div>
1245
+ </motion.div>
1246
+
1247
+ <p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
1248
+ Connect and manage your Netlify sites with advanced deployment controls and site management
1249
+ </p>
1250
+
1251
+ {/* Connection Test Results */}
1252
+ {connectionTest && (
1253
+ <motion.div
1254
+ className={classNames('p-4 rounded-lg border', {
1255
+ 'bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-700':
1256
+ connectionTest.status === 'success',
1257
+ 'bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-700': connectionTest.status === 'error',
1258
+ 'bg-blue-50 border-blue-200 dark:bg-blue-900/20 dark:border-blue-700': connectionTest.status === 'testing',
1259
+ })}
1260
+ initial={{ opacity: 0, y: 10 }}
1261
+ animate={{ opacity: 1, y: 0 }}
1262
+ >
1263
+ <div className="flex items-center gap-2">
1264
+ {connectionTest.status === 'success' && (
1265
+ <div className="i-ph:check-circle w-5 h-5 text-green-600 dark:text-green-400" />
1266
+ )}
1267
+ {connectionTest.status === 'error' && (
1268
+ <div className="i-ph:warning-circle w-5 h-5 text-red-600 dark:text-red-400" />
1269
+ )}
1270
+ {connectionTest.status === 'testing' && (
1271
+ <div className="i-ph:spinner-gap w-5 h-5 animate-spin text-blue-600 dark:text-blue-400" />
1272
+ )}
1273
+ <span
1274
+ className={classNames('text-sm font-medium', {
1275
+ 'text-green-800 dark:text-green-200': connectionTest.status === 'success',
1276
+ 'text-red-800 dark:text-red-200': connectionTest.status === 'error',
1277
+ 'text-blue-800 dark:text-blue-200': connectionTest.status === 'testing',
1278
+ })}
1279
+ >
1280
+ {connectionTest.message}
1281
+ </span>
1282
+ </div>
1283
+ {connectionTest.timestamp && (
1284
+ <p className="text-xs text-gray-500 mt-1">{new Date(connectionTest.timestamp).toLocaleString()}</p>
1285
+ )}
1286
+ </motion.div>
1287
+ )}
1288
+
1289
+ {/* Main Connection Component */}
1290
+ <motion.div
1291
+ className="bg-bolt-elements-background dark:bg-bolt-elements-background border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor rounded-lg"
1292
+ initial={{ opacity: 0, y: 20 }}
1293
+ animate={{ opacity: 1, y: 0 }}
1294
+ transition={{ delay: 0.2 }}
1295
+ >
1296
+ <div className="p-6">
1297
+ {!connection.user ? (
1298
+ <div className="space-y-4">
1299
+ <div className="text-xs text-bolt-elements-textSecondary bg-bolt-elements-background-depth-1 dark:bg-bolt-elements-background-depth-1 p-3 rounded-lg mb-4">
1300
+ <p className="flex items-center gap-1 mb-1">
1301
+ <span className="i-ph:lightbulb w-3.5 h-3.5 text-bolt-elements-icon-success dark:text-bolt-elements-icon-success" />
1302
+ <span className="font-medium">Tip:</span> You can also set the{' '}
1303
+ <code className="px-1 py-0.5 bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-2 rounded">
1304
+ VITE_NETLIFY_ACCESS_TOKEN
1305
+ </code>{' '}
1306
+ environment variable to connect automatically.
1307
+ </p>
1308
+ </div>
1309
+
1310
+ <div>
1311
+ <label className="block text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary mb-2">
1312
+ API Token
1313
+ </label>
1314
+ <input
1315
+ type="password"
1316
+ value={tokenInput}
1317
+ onChange={(e) => setTokenInput(e.target.value)}
1318
+ placeholder="Enter your Netlify API token"
1319
+ className={classNames(
1320
+ 'w-full px-3 py-2 rounded-lg text-sm',
1321
+ 'bg-[#F8F8F8] dark:bg-[#1A1A1A]',
1322
+ 'border border-[#E5E5E5] dark:border-[#333333]',
1323
+ 'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary',
1324
+ 'focus:outline-none focus:ring-1 focus:ring-bolt-elements-borderColorActive',
1325
+ 'disabled:opacity-50',
1326
+ )}
1327
+ />
1328
+ <div className="mt-2 text-sm text-bolt-elements-textSecondary">
1329
+ <a
1330
+ href="https://app.netlify.com/user/applications#personal-access-tokens"
1331
+ target="_blank"
1332
+ rel="noopener noreferrer"
1333
+ className="text-bolt-elements-borderColorActive hover:underline inline-flex items-center gap-1"
1334
+ >
1335
+ Get your token
1336
+ <div className="i-ph:arrow-square-out w-4 h-4" />
1337
+ </a>
1338
+ </div>
1339
+ </div>
1340
+
1341
+ <div className="flex items-center justify-between">
1342
+ <button
1343
+ onClick={handleConnect}
1344
+ disabled={isConnecting || !tokenInput}
1345
+ className={classNames(
1346
+ 'px-4 py-2 rounded-lg text-sm flex items-center gap-2',
1347
+ 'bg-[#303030] text-white',
1348
+ 'hover:bg-[#5E41D0] hover:text-white',
1349
+ 'disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200',
1350
+ 'transform active:scale-95',
1351
+ )}
1352
+ >
1353
+ {isConnecting ? (
1354
+ <>
1355
+ <div className="i-ph:spinner-gap animate-spin" />
1356
+ Connecting...
1357
+ </>
1358
+ ) : (
1359
+ <>
1360
+ <div className="i-ph:plug-charging w-4 h-4" />
1361
+ Connect
1362
+ </>
1363
+ )}
1364
+ </button>
1365
+ </div>
1366
+ </div>
1367
+ ) : (
1368
+ <div className="space-y-4">
1369
+ <div className="flex items-center gap-3">
1370
+ <button
1371
+ onClick={handleDisconnect}
1372
+ className={classNames(
1373
+ 'px-4 py-2 rounded-lg text-sm flex items-center gap-2',
1374
+ 'bg-red-500 text-white',
1375
+ 'hover:bg-red-600',
1376
+ )}
1377
+ >
1378
+ <div className="i-ph:plug w-4 h-4" />
1379
+ Disconnect
1380
+ </button>
1381
+ <span className="text-sm text-bolt-elements-textSecondary flex items-center gap-1">
1382
+ <div className="i-ph:check-circle w-4 h-4 text-green-500" />
1383
+ Connected to Netlify
1384
+ </span>
1385
+ </div>
1386
+ {renderStats()}
1387
+ </div>
1388
+ )}
1389
+ </div>
1390
+ </motion.div>
1391
+ </div>
1392
+ );
1393
+ }