@swarmclawai/swarmclaw 0.2.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 (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,940 @@
1
+ 'use client'
2
+
3
+ import { useMemo, useState } from 'react'
4
+ import { api } from '@/lib/api-client'
5
+ import { useAppStore } from '@/stores/use-app-store'
6
+ import type { ProviderType, Credential } from '@/types'
7
+
8
+ type WizardProvider =
9
+ | 'anthropic'
10
+ | 'openai'
11
+ | 'google'
12
+ | 'deepseek'
13
+ | 'groq'
14
+ | 'together'
15
+ | 'mistral'
16
+ | 'xai'
17
+ | 'fireworks'
18
+ | 'ollama'
19
+ | 'openclaw'
20
+ type CheckState = 'idle' | 'checking' | 'ok' | 'error'
21
+
22
+ interface WizardProviderOption {
23
+ id: WizardProvider
24
+ name: string
25
+ description: string
26
+ requiresKey: boolean
27
+ supportsEndpoint: boolean
28
+ defaultEndpoint?: string
29
+ keyUrl?: string
30
+ keyLabel?: string
31
+ keyPlaceholder?: string
32
+ optionalKey?: boolean
33
+ badge?: string
34
+ icon: string
35
+ }
36
+
37
+ interface ProviderCheckResponse {
38
+ ok: boolean
39
+ message: string
40
+ normalizedEndpoint?: string
41
+ recommendedModel?: string
42
+ }
43
+
44
+ interface SetupDoctorCheck {
45
+ id: string
46
+ label: string
47
+ status: 'pass' | 'warn' | 'fail'
48
+ detail: string
49
+ required?: boolean
50
+ }
51
+
52
+ interface SetupDoctorResponse {
53
+ ok: boolean
54
+ summary: string
55
+ checks: SetupDoctorCheck[]
56
+ actions?: string[]
57
+ }
58
+
59
+ interface SetupWizardProps {
60
+ onComplete: () => void
61
+ }
62
+
63
+ const STARTER_AGENT_TOOLS = [
64
+ 'memory',
65
+ 'files',
66
+ 'web_search',
67
+ 'web_fetch',
68
+ 'browser',
69
+ 'manage_agents',
70
+ 'manage_tasks',
71
+ 'manage_schedules',
72
+ 'manage_skills',
73
+ 'manage_connectors',
74
+ 'manage_sessions',
75
+ 'manage_secrets',
76
+ 'manage_documents',
77
+ 'manage_webhooks',
78
+ 'claude_code',
79
+ 'codex_cli',
80
+ 'opencode_cli',
81
+ ]
82
+
83
+ const SWARMCLAW_ASSISTANT_PROMPT = `You are the default SwarmClaw assistant inside the SwarmClaw dashboard.
84
+
85
+ Primary objective:
86
+ - Help the user operate SwarmClaw itself before anything else.
87
+
88
+ When the user asks about SwarmClaw, prioritize concrete guidance with exact UI paths and commands:
89
+ - Sessions: create, configure provider/model, and run chats.
90
+ - Agents: create specialist agents/orchestrators, set provider/model, tools, and prompts.
91
+ - Providers: connect API keys/endpoints, troubleshoot auth/model issues.
92
+ - Tasks + Schedules: queue work and automate recurring runs.
93
+ - Skills + Connectors + Webhooks + Secrets + Memory: explain when to use each and how to configure safely.
94
+
95
+ Behavior:
96
+ - Be concise, direct, and action-oriented.
97
+ - If the request is ambiguous, ask one focused clarifying question.
98
+ - Prefer step-by-step instructions that can be executed immediately.
99
+ - When the user asks for direct execution (for example browsing, screenshots, research, or file edits), use available tools and return real results instead of only describing what to do.
100
+ - If a capability depends on provider/tool configuration, call that out explicitly.`
101
+
102
+ const PROVIDERS: WizardProviderOption[] = [
103
+ {
104
+ id: 'openai',
105
+ name: 'OpenAI',
106
+ description: 'Great default for most users. Fast, reliable GPT models.',
107
+ requiresKey: true,
108
+ supportsEndpoint: true,
109
+ defaultEndpoint: 'https://api.openai.com/v1',
110
+ keyUrl: 'https://platform.openai.com/api-keys',
111
+ keyLabel: 'platform.openai.com',
112
+ badge: 'Recommended',
113
+ icon: 'O',
114
+ },
115
+ {
116
+ id: 'anthropic',
117
+ name: 'Anthropic',
118
+ description: 'Claude models — strong for coding, analysis, and long-form reasoning.',
119
+ requiresKey: true,
120
+ supportsEndpoint: false,
121
+ keyUrl: 'https://console.anthropic.com/settings/keys',
122
+ keyLabel: 'console.anthropic.com',
123
+ icon: 'A',
124
+ },
125
+ {
126
+ id: 'google',
127
+ name: 'Google Gemini',
128
+ description: 'Gemini models with strong multimodal and coding support.',
129
+ requiresKey: true,
130
+ supportsEndpoint: false,
131
+ keyUrl: 'https://aistudio.google.com/app/apikey',
132
+ keyLabel: 'aistudio.google.com',
133
+ keyPlaceholder: 'AIza...',
134
+ icon: 'G',
135
+ },
136
+ {
137
+ id: 'deepseek',
138
+ name: 'DeepSeek',
139
+ description: 'High-value reasoning and coding models from DeepSeek.',
140
+ requiresKey: true,
141
+ supportsEndpoint: false,
142
+ keyUrl: 'https://platform.deepseek.com/api_keys',
143
+ keyLabel: 'platform.deepseek.com',
144
+ icon: 'D',
145
+ },
146
+ {
147
+ id: 'groq',
148
+ name: 'Groq',
149
+ description: 'Very fast inference with open and reasoning model options.',
150
+ requiresKey: true,
151
+ supportsEndpoint: false,
152
+ keyUrl: 'https://console.groq.com/keys',
153
+ keyLabel: 'console.groq.com',
154
+ icon: 'G',
155
+ },
156
+ {
157
+ id: 'together',
158
+ name: 'Together AI',
159
+ description: 'Broad catalog of open models with OpenAI-compatible APIs.',
160
+ requiresKey: true,
161
+ supportsEndpoint: false,
162
+ keyUrl: 'https://api.together.xyz/settings/api-keys',
163
+ keyLabel: 'api.together.xyz',
164
+ icon: 'T',
165
+ },
166
+ {
167
+ id: 'mistral',
168
+ name: 'Mistral AI',
169
+ description: 'Efficient frontier models with strong latency and quality.',
170
+ requiresKey: true,
171
+ supportsEndpoint: false,
172
+ keyUrl: 'https://console.mistral.ai/api-keys/',
173
+ keyLabel: 'console.mistral.ai',
174
+ icon: 'M',
175
+ },
176
+ {
177
+ id: 'xai',
178
+ name: 'xAI (Grok)',
179
+ description: 'Grok models for fast answers, coding, and analysis.',
180
+ requiresKey: true,
181
+ supportsEndpoint: false,
182
+ keyUrl: 'https://console.x.ai',
183
+ keyLabel: 'console.x.ai',
184
+ icon: 'X',
185
+ },
186
+ {
187
+ id: 'fireworks',
188
+ name: 'Fireworks AI',
189
+ description: 'Serverless and optimized open-model inference endpoints.',
190
+ requiresKey: true,
191
+ supportsEndpoint: false,
192
+ keyUrl: 'https://fireworks.ai/account/api-keys',
193
+ keyLabel: 'fireworks.ai',
194
+ icon: 'F',
195
+ },
196
+ {
197
+ id: 'openclaw',
198
+ name: 'OpenClaw',
199
+ description: 'Connect to your local or remote OpenClaw gateway (multi-OpenClaw ready).',
200
+ requiresKey: false,
201
+ supportsEndpoint: true,
202
+ defaultEndpoint: 'http://localhost:18789/v1',
203
+ optionalKey: true,
204
+ badge: 'OpenClaw',
205
+ icon: 'C',
206
+ },
207
+ {
208
+ id: 'ollama',
209
+ name: 'Ollama',
210
+ description: 'Run local open-source models. No API key required.',
211
+ requiresKey: false,
212
+ supportsEndpoint: true,
213
+ defaultEndpoint: 'http://localhost:11434',
214
+ badge: 'Local',
215
+ icon: 'L',
216
+ },
217
+ ]
218
+
219
+ const DEFAULT_AGENTS: Record<WizardProvider, { name: string; description: string; systemPrompt: string; model: string; tools: string[] }> = {
220
+ anthropic: {
221
+ name: 'Assistant',
222
+ description: 'A helpful Claude-powered assistant.',
223
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
224
+ model: 'claude-sonnet-4-6',
225
+ tools: STARTER_AGENT_TOOLS,
226
+ },
227
+ openai: {
228
+ name: 'Assistant',
229
+ description: 'A helpful GPT-powered assistant.',
230
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
231
+ model: 'gpt-4o',
232
+ tools: STARTER_AGENT_TOOLS,
233
+ },
234
+ google: {
235
+ name: 'Assistant',
236
+ description: 'A helpful Gemini-powered assistant.',
237
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
238
+ model: 'gemini-2.5-pro',
239
+ tools: STARTER_AGENT_TOOLS,
240
+ },
241
+ deepseek: {
242
+ name: 'Assistant',
243
+ description: 'A helpful DeepSeek-powered assistant.',
244
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
245
+ model: 'deepseek-chat',
246
+ tools: STARTER_AGENT_TOOLS,
247
+ },
248
+ groq: {
249
+ name: 'Assistant',
250
+ description: 'A low-latency assistant powered by Groq.',
251
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
252
+ model: 'llama-3.3-70b-versatile',
253
+ tools: STARTER_AGENT_TOOLS,
254
+ },
255
+ together: {
256
+ name: 'Assistant',
257
+ description: 'A helpful assistant powered by Together AI.',
258
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
259
+ model: 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8',
260
+ tools: STARTER_AGENT_TOOLS,
261
+ },
262
+ mistral: {
263
+ name: 'Assistant',
264
+ description: 'A helpful assistant powered by Mistral.',
265
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
266
+ model: 'mistral-large-latest',
267
+ tools: STARTER_AGENT_TOOLS,
268
+ },
269
+ xai: {
270
+ name: 'Assistant',
271
+ description: 'A helpful assistant powered by xAI Grok.',
272
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
273
+ model: 'grok-3',
274
+ tools: STARTER_AGENT_TOOLS,
275
+ },
276
+ fireworks: {
277
+ name: 'Assistant',
278
+ description: 'A helpful assistant powered by Fireworks AI.',
279
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
280
+ model: 'accounts/fireworks/models/deepseek-r1-0528',
281
+ tools: STARTER_AGENT_TOOLS,
282
+ },
283
+ ollama: {
284
+ name: 'Assistant',
285
+ description: 'A local assistant running through Ollama.',
286
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
287
+ model: 'llama3',
288
+ tools: STARTER_AGENT_TOOLS,
289
+ },
290
+ openclaw: {
291
+ name: 'OpenClaw Operator',
292
+ description: 'A manager agent for talking to and coordinating OpenClaw instances.',
293
+ systemPrompt: 'You are an operator focused on reliable execution, clear status updates, and task completion.',
294
+ model: 'default',
295
+ tools: STARTER_AGENT_TOOLS,
296
+ },
297
+ }
298
+
299
+ function SparkleIcon() {
300
+ return (
301
+ <div className="flex justify-center mb-6">
302
+ <div className="relative w-12 h-12">
303
+ <svg
304
+ width="48"
305
+ height="48"
306
+ viewBox="0 0 48 48"
307
+ fill="none"
308
+ className="text-accent-bright"
309
+ style={{ animation: 'sparkle-spin 8s linear infinite' }}
310
+ >
311
+ <path
312
+ d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
313
+ fill="currentColor"
314
+ opacity="0.9"
315
+ />
316
+ </svg>
317
+ <div className="absolute inset-0 blur-xl bg-accent-bright/20" />
318
+ </div>
319
+ </div>
320
+ )
321
+ }
322
+
323
+ function StepDots({ current, total }: { current: number; total: number }) {
324
+ return (
325
+ <div className="flex items-center justify-center gap-2 mb-8">
326
+ {Array.from({ length: total }, (_, i) => (
327
+ <div
328
+ key={i}
329
+ className={`h-1.5 rounded-full transition-all duration-300 ${
330
+ i === current
331
+ ? 'w-6 bg-accent-bright'
332
+ : i < current
333
+ ? 'w-1.5 bg-accent-bright/50'
334
+ : 'w-1.5 bg-white/10'
335
+ }`}
336
+ />
337
+ ))}
338
+ </div>
339
+ )
340
+ }
341
+
342
+ function SkipLink({ onClick }: { onClick: () => void }) {
343
+ return (
344
+ <button
345
+ onClick={onClick}
346
+ className="mt-8 text-[13px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none"
347
+ >
348
+ Skip setup for now
349
+ </button>
350
+ )
351
+ }
352
+
353
+ function ProviderBadge({ label }: { label?: string }) {
354
+ if (!label) return null
355
+ return (
356
+ <span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent-bright/15 text-accent-bright text-[10px] uppercase tracking-[0.08em] font-600">
357
+ {label}
358
+ </span>
359
+ )
360
+ }
361
+
362
+ function getOpenClawErrorHint(message: string): string | null {
363
+ const lower = message.toLowerCase()
364
+ if (lower.includes('timeout') || lower.includes('timed out'))
365
+ return 'Ensure the port is open and reachable from this machine.'
366
+ if (lower.includes('401') || lower.includes('unauthorized'))
367
+ return 'Check your gateway auth token.'
368
+ if (lower.includes('405') || lower.includes('method not allowed'))
369
+ return 'Enable chatCompletions in your OpenClaw config: openclaw config set gateway.http.endpoints.chatCompletions.enabled true'
370
+ if (lower.includes('econnrefused') || lower.includes('connection refused') || lower.includes('connect econnrefused'))
371
+ return 'Verify that the OpenClaw gateway is running on the target host.'
372
+ return null
373
+ }
374
+
375
+ export function SetupWizard({ onComplete }: SetupWizardProps) {
376
+ const [step, setStep] = useState(0)
377
+ const [provider, setProvider] = useState<WizardProvider | null>(null)
378
+ const [endpoint, setEndpoint] = useState('')
379
+ const [apiKey, setApiKey] = useState('')
380
+ const [credentialId, setCredentialId] = useState<string | null>(null)
381
+ const [checkState, setCheckState] = useState<CheckState>('idle')
382
+ const [checkMessage, setCheckMessage] = useState('')
383
+ const [doctorState, setDoctorState] = useState<'idle' | 'checking' | 'done' | 'error'>('idle')
384
+ const [doctorError, setDoctorError] = useState('')
385
+ const [doctorReport, setDoctorReport] = useState<SetupDoctorResponse | null>(null)
386
+ const [saving, setSaving] = useState(false)
387
+ const [error, setError] = useState('')
388
+
389
+ const [agentName, setAgentName] = useState('')
390
+ const [agentDescription, setAgentDescription] = useState('')
391
+ const [agentPrompt, setAgentPrompt] = useState('')
392
+ const [agentModel, setAgentModel] = useState('')
393
+
394
+ const selectedProvider = useMemo(
395
+ () => PROVIDERS.find((p) => p.id === provider) || null,
396
+ [provider],
397
+ )
398
+ const totalSteps = 3
399
+ const requiresKey = selectedProvider?.requiresKey || false
400
+ const supportsEndpoint = selectedProvider?.supportsEndpoint || false
401
+ const keyIsOptional = selectedProvider?.optionalKey || false
402
+ const requiresVerifiedConnection = provider === 'openclaw'
403
+
404
+ const skip = async () => {
405
+ try {
406
+ await api('PUT', '/settings', { setupCompleted: true })
407
+ } catch {
408
+ // Continue anyway.
409
+ }
410
+ onComplete()
411
+ }
412
+
413
+ const selectProvider = (next: WizardProvider) => {
414
+ const defaults = DEFAULT_AGENTS[next]
415
+ const meta = PROVIDERS.find((p) => p.id === next)
416
+
417
+ setProvider(next)
418
+ setEndpoint(meta?.defaultEndpoint || '')
419
+ setApiKey('')
420
+ setCredentialId(null)
421
+ setCheckState('idle')
422
+ setCheckMessage('')
423
+ setError('')
424
+
425
+ setAgentName(defaults.name)
426
+ setAgentDescription(defaults.description)
427
+ setAgentPrompt(defaults.systemPrompt)
428
+ setAgentModel(defaults.model)
429
+
430
+ setStep(1)
431
+ }
432
+
433
+ const runConnectionCheck = async (): Promise<boolean> => {
434
+ if (!provider || !selectedProvider) return false
435
+ if (requiresKey && !apiKey.trim()) {
436
+ setCheckState('error')
437
+ setCheckMessage('Please paste your API key first.')
438
+ return false
439
+ }
440
+
441
+ setCheckState('checking')
442
+ setCheckMessage('')
443
+ setError('')
444
+ try {
445
+ const result = await api<ProviderCheckResponse>('POST', '/setup/check-provider', {
446
+ provider,
447
+ apiKey: apiKey.trim() || undefined,
448
+ endpoint: supportsEndpoint ? endpoint.trim() || undefined : undefined,
449
+ model: agentModel.trim() || undefined,
450
+ })
451
+
452
+ if (result.normalizedEndpoint && supportsEndpoint) {
453
+ setEndpoint(result.normalizedEndpoint)
454
+ }
455
+ if (result.recommendedModel && provider) {
456
+ const currentModel = agentModel.trim()
457
+ const defaultModel = DEFAULT_AGENTS[provider].model
458
+ if (!currentModel || currentModel === defaultModel) {
459
+ setAgentModel(result.recommendedModel)
460
+ }
461
+ }
462
+ setCheckState(result.ok ? 'ok' : 'error')
463
+ setCheckMessage(result.message || (result.ok ? 'Connected successfully.' : 'Connection failed.'))
464
+ return !!result.ok
465
+ } catch (err: any) {
466
+ setCheckState('error')
467
+ setCheckMessage(err?.message || 'Connection check failed.')
468
+ return false
469
+ }
470
+ }
471
+
472
+ const runSetupDoctor = async () => {
473
+ setDoctorState('checking')
474
+ setDoctorError('')
475
+ try {
476
+ const report = await api<SetupDoctorResponse>('GET', '/setup/doctor')
477
+ setDoctorReport(report)
478
+ setDoctorState('done')
479
+ } catch (err: any) {
480
+ setDoctorState('error')
481
+ setDoctorReport(null)
482
+ setDoctorError(err?.message || 'Failed to run setup diagnostics.')
483
+ }
484
+ }
485
+
486
+ const saveProviderAndContinue = async () => {
487
+ if (!provider || !selectedProvider) return
488
+ if (requiresKey && !apiKey.trim()) {
489
+ setError('This provider requires an API key.')
490
+ return
491
+ }
492
+
493
+ setSaving(true)
494
+ setError('')
495
+ try {
496
+ if (requiresVerifiedConnection && checkState !== 'ok') {
497
+ const ok = await runConnectionCheck()
498
+ if (!ok) {
499
+ setError('OpenClaw must pass connection verification before continuing.')
500
+ return
501
+ }
502
+ }
503
+
504
+ let nextCredentialId = credentialId
505
+ const shouldSaveCredential = !!apiKey.trim() && (requiresKey || keyIsOptional)
506
+
507
+ if (shouldSaveCredential && !nextCredentialId) {
508
+ const cred = await api<Credential>('POST', '/credentials', {
509
+ provider,
510
+ name: `${selectedProvider.name} key`,
511
+ apiKey: apiKey.trim(),
512
+ })
513
+ nextCredentialId = cred.id
514
+ }
515
+
516
+ setCredentialId(nextCredentialId || null)
517
+ setStep(2)
518
+ } catch (err: any) {
519
+ setError(err?.message || 'Failed to save provider setup.')
520
+ } finally {
521
+ setSaving(false)
522
+ }
523
+ }
524
+
525
+ const createStarterAgent = async () => {
526
+ if (!provider || !agentName.trim()) return
527
+ if (requiresVerifiedConnection && checkState !== 'ok') {
528
+ setError('OpenClaw connection is not verified. Go back and run the connection check.')
529
+ setStep(1)
530
+ return
531
+ }
532
+ setSaving(true)
533
+ setError('')
534
+ try {
535
+ const payload: Record<string, unknown> = {
536
+ name: agentName.trim(),
537
+ description: agentDescription.trim(),
538
+ systemPrompt: agentPrompt.trim(),
539
+ provider: provider as ProviderType,
540
+ model: agentModel.trim() || DEFAULT_AGENTS[provider].model,
541
+ credentialId: credentialId || null,
542
+ tools: DEFAULT_AGENTS[provider].tools,
543
+ }
544
+
545
+ if (supportsEndpoint && endpoint.trim()) {
546
+ payload.apiEndpoint = endpoint.trim()
547
+ }
548
+
549
+ const agents = await api<Record<string, { id: string }>>('GET', '/agents')
550
+ const canReuseDefault =
551
+ !!agents.default
552
+ && Object.keys(agents).length === 1
553
+ && agentName.trim().toLowerCase() === 'assistant'
554
+
555
+ const agentId = canReuseDefault
556
+ ? 'default'
557
+ : (await api<{ id: string }>('POST', '/agents', payload)).id
558
+
559
+ if (canReuseDefault) {
560
+ await api('PUT', '/agents/default', payload)
561
+ }
562
+
563
+ const appState = useAppStore.getState()
564
+ const currentUser = appState.currentUser
565
+ if (currentUser && agentId) {
566
+ const sessionMap = await api<Record<string, { id: string; name: string; user: string }>>('GET', '/sessions')
567
+ const existingMain = Object.values(sessionMap).find((s) => s.name === '__main__' && s.user === currentUser)
568
+ const mainId = existingMain?.id || `main-${currentUser}`
569
+ const selectedModel = (payload.model as string) || DEFAULT_AGENTS[provider].model
570
+ const selectedEndpoint = supportsEndpoint ? (payload.apiEndpoint as string | undefined) || null : null
571
+ const selectedTools = Array.isArray(payload.tools) ? payload.tools as string[] : DEFAULT_AGENTS[provider].tools
572
+
573
+ if (!existingMain) {
574
+ await api('POST', '/sessions', {
575
+ id: mainId,
576
+ name: '__main__',
577
+ user: currentUser,
578
+ agentId,
579
+ provider,
580
+ model: selectedModel,
581
+ credentialId: credentialId || null,
582
+ apiEndpoint: selectedEndpoint,
583
+ tools: selectedTools,
584
+ heartbeatEnabled: true,
585
+ })
586
+ }
587
+
588
+ await api('PUT', `/sessions/${mainId}`, {
589
+ agentId,
590
+ provider,
591
+ model: selectedModel,
592
+ credentialId: credentialId || null,
593
+ apiEndpoint: selectedEndpoint,
594
+ tools: selectedTools,
595
+ heartbeatEnabled: true,
596
+ })
597
+
598
+ await appState.loadSessions()
599
+ appState.setCurrentSession(mainId)
600
+ }
601
+ await api('PUT', '/settings', { setupCompleted: true })
602
+ onComplete()
603
+ } catch (err: any) {
604
+ setError(err?.message || 'Failed to create starter assistant.')
605
+ } finally {
606
+ setSaving(false)
607
+ }
608
+ }
609
+
610
+ return (
611
+ <div className="h-full flex flex-col items-center justify-center px-8 bg-bg relative overflow-hidden">
612
+ <div className="absolute inset-0 pointer-events-none">
613
+ <div
614
+ className="absolute top-[30%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px]"
615
+ style={{
616
+ background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.06) 0%, transparent 70%)',
617
+ animation: 'glow-pulse 6s ease-in-out infinite',
618
+ }}
619
+ />
620
+ </div>
621
+
622
+ <div
623
+ className="relative max-w-[520px] w-full text-center"
624
+ style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}
625
+ >
626
+ <SparkleIcon />
627
+ <StepDots current={step} total={totalSteps} />
628
+
629
+ {step === 0 && (
630
+ <>
631
+ <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
632
+ 2-Minute Setup
633
+ </h1>
634
+ <p className="text-[15px] text-text-2 mb-2">
635
+ No coding required. Pick a provider, paste a key if needed, and start chatting.
636
+ </p>
637
+ <p className="text-[13px] text-text-3 mb-8">
638
+ You can change providers, models, and agent settings anytime later.
639
+ </p>
640
+
641
+ <div className="flex flex-col gap-3 max-h-[44vh] overflow-y-auto pr-1">
642
+ {PROVIDERS.map((p) => (
643
+ <button
644
+ key={p.id}
645
+ onClick={() => selectProvider(p.id)}
646
+ className="w-full px-5 py-4 rounded-[14px] border border-white/[0.08] bg-surface text-left
647
+ cursor-pointer hover:border-accent-bright/30 hover:bg-surface-hover transition-all duration-200
648
+ flex items-start gap-4"
649
+ >
650
+ <div className="w-10 h-10 rounded-[10px] bg-white/[0.04] border border-white/[0.06] flex items-center justify-center shrink-0 mt-0.5">
651
+ <span className="text-[16px] font-display font-700 text-accent-bright">
652
+ {p.icon}
653
+ </span>
654
+ </div>
655
+ <div>
656
+ <div className="text-[15px] font-display font-600 text-text mb-1">
657
+ {p.name}
658
+ <ProviderBadge label={p.badge} />
659
+ </div>
660
+ <div className="text-[13px] text-text-3 leading-relaxed">{p.description}</div>
661
+ {!p.requiresKey && (
662
+ <div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
663
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
664
+ No API key required
665
+ </div>
666
+ )}
667
+ </div>
668
+ </button>
669
+ ))}
670
+ </div>
671
+
672
+ <div className="mt-4 text-left">
673
+ <button
674
+ onClick={runSetupDoctor}
675
+ disabled={doctorState === 'checking'}
676
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-white/[0.02] text-[13px] text-text-2
677
+ cursor-pointer hover:bg-white/[0.05] transition-all duration-200 disabled:opacity-40"
678
+ >
679
+ {doctorState === 'checking' ? 'Running System Check...' : 'Run System Check'}
680
+ </button>
681
+
682
+ {doctorState === 'error' && doctorError && (
683
+ <p className="mt-2 text-[12px] text-red-300">{doctorError}</p>
684
+ )}
685
+
686
+ {doctorReport && doctorState === 'done' && (
687
+ <div className="mt-3 p-3 rounded-[12px] border border-white/[0.08] bg-surface">
688
+ <div className={`text-[12px] font-600 ${doctorReport.ok ? 'text-emerald-300' : 'text-amber-300'}`}>
689
+ {doctorReport.summary}
690
+ </div>
691
+ {doctorReport.checks.filter((c) => c.status !== 'pass').slice(0, 3).map((check) => (
692
+ <div key={check.id} className="mt-1 text-[11px] text-text-3">
693
+ - {check.label}: {check.detail}
694
+ </div>
695
+ ))}
696
+ {!!doctorReport.actions?.length && (
697
+ <div className="mt-2 text-[11px] text-text-3/80">
698
+ Next: {doctorReport.actions.slice(0, 2).join(' ')}
699
+ </div>
700
+ )}
701
+ </div>
702
+ )}
703
+ </div>
704
+
705
+ <SkipLink onClick={skip} />
706
+ </>
707
+ )}
708
+
709
+ {step === 1 && provider && selectedProvider && (
710
+ <>
711
+ <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
712
+ Connect {selectedProvider.name}
713
+ </h1>
714
+ <p className="text-[15px] text-text-2 mb-2">
715
+ Add only what is needed for this provider, then check connection.
716
+ </p>
717
+ <p className="text-[13px] text-text-3 mb-7">
718
+ {requiresVerifiedConnection
719
+ ? 'OpenClaw must pass connection check before you can continue.'
720
+ : 'You can keep going even if the check fails and fix details later.'}
721
+ </p>
722
+
723
+ <div className="flex flex-col gap-3 text-left mb-4">
724
+ {supportsEndpoint && (
725
+ <div>
726
+ <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">
727
+ Endpoint
728
+ </label>
729
+ <input
730
+ type="text"
731
+ value={endpoint}
732
+ onChange={(e) => { setEndpoint(e.target.value); setCheckState('idle'); setCheckMessage('') }}
733
+ placeholder={selectedProvider.defaultEndpoint || ''}
734
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface
735
+ text-text text-[14px] font-mono outline-none transition-all duration-200
736
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
737
+ />
738
+ {provider === 'openclaw' && (
739
+ <div className="mt-2 space-y-0.5">
740
+ <p className="text-[12px] text-text-3">Works with local (<code className="text-text-2">localhost:18789</code>) or remote OpenClaw instances.</p>
741
+ <p className="text-[12px] text-text-3">For remote: use your server&apos;s IP/domain with port (e.g. <code className="text-text-2">http://your-server:60924/v1</code>).</p>
742
+ <p className="text-[12px] text-text-3 mt-1">
743
+ <a href="/docs/openclaw-setup" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">
744
+ Setup guide &rarr;
745
+ </a>
746
+ </p>
747
+ </div>
748
+ )}
749
+ </div>
750
+ )}
751
+
752
+ {(requiresKey || keyIsOptional) && (
753
+ <div>
754
+ <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">
755
+ {keyIsOptional ? 'Token (optional)' : 'API key'}
756
+ </label>
757
+ <input
758
+ type="password"
759
+ value={apiKey}
760
+ onChange={(e) => { setApiKey(e.target.value); setCheckState('idle'); setCheckMessage(''); setError('') }}
761
+ placeholder={selectedProvider.keyPlaceholder || (provider === 'openclaw' ? 'Paste OpenClaw bearer token' : 'sk-...')}
762
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface
763
+ text-text text-[14px] font-mono outline-none transition-all duration-200
764
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
765
+ />
766
+ {selectedProvider.keyUrl && (
767
+ <p className="text-[11px] text-text-3 mt-1.5">
768
+ Get one at{' '}
769
+ <a
770
+ href={selectedProvider.keyUrl}
771
+ target="_blank"
772
+ rel="noopener noreferrer"
773
+ className="text-accent-bright hover:underline"
774
+ >
775
+ {selectedProvider.keyLabel}
776
+ </a>
777
+ </p>
778
+ )}
779
+ </div>
780
+ )}
781
+ </div>
782
+
783
+ {checkState !== 'idle' && (
784
+ <div
785
+ className={`mb-4 px-3 py-2 rounded-[10px] text-[12px] border ${
786
+ checkState === 'ok'
787
+ ? 'bg-emerald-500/10 border-emerald-500/25 text-emerald-300'
788
+ : checkState === 'checking'
789
+ ? 'bg-white/[0.03] border-white/[0.08] text-text-2'
790
+ : 'bg-red-500/10 border-red-500/25 text-red-300'
791
+ }`}
792
+ >
793
+ {checkState === 'checking' ? 'Checking connection...' : checkMessage}
794
+ {checkState === 'error' && provider === 'openclaw' && (() => {
795
+ const hint = getOpenClawErrorHint(checkMessage)
796
+ return hint ? <p className="mt-1.5 text-[11px] text-text-3">{hint}</p> : null
797
+ })()}
798
+ </div>
799
+ )}
800
+
801
+ {error && <p className="mb-4 text-[13px] text-red-400">{error}</p>}
802
+
803
+ <div className="flex items-center justify-center gap-3">
804
+ <button
805
+ onClick={() => { setStep(0); setError('') }}
806
+ className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px]
807
+ font-display font-500 cursor-pointer hover:bg-white/[0.03] transition-all duration-200"
808
+ >
809
+ Back
810
+ </button>
811
+ <button
812
+ onClick={runConnectionCheck}
813
+ disabled={checkState === 'checking' || saving}
814
+ className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-white/[0.03] text-text text-[14px]
815
+ font-display font-600 cursor-pointer hover:bg-white/[0.06] transition-all duration-200 disabled:opacity-40"
816
+ >
817
+ {checkState === 'checking' ? 'Checking...' : 'Check Connection'}
818
+ </button>
819
+ <button
820
+ onClick={saveProviderAndContinue}
821
+ disabled={(requiresKey && !apiKey.trim()) || saving}
822
+ className="px-8 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-display font-600
823
+ cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
824
+ shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
825
+ >
826
+ {saving
827
+ ? 'Saving...'
828
+ : requiresVerifiedConnection
829
+ ? 'Verify & Continue'
830
+ : 'Save & Continue'}
831
+ </button>
832
+ </div>
833
+
834
+ <SkipLink onClick={skip} />
835
+ </>
836
+ )}
837
+
838
+ {step === 2 && provider && selectedProvider && (
839
+ <>
840
+ <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
841
+ You&apos;re Ready
842
+ </h1>
843
+ <p className="text-[15px] text-text-2 mb-7">
844
+ We&apos;ll create a starter assistant so you can begin immediately.
845
+ </p>
846
+
847
+ <div className="mb-5 p-4 rounded-[14px] border border-white/[0.08] bg-surface text-left">
848
+ <div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-2">Setup Summary</div>
849
+ <div className="text-[14px] text-text mb-1">Provider: {selectedProvider.name}</div>
850
+ {supportsEndpoint && endpoint.trim() && (
851
+ <div className="text-[12px] font-mono text-text-3 break-all">Endpoint: {endpoint.trim()}</div>
852
+ )}
853
+ {checkState === 'ok' && (
854
+ <div className="mt-2 text-[12px] text-emerald-300">{checkMessage}</div>
855
+ )}
856
+ {checkState === 'error' && (
857
+ <div className="mt-2 text-[12px] text-amber-300">Connection was not verified. You can still continue.</div>
858
+ )}
859
+ </div>
860
+
861
+ <details className="mb-6 text-left rounded-[14px] border border-white/[0.08] bg-surface px-4 py-3">
862
+ <summary className="cursor-pointer text-[13px] text-text-2 font-600">
863
+ Advanced agent settings (optional)
864
+ </summary>
865
+ <div className="mt-3 space-y-3">
866
+ <div>
867
+ <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Name</label>
868
+ <input
869
+ type="text"
870
+ value={agentName}
871
+ onChange={(e) => setAgentName(e.target.value)}
872
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
873
+ text-text text-[14px] outline-none transition-all duration-200
874
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
875
+ />
876
+ </div>
877
+ <div>
878
+ <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Description</label>
879
+ <input
880
+ type="text"
881
+ value={agentDescription}
882
+ onChange={(e) => setAgentDescription(e.target.value)}
883
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
884
+ text-text text-[14px] outline-none transition-all duration-200
885
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
886
+ />
887
+ </div>
888
+ <div>
889
+ <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">System Prompt</label>
890
+ <textarea
891
+ value={agentPrompt}
892
+ onChange={(e) => setAgentPrompt(e.target.value)}
893
+ rows={3}
894
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
895
+ text-text text-[14px] outline-none transition-all duration-200 resize-none
896
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
897
+ />
898
+ </div>
899
+ <div>
900
+ <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Model</label>
901
+ <input
902
+ type="text"
903
+ value={agentModel}
904
+ onChange={(e) => setAgentModel(e.target.value)}
905
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
906
+ text-text text-[14px] font-mono outline-none transition-all duration-200
907
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
908
+ />
909
+ </div>
910
+ </div>
911
+ </details>
912
+
913
+ {error && <p className="mb-4 text-[13px] text-red-400">{error}</p>}
914
+
915
+ <div className="flex items-center justify-center gap-3">
916
+ <button
917
+ onClick={() => { setStep(1); setError('') }}
918
+ className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px]
919
+ font-display font-500 cursor-pointer hover:bg-white/[0.03] transition-all duration-200"
920
+ >
921
+ Back
922
+ </button>
923
+ <button
924
+ onClick={createStarterAgent}
925
+ disabled={!agentName.trim() || saving}
926
+ className="px-10 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-display font-600
927
+ cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
928
+ shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
929
+ >
930
+ {saving ? 'Creating...' : 'Create Starter Assistant'}
931
+ </button>
932
+ </div>
933
+
934
+ <SkipLink onClick={skip} />
935
+ </>
936
+ )}
937
+ </div>
938
+ </div>
939
+ )
940
+ }