@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,43 @@
1
+ import { NextResponse } from 'next/server'
2
+ import crypto from 'crypto'
3
+ import { loadAgents, loadTasks, saveTasks } from '@/lib/server/storage'
4
+ import { enqueueTask } from '@/lib/server/queue'
5
+
6
+ export async function POST(req: Request) {
7
+ const { agentId, task } = await req.json()
8
+ if (!agentId || !task) {
9
+ return NextResponse.json({ error: 'agentId and task are required' }, { status: 400 })
10
+ }
11
+
12
+ const agents = loadAgents()
13
+ const agent = agents[agentId]
14
+ if (!agent) {
15
+ return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
16
+ }
17
+
18
+ // Create a board task and enqueue it
19
+ const taskId = crypto.randomBytes(4).toString('hex')
20
+ const now = Date.now()
21
+ const tasks = loadTasks()
22
+ tasks[taskId] = {
23
+ id: taskId,
24
+ title: task.slice(0, 80),
25
+ description: task,
26
+ status: 'backlog',
27
+ agentId,
28
+ sessionId: null,
29
+ result: null,
30
+ error: null,
31
+ createdAt: now,
32
+ updatedAt: now,
33
+ queuedAt: null,
34
+ startedAt: null,
35
+ completedAt: null,
36
+ }
37
+ saveTasks(tasks)
38
+
39
+ // Enqueue — this sets status to queued and kicks the worker
40
+ enqueueTask(taskId)
41
+
42
+ return NextResponse.json({ ok: true, taskId })
43
+ }
@@ -0,0 +1,58 @@
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const PLUGINS_DIR = path.join(process.cwd(), 'data', 'plugins')
6
+
7
+ export async function POST(req: Request) {
8
+ const body = await req.json()
9
+ const { url, filename } = body
10
+
11
+ // Validate URL
12
+ if (!url || typeof url !== 'string' || !url.startsWith('https://')) {
13
+ return NextResponse.json(
14
+ { error: 'URL must be a valid HTTPS URL' },
15
+ { status: 400 },
16
+ )
17
+ }
18
+
19
+ // Validate filename
20
+ if (!filename || typeof filename !== 'string' || !filename.endsWith('.js')) {
21
+ return NextResponse.json(
22
+ { error: 'Filename must end in .js' },
23
+ { status: 400 },
24
+ )
25
+ }
26
+
27
+ // Path traversal protection
28
+ const sanitized = path.basename(filename)
29
+ if (sanitized !== filename || filename.includes('..')) {
30
+ return NextResponse.json(
31
+ { error: 'Invalid filename' },
32
+ { status: 400 },
33
+ )
34
+ }
35
+
36
+ try {
37
+ const res = await fetch(url)
38
+ if (!res.ok) {
39
+ throw new Error(`Download failed: ${res.status}`)
40
+ }
41
+ const code = await res.text()
42
+
43
+ // Ensure plugins directory exists
44
+ if (!fs.existsSync(PLUGINS_DIR)) {
45
+ fs.mkdirSync(PLUGINS_DIR, { recursive: true })
46
+ }
47
+
48
+ const dest = path.join(PLUGINS_DIR, sanitized)
49
+ fs.writeFileSync(dest, code, 'utf8')
50
+
51
+ return NextResponse.json({ ok: true, filename: sanitized })
52
+ } catch (err: any) {
53
+ return NextResponse.json(
54
+ { error: 'Failed to install plugin', message: err.message },
55
+ { status: 500 },
56
+ )
57
+ }
58
+ }
@@ -0,0 +1,33 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ const REGISTRY_URL = 'https://swarmclaw.ai/registry/plugins.json'
4
+ const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
5
+
6
+ let cache: { data: any; fetchedAt: number } | null = null
7
+
8
+ export async function GET() {
9
+ const now = Date.now()
10
+
11
+ if (cache && now - cache.fetchedAt < CACHE_TTL) {
12
+ return NextResponse.json(cache.data)
13
+ }
14
+
15
+ try {
16
+ const res = await fetch(REGISTRY_URL, { cache: 'no-store' })
17
+ if (!res.ok) {
18
+ throw new Error(`Registry returned ${res.status}`)
19
+ }
20
+ const data = await res.json()
21
+ cache = { data, fetchedAt: now }
22
+ return NextResponse.json(data)
23
+ } catch (err: any) {
24
+ // Return stale cache if available
25
+ if (cache) {
26
+ return NextResponse.json(cache.data)
27
+ }
28
+ return NextResponse.json(
29
+ { error: 'Failed to fetch plugin registry', message: err.message },
30
+ { status: 502 },
31
+ )
32
+ }
33
+ }
@@ -0,0 +1,21 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getPluginManager } from '@/lib/server/plugins'
3
+
4
+ export async function GET() {
5
+ const manager = getPluginManager()
6
+ return NextResponse.json(manager.listPlugins())
7
+ }
8
+
9
+ export async function POST(req: Request) {
10
+ const body = await req.json()
11
+ const { filename, enabled } = body
12
+
13
+ if (!filename || typeof enabled !== 'boolean') {
14
+ return NextResponse.json({ error: 'filename and enabled required' }, { status: 400 })
15
+ }
16
+
17
+ const manager = getPluginManager()
18
+ manager.setEnabled(filename, enabled)
19
+
20
+ return NextResponse.json({ ok: true })
21
+ }
@@ -0,0 +1,339 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { spawn, type ChildProcess } from 'child_process'
3
+ import http from 'http'
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+ import { localIP } from '@/lib/server/storage'
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // MIME types for static server
10
+ // ---------------------------------------------------------------------------
11
+
12
+ const MIME_MAP: Record<string, string> = {
13
+ '.html': 'text/html', '.htm': 'text/html', '.css': 'text/css',
14
+ '.js': 'application/javascript', '.mjs': 'application/javascript',
15
+ '.json': 'application/json', '.svg': 'image/svg+xml',
16
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
17
+ '.gif': 'image/gif', '.webp': 'image/webp', '.ico': 'image/x-icon',
18
+ '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf',
19
+ '.mp4': 'video/mp4', '.webm': 'video/webm',
20
+ '.txt': 'text/plain', '.md': 'text/plain',
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Server tracking
25
+ // ---------------------------------------------------------------------------
26
+
27
+ interface PreviewServer {
28
+ type: 'static' | 'npm'
29
+ server?: http.Server // static server
30
+ proc?: ChildProcess // npm process
31
+ port: number
32
+ dir: string
33
+ startedAt: number
34
+ log: string
35
+ }
36
+
37
+ const globalKey = '__swarmclaw_preview_servers__' as const
38
+ const servers: Map<string, PreviewServer> = (globalThis as unknown as Record<string, unknown>)[globalKey] as Map<string, PreviewServer>
39
+ ?? ((globalThis as unknown as Record<string, unknown>)[globalKey] = new Map<string, PreviewServer>())
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Helpers
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function resolveServeDir(filePath: string): string {
46
+ const resolved = path.resolve(filePath)
47
+ try {
48
+ return fs.statSync(resolved).isDirectory() ? resolved : path.dirname(resolved)
49
+ } catch {
50
+ return path.dirname(resolved)
51
+ }
52
+ }
53
+
54
+ function dirKey(dir: string): string {
55
+ return dir.replace(/\//g, '_')
56
+ }
57
+
58
+ function findFreePort(): Promise<number> {
59
+ return new Promise((resolve, reject) => {
60
+ const srv = http.createServer()
61
+ srv.listen(0, () => {
62
+ const addr = srv.address()
63
+ const port = typeof addr === 'object' && addr ? addr.port : 0
64
+ srv.close(() => resolve(port))
65
+ })
66
+ srv.on('error', reject)
67
+ })
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Project type detection
72
+ // ---------------------------------------------------------------------------
73
+
74
+ interface ProjectInfo {
75
+ type: 'npm' | 'static'
76
+ devCommand?: string[] // e.g. ['npm', 'run', 'dev']
77
+ framework?: string // e.g. 'vite', 'next', 'cra'
78
+ }
79
+
80
+ function detectProject(dir: string): ProjectInfo {
81
+ const pkgPath = path.join(dir, 'package.json')
82
+ if (!fs.existsSync(pkgPath)) {
83
+ return { type: 'static' }
84
+ }
85
+
86
+ try {
87
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
88
+ const scripts = pkg.scripts || {}
89
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
90
+
91
+ // Detect framework
92
+ let framework = 'node'
93
+ if (deps.next) framework = 'next'
94
+ else if (deps.vite || deps['@vitejs/plugin-react']) framework = 'vite'
95
+ else if (deps['react-scripts']) framework = 'cra'
96
+ else if (deps.astro) framework = 'astro'
97
+ else if (deps.nuxt) framework = 'nuxt'
98
+ else if (deps.svelte || deps['@sveltejs/kit']) framework = 'svelte'
99
+ else if (deps.vue) framework = 'vue'
100
+ else if (deps.angular || deps['@angular/core']) framework = 'angular'
101
+
102
+ // Pick the best dev command
103
+ if (scripts.dev) {
104
+ return { type: 'npm', devCommand: ['npm', 'run', 'dev'], framework }
105
+ }
106
+ if (scripts.start) {
107
+ return { type: 'npm', devCommand: ['npm', 'start'], framework }
108
+ }
109
+ if (scripts.serve) {
110
+ return { type: 'npm', devCommand: ['npm', 'run', 'serve'], framework }
111
+ }
112
+
113
+ return { type: 'static', framework }
114
+ } catch {
115
+ return { type: 'static' }
116
+ }
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // Static file server
121
+ // ---------------------------------------------------------------------------
122
+
123
+ function createStaticServer(dir: string): http.Server {
124
+ return http.createServer((req, res) => {
125
+ res.setHeader('Access-Control-Allow-Origin', '*')
126
+
127
+ let reqPath = decodeURIComponent((req.url || '/').split('?')[0])
128
+ if (reqPath === '/') reqPath = '/index.html'
129
+
130
+ const filePath = path.join(dir, reqPath)
131
+ const normalizedFile = path.resolve(filePath)
132
+
133
+ if (!normalizedFile.startsWith(dir)) {
134
+ res.writeHead(403)
135
+ res.end('Forbidden')
136
+ return
137
+ }
138
+
139
+ const candidates = [
140
+ normalizedFile,
141
+ normalizedFile + '.html',
142
+ path.join(normalizedFile, 'index.html'),
143
+ ]
144
+
145
+ for (const candidate of candidates) {
146
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
147
+ const ext = path.extname(candidate).toLowerCase()
148
+ res.writeHead(200, { 'Content-Type': MIME_MAP[ext] || 'application/octet-stream' })
149
+ fs.createReadStream(candidate).pipe(res)
150
+ return
151
+ }
152
+ }
153
+
154
+ if (fs.existsSync(normalizedFile) && fs.statSync(normalizedFile).isDirectory()) {
155
+ const files = fs.readdirSync(normalizedFile)
156
+ const links = files.map((f) => `<li><a href="${reqPath.replace(/\/$/, '')}/${f}">${f}</a></li>`).join('\n')
157
+ res.writeHead(200, { 'Content-Type': 'text/html' })
158
+ res.end(`<!DOCTYPE html><html><head><title>Index of ${reqPath}</title><style>body{font-family:monospace;padding:20px;background:#1a1a2e;color:#e0e0e0}a{color:#60a5fa}</style></head><body><h2>Index of ${reqPath}</h2><ul>${links}</ul></body></html>`)
159
+ return
160
+ }
161
+
162
+ res.writeHead(404)
163
+ res.end('Not found')
164
+ })
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // npm dev server
169
+ // ---------------------------------------------------------------------------
170
+
171
+ async function startNpmServer(dir: string, command: string[], port: number): Promise<PreviewServer> {
172
+ // Install deps if node_modules missing
173
+ if (!fs.existsSync(path.join(dir, 'node_modules'))) {
174
+ console.log(`[preview] Installing dependencies in ${dir}`)
175
+ await new Promise<void>((resolve, reject) => {
176
+ const install = spawn('npm', ['install'], { cwd: dir, stdio: 'pipe' })
177
+ install.on('close', (code) => code === 0 ? resolve() : reject(new Error(`npm install exited ${code}`)))
178
+ install.on('error', reject)
179
+ })
180
+ }
181
+
182
+ const env = {
183
+ ...process.env,
184
+ PORT: String(port),
185
+ FORCE_COLOR: '0',
186
+ BROWSER: 'none', // CRA: don't open browser
187
+ }
188
+
189
+ // Add --port flag for common frameworks
190
+ const args = [...command.slice(1)]
191
+ const cmdName = command[0]
192
+
193
+ const proc = spawn(cmdName, [...args, '--', '--port', String(port), '--host', '0.0.0.0'], {
194
+ cwd: dir,
195
+ stdio: ['ignore', 'pipe', 'pipe'],
196
+ env,
197
+ })
198
+
199
+ let log = ''
200
+ let detectedPort = port
201
+ const urlRe = /https?:\/\/(?:localhost|0\.0\.0\.0|127\.0\.0\.1|[\d.]+):(\d+)/
202
+
203
+ const onData = (chunk: Buffer) => {
204
+ const text = chunk.toString()
205
+ log += text
206
+ if (log.length > 10000) log = log.slice(-5000)
207
+ const match = text.match(urlRe)
208
+ if (match) {
209
+ detectedPort = parseInt(match[1], 10)
210
+ const entry = servers.get(dirKey(dir))
211
+ if (entry) entry.port = detectedPort
212
+ }
213
+ }
214
+
215
+ proc.stdout?.on('data', onData)
216
+ proc.stderr?.on('data', onData)
217
+
218
+ const entry: PreviewServer = {
219
+ type: 'npm',
220
+ proc,
221
+ port,
222
+ dir,
223
+ startedAt: Date.now(),
224
+ log: '',
225
+ }
226
+
227
+ proc.on('close', () => {
228
+ servers.delete(dirKey(dir))
229
+ console.log(`[preview] npm server stopped for ${dir}`)
230
+ })
231
+ proc.on('error', () => servers.delete(dirKey(dir)))
232
+
233
+ servers.set(dirKey(dir), entry)
234
+
235
+ // Wait for the server to start and detect the actual port
236
+ await new Promise((resolve) => setTimeout(resolve, 5000))
237
+ entry.port = detectedPort
238
+ entry.log = log
239
+
240
+ return entry
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // API handler
245
+ // ---------------------------------------------------------------------------
246
+
247
+ function buildResponse(srv: PreviewServer) {
248
+ return {
249
+ running: true,
250
+ type: srv.type,
251
+ port: srv.port,
252
+ url: `http://localhost:${srv.port}`,
253
+ networkUrl: `http://${localIP()}:${srv.port}`,
254
+ dir: srv.dir,
255
+ }
256
+ }
257
+
258
+ export async function POST(req: Request) {
259
+ const { action, path: filePath } = await req.json()
260
+
261
+ if (!filePath || typeof filePath !== 'string') {
262
+ return NextResponse.json({ error: 'Missing path' }, { status: 400 })
263
+ }
264
+
265
+ const dir = resolveServeDir(filePath)
266
+ const key = dirKey(dir)
267
+
268
+ if (action === 'start') {
269
+ if (servers.has(key)) {
270
+ return NextResponse.json(buildResponse(servers.get(key)!))
271
+ }
272
+
273
+ if (!fs.existsSync(dir)) {
274
+ return NextResponse.json({ error: 'Directory not found' }, { status: 404 })
275
+ }
276
+
277
+ const project = detectProject(dir)
278
+ const port = await findFreePort()
279
+
280
+ if (project.type === 'npm' && project.devCommand) {
281
+ console.log(`[preview] Detected ${project.framework} project in ${dir}, running: ${project.devCommand.join(' ')}`)
282
+ try {
283
+ const entry = await startNpmServer(dir, project.devCommand, port)
284
+ return NextResponse.json({
285
+ ...buildResponse(entry),
286
+ framework: project.framework,
287
+ })
288
+ } catch (err: unknown) {
289
+ console.error(`[preview] npm server failed, falling back to static:`, err)
290
+ // Fall through to static server
291
+ }
292
+ }
293
+
294
+ // Static file server
295
+ const server = createStaticServer(dir)
296
+ await new Promise<void>((resolve, reject) => {
297
+ server.listen(port, '0.0.0.0', () => resolve())
298
+ server.on('error', reject)
299
+ })
300
+
301
+ const entry: PreviewServer = { type: 'static', server, port, dir, startedAt: Date.now(), log: '' }
302
+ servers.set(key, entry)
303
+ console.log(`[preview] Started static server for ${dir} on port ${port}`)
304
+
305
+ return NextResponse.json(buildResponse(entry))
306
+
307
+ } else if (action === 'stop') {
308
+ if (servers.has(key)) {
309
+ const srv = servers.get(key)!
310
+ if (srv.type === 'npm' && srv.proc) {
311
+ try { srv.proc.kill('SIGTERM') } catch {}
312
+ try { if (srv.proc.pid) process.kill(-srv.proc.pid, 'SIGTERM') } catch {}
313
+ }
314
+ if (srv.server) srv.server.close()
315
+ servers.delete(key)
316
+ console.log(`[preview] Stopped server for ${dir}`)
317
+ }
318
+ return NextResponse.json({ running: false, dir })
319
+
320
+ } else if (action === 'status') {
321
+ if (servers.has(key)) {
322
+ return NextResponse.json(buildResponse(servers.get(key)!))
323
+ }
324
+ return NextResponse.json({ running: false, dir })
325
+
326
+ } else if (action === 'list') {
327
+ const list = Array.from(servers.values()).map((s) => ({
328
+ ...buildResponse(s),
329
+ startedAt: s.startedAt,
330
+ }))
331
+ return NextResponse.json({ servers: list })
332
+
333
+ } else if (action === 'detect') {
334
+ const project = detectProject(dir)
335
+ return NextResponse.json({ dir, ...project })
336
+ }
337
+
338
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
339
+ }
@@ -0,0 +1,29 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadModelOverrides, saveModelOverrides } from '@/lib/server/storage'
3
+ import { getProviderList } from '@/lib/providers'
4
+
5
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ const { id } = await params
7
+ const overrides = loadModelOverrides()
8
+ const providers = getProviderList()
9
+ const provider = providers.find((p) => p.id === id)
10
+ if (!provider) return new NextResponse(null, { status: 404 })
11
+ return NextResponse.json({ models: provider.models, hasOverride: !!overrides[id] })
12
+ }
13
+
14
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
15
+ const { id } = await params
16
+ const body = await req.json()
17
+ const overrides = loadModelOverrides()
18
+ overrides[id] = body.models || []
19
+ saveModelOverrides(overrides)
20
+ return NextResponse.json({ models: overrides[id] })
21
+ }
22
+
23
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
24
+ const { id } = await params
25
+ const overrides = loadModelOverrides()
26
+ delete overrides[id]
27
+ saveModelOverrides(overrides)
28
+ return NextResponse.json({ ok: true })
29
+ }
@@ -0,0 +1,34 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadProviderConfigs, saveProviderConfigs } from '@/lib/server/storage'
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params
6
+ const configs = loadProviderConfigs()
7
+ const config = configs[id]
8
+ if (!config) return new NextResponse(null, { status: 404 })
9
+ return NextResponse.json(config)
10
+ }
11
+
12
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
13
+ const { id } = await params
14
+ const body = await req.json()
15
+ const configs = loadProviderConfigs()
16
+ const existing = configs[id]
17
+ if (!existing) return new NextResponse(null, { status: 404 })
18
+ configs[id] = { ...existing, ...body, id, updatedAt: Date.now() }
19
+ saveProviderConfigs(configs)
20
+ return NextResponse.json(configs[id])
21
+ }
22
+
23
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
24
+ const { id } = await params
25
+ const configs = loadProviderConfigs()
26
+ if (!configs[id]) return new NextResponse(null, { status: 404 })
27
+ // Only allow deleting custom providers
28
+ if (configs[id].type === 'builtin') {
29
+ return NextResponse.json({ error: 'Cannot delete built-in providers' }, { status: 400 })
30
+ }
31
+ delete configs[id]
32
+ saveProviderConfigs(configs)
33
+ return NextResponse.json({ ok: true })
34
+ }
@@ -0,0 +1,7 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadProviderConfigs } from '@/lib/server/storage'
3
+
4
+ export async function GET() {
5
+ const configs = loadProviderConfigs()
6
+ return NextResponse.json(Object.values(configs))
7
+ }
@@ -0,0 +1,30 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ /** Fetch locally installed models from Ollama API */
4
+ export async function GET(req: Request) {
5
+ const { searchParams } = new URL(req.url)
6
+ const endpoint = searchParams.get('endpoint') || 'http://localhost:11434'
7
+
8
+ try {
9
+ const res = await fetch(`${endpoint.replace(/\/+$/, '')}/api/tags`, {
10
+ signal: AbortSignal.timeout(5000),
11
+ })
12
+ if (!res.ok) {
13
+ return NextResponse.json({ models: [], error: `Ollama returned ${res.status}` })
14
+ }
15
+ const data = await res.json()
16
+ const models = (data.models || []).map((m: any) => ({
17
+ name: m.name?.replace(/:latest$/, '') || m.name,
18
+ size: m.size,
19
+ modified: m.modified_at,
20
+ }))
21
+ return NextResponse.json({ models })
22
+ } catch (err: any) {
23
+ return NextResponse.json({
24
+ models: [],
25
+ error: err.code === 'ECONNREFUSED'
26
+ ? 'Ollama is not running'
27
+ : err.message || 'Failed to connect',
28
+ })
29
+ }
30
+ }
@@ -0,0 +1,23 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { probeOpenClawHealth } from '@/lib/server/openclaw-health'
3
+
4
+ function parseIntBounded(value: unknown, fallback: number, min: number, max: number): number {
5
+ const parsed = typeof value === 'number'
6
+ ? value
7
+ : typeof value === 'string'
8
+ ? Number.parseInt(value, 10)
9
+ : Number.NaN
10
+ if (!Number.isFinite(parsed)) return fallback
11
+ return Math.max(min, Math.min(max, Math.trunc(parsed)))
12
+ }
13
+
14
+ export async function GET(req: Request) {
15
+ const { searchParams } = new URL(req.url)
16
+ const endpoint = searchParams.get('endpoint')
17
+ const credentialId = searchParams.get('credentialId')
18
+ const token = searchParams.get('token')
19
+ const model = searchParams.get('model')
20
+ const timeoutMs = parseIntBounded(searchParams.get('timeoutMs'), 8000, 1000, 30000)
21
+ const result = await probeOpenClawHealth({ endpoint, credentialId, token, model, timeoutMs })
22
+ return NextResponse.json(result)
23
+ }
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server'
2
+ import crypto from 'crypto'
3
+ import { getProviderList } from '@/lib/providers'
4
+ import { loadProviderConfigs, saveProviderConfigs } from '@/lib/server/storage'
5
+
6
+ export async function GET() {
7
+ return NextResponse.json(getProviderList())
8
+ }
9
+
10
+ export async function POST(req: Request) {
11
+ const body = await req.json()
12
+ const configs = loadProviderConfigs()
13
+ const id = body.id || `custom-${crypto.randomBytes(4).toString('hex')}`
14
+ configs[id] = {
15
+ id,
16
+ name: body.name || 'Custom Provider',
17
+ type: 'custom',
18
+ baseUrl: body.baseUrl || '',
19
+ models: body.models || [],
20
+ requiresApiKey: body.requiresApiKey ?? true,
21
+ credentialId: body.credentialId || null,
22
+ isEnabled: body.isEnabled ?? true,
23
+ createdAt: Date.now(),
24
+ updatedAt: Date.now(),
25
+ }
26
+ saveProviderConfigs(configs)
27
+ return NextResponse.json(configs[id])
28
+ }
@@ -0,0 +1,9 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getRunById } from '@/lib/server/session-run-manager'
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params
6
+ const run = getRunById(id)
7
+ if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 })
8
+ return NextResponse.json(run)
9
+ }
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { listRuns, type SessionRunStatus } from '@/lib/server/session-run-manager'
3
+
4
+ export async function GET(req: Request) {
5
+ const { searchParams } = new URL(req.url)
6
+ const sessionId = searchParams.get('sessionId') || undefined
7
+ const status = (searchParams.get('status') || undefined) as SessionRunStatus | undefined
8
+ const limitRaw = searchParams.get('limit')
9
+ const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined
10
+
11
+ const runs = listRuns({ sessionId, status, limit })
12
+ return NextResponse.json(runs)
13
+ }