@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,311 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState, useCallback } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { BottomSheet } from '@/components/shared/bottom-sheet'
6
+ import { api } from '@/lib/api-client'
7
+ import type { PluginMeta, MarketplacePlugin } from '@/types'
8
+
9
+ export function PluginSheet() {
10
+ const open = useAppStore((s) => s.pluginSheetOpen)
11
+ const setOpen = useAppStore((s) => s.setPluginSheetOpen)
12
+ const editingFilename = useAppStore((s) => s.editingPluginFilename)
13
+ const setEditingFilename = useAppStore((s) => s.setEditingPluginFilename)
14
+ const plugins = useAppStore((s) => s.plugins)
15
+ const loadPlugins = useAppStore((s) => s.loadPlugins)
16
+
17
+ const [tab, setTab] = useState<'marketplace' | 'url'>('marketplace')
18
+ const [marketplace, setMarketplace] = useState<MarketplacePlugin[]>([])
19
+ const [loading, setLoading] = useState(false)
20
+ const [installing, setInstalling] = useState<string | null>(null)
21
+ const [urlInput, setUrlInput] = useState('')
22
+ const [urlFilename, setUrlFilename] = useState('')
23
+ const [urlStatus, setUrlStatus] = useState<{ ok: boolean; message: string } | null>(null)
24
+ const [deleting, setDeleting] = useState(false)
25
+ const [search, setSearch] = useState('')
26
+ const [activeTag, setActiveTag] = useState<string | null>(null)
27
+ const [sort, setSort] = useState<'name' | 'downloads'>('downloads')
28
+
29
+ const editing = editingFilename ? plugins[editingFilename] : null
30
+
31
+ const loadMarketplace = useCallback(async () => {
32
+ setLoading(true)
33
+ try {
34
+ const data = await api<MarketplacePlugin[]>('GET', '/plugins/marketplace')
35
+ if (Array.isArray(data)) setMarketplace(data)
36
+ } catch { /* ignore */ }
37
+ setLoading(false)
38
+ }, [])
39
+
40
+ useEffect(() => {
41
+ if (open && !editingFilename && tab === 'marketplace') loadMarketplace()
42
+ }, [open, editingFilename, tab])
43
+
44
+ const handleClose = () => {
45
+ setOpen(false)
46
+ setEditingFilename(null)
47
+ setUrlInput('')
48
+ setUrlFilename('')
49
+ setUrlStatus(null)
50
+ }
51
+
52
+ const togglePlugin = async (filename: string, enabled: boolean) => {
53
+ await api('POST', '/plugins', { filename, enabled })
54
+ loadPlugins()
55
+ }
56
+
57
+ const deletePlugin = async (filename: string) => {
58
+ setDeleting(true)
59
+ try {
60
+ await api('DELETE', `/plugins/${encodeURIComponent(filename)}`)
61
+ await loadPlugins()
62
+ handleClose()
63
+ } catch { /* ignore */ }
64
+ setDeleting(false)
65
+ }
66
+
67
+ const installFromMarketplace = async (p: MarketplacePlugin) => {
68
+ setInstalling(p.id)
69
+ try {
70
+ await api('POST', '/plugins/install', { url: p.url, filename: `${p.id}.js` })
71
+ await loadPlugins()
72
+ } catch { /* ignore */ }
73
+ setInstalling(null)
74
+ }
75
+
76
+ const installFromUrl = async () => {
77
+ if (!urlInput || !urlFilename) return
78
+ setUrlStatus(null)
79
+ setInstalling('url')
80
+ try {
81
+ await api('POST', '/plugins/install', { url: urlInput, filename: urlFilename })
82
+ await loadPlugins()
83
+ setUrlStatus({ ok: true, message: 'Installed successfully' })
84
+ setUrlInput('')
85
+ setUrlFilename('')
86
+ } catch (err: any) {
87
+ setUrlStatus({ ok: false, message: err.message || 'Install failed' })
88
+ }
89
+ setInstalling(null)
90
+ }
91
+
92
+ const installedFilenames = new Set(Object.keys(plugins))
93
+
94
+ const tabClass = (t: string) =>
95
+ `py-2.5 px-4 rounded-[10px] text-center cursor-pointer transition-all text-[12px] font-600 border
96
+ ${tab === t
97
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
98
+ : 'bg-bg border-white/[0.06] text-text-3 hover:bg-surface-2'}`
99
+
100
+ return (
101
+ <BottomSheet open={open} onClose={handleClose}>
102
+ {editing ? (
103
+ <div className="space-y-5">
104
+ <div className="py-3 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
105
+ <div className="flex items-center gap-2 mb-1">
106
+ <span className="text-[14px] font-600 text-text">{editing.name}</span>
107
+ {editing.openclaw && <span className="text-[9px] font-600 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded-full">OpenClaw</span>}
108
+ </div>
109
+ <div className="text-[11px] font-mono text-text-3">{editing.filename}</div>
110
+ {editing.description && <div className="text-[11px] text-text-3/60 mt-1">{editing.description}</div>}
111
+ {editing.author && <div className="text-[10px] text-text-3/70 mt-1">by {editing.author}</div>}
112
+ </div>
113
+
114
+ <div className="flex items-center justify-between py-3 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
115
+ <span className="text-[13px] font-600 text-text">Enabled</span>
116
+ <div
117
+ onClick={() => togglePlugin(editing.filename, !editing.enabled)}
118
+ className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
119
+ ${editing.enabled ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
120
+ >
121
+ <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
122
+ ${editing.enabled ? 'left-[22px]' : 'left-0.5'}`} />
123
+ </div>
124
+ </div>
125
+
126
+ <button
127
+ onClick={() => deletePlugin(editing.filename)}
128
+ disabled={deleting}
129
+ className="w-full py-2.5 rounded-[10px] text-[13px] font-600 bg-red-500/10 text-red-400 border border-red-500/20
130
+ hover:bg-red-500/20 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default"
131
+ style={{ fontFamily: 'inherit' }}
132
+ >
133
+ {deleting ? 'Deleting...' : 'Delete Plugin'}
134
+ </button>
135
+ </div>
136
+ ) : (
137
+ <div>
138
+ <div className="flex gap-2 mb-5">
139
+ <button onClick={() => setTab('marketplace')} className={tabClass('marketplace')} style={{ fontFamily: 'inherit' }}>
140
+ Marketplace
141
+ </button>
142
+ <button onClick={() => setTab('url')} className={tabClass('url')} style={{ fontFamily: 'inherit' }}>
143
+ Install from URL
144
+ </button>
145
+ </div>
146
+
147
+ {tab === 'marketplace' && (
148
+ loading
149
+ ? <p className="text-[12px] text-text-3/70">Loading marketplace...</p>
150
+ : marketplace.length === 0
151
+ ? <p className="text-[12px] text-text-3/70">No plugins available</p>
152
+ : (() => {
153
+ const allTags = Array.from(new Set(marketplace.flatMap((p) => p.tags))).sort()
154
+ const q = search.toLowerCase()
155
+ const filtered = marketplace
156
+ .filter((p) => {
157
+ if (q && !p.name.toLowerCase().includes(q) && !p.description.toLowerCase().includes(q) && !p.tags.some((t) => t.toLowerCase().includes(q))) return false
158
+ if (activeTag && !p.tags.includes(activeTag)) return false
159
+ return true
160
+ })
161
+ .sort((a, b) => sort === 'downloads' ? b.downloads - a.downloads : a.name.localeCompare(b.name))
162
+
163
+ return (
164
+ <div className="space-y-3">
165
+ {/* Search */}
166
+ <input
167
+ value={search}
168
+ onChange={(e) => setSearch(e.target.value)}
169
+ placeholder="Search plugins..."
170
+ className="w-full px-3 py-2.5 rounded-[10px] bg-bg border border-white/[0.06] text-[12px] text-text placeholder:text-text-3/50 outline-none focus:border-accent-bright/30"
171
+ style={{ fontFamily: 'inherit' }}
172
+ />
173
+
174
+ {/* Tags + Sort */}
175
+ <div className="flex items-center gap-1.5 flex-wrap">
176
+ <button
177
+ onClick={() => setActiveTag(null)}
178
+ className={`px-2 py-1 rounded-[6px] text-[10px] font-600 cursor-pointer transition-all border-none ${
179
+ !activeTag ? 'bg-accent-soft text-accent-bright' : 'bg-white/[0.03] text-text-3/60 hover:text-text-3'
180
+ }`}
181
+ >
182
+ All
183
+ </button>
184
+ {allTags.map((t) => (
185
+ <button
186
+ key={t}
187
+ onClick={() => setActiveTag(activeTag === t ? null : t)}
188
+ className={`px-2 py-1 rounded-[6px] text-[10px] font-600 cursor-pointer transition-all border-none ${
189
+ activeTag === t ? 'bg-accent-soft text-accent-bright' : 'bg-white/[0.03] text-text-3/60 hover:text-text-3'
190
+ }`}
191
+ >
192
+ {t}
193
+ </button>
194
+ ))}
195
+ <div className="flex-1" />
196
+ <select
197
+ value={sort}
198
+ onChange={(e) => setSort(e.target.value as 'name' | 'downloads')}
199
+ className="px-2 py-1 rounded-[6px] bg-bg border border-white/[0.06] text-[10px] text-text-3 outline-none cursor-pointer appearance-none"
200
+ style={{ fontFamily: 'inherit' }}
201
+ >
202
+ <option value="downloads">Popular</option>
203
+ <option value="name">A-Z</option>
204
+ </select>
205
+ </div>
206
+
207
+ {/* Results */}
208
+ {filtered.length === 0 ? (
209
+ <p className="text-[12px] text-text-3/50 text-center py-4">No plugins match your search</p>
210
+ ) : (
211
+ <div className="space-y-2.5">
212
+ {filtered.map((p) => {
213
+ const isInstalled = installedFilenames.has(`${p.id}.js`)
214
+ return (
215
+ <div key={p.id} className="py-3.5 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
216
+ <div className="flex items-start gap-3">
217
+ <div className="flex-1 min-w-0">
218
+ <div className="flex items-center gap-2">
219
+ <span className="text-[14px] font-600 text-text">{p.name}</span>
220
+ <span className="text-[10px] font-mono text-text-3/70">v{p.version}</span>
221
+ {p.openclaw && <span className="text-[9px] font-600 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded-full">OpenClaw</span>}
222
+ </div>
223
+ <div className="text-[11px] text-text-3/60 mt-1">{p.description}</div>
224
+ <div className="flex items-center gap-2 mt-2">
225
+ <span className="text-[10px] text-text-3/70">by {p.author}</span>
226
+ <span className="text-[10px] text-text-3/50">&middot;</span>
227
+ {p.tags.slice(0, 3).map((t) => (
228
+ <button
229
+ key={t}
230
+ onClick={() => setActiveTag(activeTag === t ? null : t)}
231
+ className={`text-[9px] font-600 px-1.5 py-0.5 rounded-full cursor-pointer transition-all border-none ${
232
+ activeTag === t ? 'text-accent-bright bg-accent-soft' : 'text-text-3/50 bg-white/[0.04] hover:text-text-3'
233
+ }`}
234
+ >
235
+ {t}
236
+ </button>
237
+ ))}
238
+ </div>
239
+ </div>
240
+ <button
241
+ onClick={() => !isInstalled && installFromMarketplace(p)}
242
+ disabled={isInstalled || installing === p.id}
243
+ className={`shrink-0 py-2 px-4 rounded-[10px] text-[12px] font-600 transition-all cursor-pointer
244
+ ${isInstalled
245
+ ? 'bg-white/[0.04] text-text-3/70 cursor-default'
246
+ : installing === p.id
247
+ ? 'bg-accent-soft text-accent-bright animate-pulse'
248
+ : 'bg-accent-soft text-accent-bright hover:bg-accent-soft/80 border border-accent-bright/20'}`}
249
+ style={{ fontFamily: 'inherit' }}
250
+ >
251
+ {isInstalled ? 'Installed' : installing === p.id ? 'Installing...' : 'Install'}
252
+ </button>
253
+ </div>
254
+ </div>
255
+ )
256
+ })}
257
+ </div>
258
+ )}
259
+ </div>
260
+ )
261
+ })()
262
+ )}
263
+
264
+ {tab === 'url' && (
265
+ <div className="p-5 rounded-[14px] bg-surface border border-white/[0.06]">
266
+ <div className="mb-4">
267
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Plugin URL</label>
268
+ <input
269
+ type="url"
270
+ value={urlInput}
271
+ onChange={(e) => setUrlInput(e.target.value)}
272
+ placeholder="https://example.com/my-plugin.js"
273
+ className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/60 outline-none focus:border-accent-bright/30"
274
+ style={{ fontFamily: 'inherit' }}
275
+ />
276
+ </div>
277
+ <div className="mb-4">
278
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Save as filename</label>
279
+ <input
280
+ type="text"
281
+ value={urlFilename}
282
+ onChange={(e) => setUrlFilename(e.target.value)}
283
+ placeholder="my-plugin.js"
284
+ className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/60 outline-none focus:border-accent-bright/30"
285
+ style={{ fontFamily: 'inherit' }}
286
+ />
287
+ </div>
288
+ <button
289
+ onClick={installFromUrl}
290
+ disabled={!urlInput || !urlFilename || installing === 'url'}
291
+ className="w-full py-2.5 rounded-[10px] text-[13px] font-600 bg-accent-soft text-accent-bright border border-accent-bright/20
292
+ hover:bg-accent-soft/80 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default"
293
+ style={{ fontFamily: 'inherit' }}
294
+ >
295
+ {installing === 'url' ? 'Installing...' : 'Install Plugin'}
296
+ </button>
297
+ {urlStatus && (
298
+ <p className={`text-[11px] mt-3 ${urlStatus.ok ? 'text-emerald-400' : 'text-red-400'}`}>
299
+ {urlStatus.message}
300
+ </p>
301
+ )}
302
+ <p className="text-[10px] text-text-3/60 mt-3">
303
+ Works with SwarmClaw and OpenClaw plugin formats. URL must be HTTPS.
304
+ </p>
305
+ </div>
306
+ )}
307
+ </div>
308
+ )}
309
+ </BottomSheet>
310
+ )
311
+ }
@@ -0,0 +1,96 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useState } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+
6
+ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
7
+ const providers = useAppStore((s) => s.providers)
8
+ const providerConfigs = useAppStore((s) => s.providerConfigs)
9
+ const loadProviders = useAppStore((s) => s.loadProviders)
10
+ const loadProviderConfigs = useAppStore((s) => s.loadProviderConfigs)
11
+ const credentials = useAppStore((s) => s.credentials)
12
+ const loadCredentials = useAppStore((s) => s.loadCredentials)
13
+ const setProviderSheetOpen = useAppStore((s) => s.setProviderSheetOpen)
14
+ const setEditingProviderId = useAppStore((s) => s.setEditingProviderId)
15
+ const [loaded, setLoaded] = useState(false)
16
+
17
+ const refresh = useCallback(async () => {
18
+ await Promise.all([loadProviders(), loadProviderConfigs(), loadCredentials()])
19
+ setLoaded(true)
20
+ }, [loadProviders, loadProviderConfigs, loadCredentials])
21
+
22
+ useEffect(() => {
23
+ const bootstrap = setTimeout(() => { void refresh() }, 0)
24
+ const poll = setInterval(() => { void loadProviders() }, 20_000)
25
+ return () => {
26
+ clearTimeout(bootstrap)
27
+ clearInterval(poll)
28
+ }
29
+ }, [refresh, loadProviders])
30
+
31
+ const handleEdit = (id: string) => {
32
+ setEditingProviderId(id)
33
+ setProviderSheetOpen(true)
34
+ }
35
+
36
+ // Merge built-in providers with custom configs
37
+ const builtinItems = providers.map((p) => ({
38
+ id: p.id,
39
+ name: p.name,
40
+ type: 'builtin' as const,
41
+ models: p.models,
42
+ requiresApiKey: p.requiresApiKey,
43
+ isEnabled: true,
44
+ isConnected: !p.requiresApiKey || Object.values(credentials).some((c) => c.provider === p.id),
45
+ }))
46
+
47
+ const customItems = providerConfigs.map((c) => ({
48
+ id: c.id,
49
+ name: c.name,
50
+ type: 'custom' as const,
51
+ models: c.models,
52
+ requiresApiKey: c.requiresApiKey,
53
+ isEnabled: c.isEnabled,
54
+ isConnected: !c.requiresApiKey || !!c.credentialId,
55
+ }))
56
+
57
+ const allItems = [...builtinItems, ...customItems]
58
+
59
+ if (!loaded) {
60
+ return (
61
+ <div className={`flex-1 flex items-center justify-center ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
62
+ <p className="text-[13px] text-text-3">Loading providers...</p>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ return (
68
+ <div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
69
+ <div className="space-y-2">
70
+ {allItems.map((item) => (
71
+ <button
72
+ key={item.id}
73
+ onClick={() => handleEdit(item.id)}
74
+ className="w-full text-left p-4 rounded-[14px] border transition-all duration-200
75
+ cursor-pointer hover:bg-surface-2 bg-surface border-white/[0.06]"
76
+ >
77
+ <div className="flex items-center justify-between mb-1.5">
78
+ <span className="font-display text-[14px] font-600 text-text truncate">{item.name}</span>
79
+ <div className="flex items-center gap-2 shrink-0">
80
+ <span className={`text-[10px] font-600 px-2 py-0.5 rounded-[5px] uppercase tracking-wider
81
+ ${item.type === 'builtin' ? 'bg-white/[0.04] text-text-3' : 'bg-[#6366F1]/10 text-[#6366F1]'}`}>
82
+ {item.type === 'builtin' ? 'Built-in' : 'Custom'}
83
+ </span>
84
+ <span className={`w-2 h-2 rounded-full ${item.isConnected ? 'bg-emerald-400' : 'bg-white/10'}`} />
85
+ </div>
86
+ </div>
87
+ <div className="text-[12px] text-text-3/60 font-mono truncate">
88
+ {item.models.slice(0, 3).join(', ')}
89
+ {item.models.length > 3 && ` +${item.models.length - 3}`}
90
+ </div>
91
+ </button>
92
+ ))}
93
+ </div>
94
+ </div>
95
+ )
96
+ }