@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,60 @@
1
+ import { NextResponse } from 'next/server'
2
+ import crypto from 'crypto'
3
+ import { loadConnectors, saveConnectors } from '@/lib/server/storage'
4
+ import type { Connector } from '@/types'
5
+
6
+ export async function GET() {
7
+ const connectors = loadConnectors()
8
+ // Merge runtime status from manager
9
+ try {
10
+ const { getConnectorStatus, isConnectorAuthenticated, hasConnectorCredentials, getConnectorQR } = await import('@/lib/server/connectors/manager')
11
+ for (const c of Object.values(connectors) as Connector[]) {
12
+ c.status = getConnectorStatus(c.id)
13
+ if (c.platform === 'whatsapp') {
14
+ c.authenticated = isConnectorAuthenticated(c.id)
15
+ c.hasCredentials = hasConnectorCredentials(c.id)
16
+ const qr = getConnectorQR(c.id)
17
+ if (qr) c.qrDataUrl = qr
18
+ }
19
+ }
20
+ } catch { /* manager not loaded yet */ }
21
+ return NextResponse.json(connectors)
22
+ }
23
+
24
+ export async function POST(req: Request) {
25
+ const body = await req.json()
26
+ const connectors = loadConnectors()
27
+ const id = crypto.randomBytes(4).toString('hex')
28
+
29
+ const connector: Connector = {
30
+ id,
31
+ name: body.name || `${body.platform} Connector`,
32
+ platform: body.platform,
33
+ agentId: body.agentId,
34
+ credentialId: body.credentialId || null,
35
+ config: body.config || {},
36
+ isEnabled: false,
37
+ status: 'stopped',
38
+ lastError: null,
39
+ createdAt: Date.now(),
40
+ updatedAt: Date.now(),
41
+ }
42
+
43
+ connectors[id] = connector
44
+ saveConnectors(connectors)
45
+
46
+ // Auto-start if connector has credentials (or is WhatsApp which uses QR)
47
+ const hasCredentials = connector.platform === 'whatsapp' || connector.platform === 'openclaw' || !!connector.credentialId
48
+ if (hasCredentials && body.autoStart !== false) {
49
+ try {
50
+ const { startConnector } = await import('@/lib/server/connectors/manager')
51
+ await startConnector(id)
52
+ connector.isEnabled = true
53
+ connector.status = 'running'
54
+ connectors[id] = connector
55
+ saveConnectors(connectors)
56
+ } catch { /* auto-start is best-effort */ }
57
+ }
58
+
59
+ return NextResponse.json(connector)
60
+ }
@@ -0,0 +1,14 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadCredentials, saveCredentials } from '@/lib/server/storage'
3
+
4
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id: credId } = await params
6
+ const creds = loadCredentials()
7
+ if (!creds[credId]) {
8
+ return new NextResponse(null, { status: 404 })
9
+ }
10
+ delete creds[credId]
11
+ saveCredentials(creds)
12
+ console.log(`[credentials] deleted ${credId}`)
13
+ return new NextResponse('OK')
14
+ }
@@ -0,0 +1,31 @@
1
+ import { NextResponse } from 'next/server'
2
+ import crypto from 'crypto'
3
+ import { loadCredentials, saveCredentials, encryptKey } from '@/lib/server/storage'
4
+
5
+ export async function GET() {
6
+ const creds = loadCredentials()
7
+ const safe: Record<string, any> = {}
8
+ for (const [id, c] of Object.entries(creds) as [string, any][]) {
9
+ safe[id] = { id: c.id, provider: c.provider, name: c.name, createdAt: c.createdAt }
10
+ }
11
+ return NextResponse.json(safe)
12
+ }
13
+
14
+ export async function POST(req: Request) {
15
+ const { provider, name, apiKey } = await req.json()
16
+ if (!provider || !apiKey) {
17
+ return NextResponse.json({ error: 'provider and apiKey are required' }, { status: 400 })
18
+ }
19
+ const id = 'cred_' + crypto.randomBytes(6).toString('hex')
20
+ const creds = loadCredentials()
21
+ creds[id] = {
22
+ id,
23
+ provider,
24
+ name: name || `${provider} key`,
25
+ encryptedKey: encryptKey(apiKey),
26
+ createdAt: Date.now(),
27
+ }
28
+ saveCredentials(creds)
29
+ console.log(`[credentials] stored ${id} for ${provider}`)
30
+ return NextResponse.json({ id, provider, name: creds[id].name, createdAt: creds[id].createdAt })
31
+ }
@@ -0,0 +1,11 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { ensureDaemonStarted, getDaemonStatus, runDaemonHealthCheckNow } from '@/lib/server/daemon-state'
3
+
4
+ export async function POST() {
5
+ ensureDaemonStarted('api/daemon/health-check:post')
6
+ await runDaemonHealthCheckNow()
7
+ return NextResponse.json({
8
+ ok: true,
9
+ status: getDaemonStatus(),
10
+ })
11
+ }
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { ensureDaemonStarted, getDaemonStatus, startDaemon, stopDaemon } from '@/lib/server/daemon-state'
3
+
4
+ export async function GET() {
5
+ ensureDaemonStarted('api/daemon:get')
6
+ return NextResponse.json(getDaemonStatus())
7
+ }
8
+
9
+ export async function POST(req: Request) {
10
+ const body = await req.json().catch(() => ({}))
11
+ const action = body.action
12
+
13
+ if (action === 'start') {
14
+ startDaemon({ source: 'api/daemon:post:start', manualStart: true })
15
+ return NextResponse.json({ ok: true, status: 'running' })
16
+ } else if (action === 'stop') {
17
+ stopDaemon({ source: 'api/daemon:post:stop', manualStop: true })
18
+ return NextResponse.json({ ok: true, status: 'stopped' })
19
+ }
20
+
21
+ return NextResponse.json({ error: 'Invalid action. Use "start" or "stop".' }, { status: 400 })
22
+ }
@@ -0,0 +1,60 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { execSync } from 'child_process'
3
+ import path from 'path'
4
+ import os from 'os'
5
+
6
+ function pickMacOS(mode: 'file' | 'folder'): string | null {
7
+ const script = mode === 'folder'
8
+ ? `osascript -e 'POSIX path of (choose folder with prompt "Select a directory")'`
9
+ : `osascript -e 'POSIX path of (choose file with prompt "Select a file")'`
10
+ try {
11
+ return execSync(script, { timeout: 60000, encoding: 'utf-8' }).trim().replace(/\/$/, '')
12
+ } catch {
13
+ return null
14
+ }
15
+ }
16
+
17
+ function pickLinux(mode: 'file' | 'folder'): string | null {
18
+ // Try zenity first (GTK), then kdialog (KDE)
19
+ const zenityFlag = mode === 'folder' ? '--file-selection --directory' : '--file-selection'
20
+ const kdialogFlag = mode === 'folder' ? '--getexistingdirectory ~' : '--getopenfilename ~'
21
+ for (const cmd of [
22
+ `zenity ${zenityFlag} --title="Select a ${mode}"`,
23
+ `kdialog ${kdialogFlag}`,
24
+ ]) {
25
+ try {
26
+ return execSync(cmd, { timeout: 60000, encoding: 'utf-8' }).trim()
27
+ } catch { /* try next */ }
28
+ }
29
+ return null
30
+ }
31
+
32
+ function pickWindows(mode: 'file' | 'folder'): string | null {
33
+ if (mode === 'folder') {
34
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.FolderBrowserDialog; if($d.ShowDialog() -eq 'OK'){$d.SelectedPath}`
35
+ try {
36
+ return execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 60000, encoding: 'utf-8' }).trim() || null
37
+ } catch { return null }
38
+ }
39
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.OpenFileDialog; if($d.ShowDialog() -eq 'OK'){$d.FileName}`
40
+ try {
41
+ return execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 60000, encoding: 'utf-8' }).trim() || null
42
+ } catch { return null }
43
+ }
44
+
45
+ export async function POST(req: NextRequest) {
46
+ const { mode = 'folder' } = (await req.json().catch(() => ({}))) as { mode?: 'file' | 'folder' }
47
+ const platform = os.platform()
48
+
49
+ let picked: string | null = null
50
+ if (platform === 'darwin') picked = pickMacOS(mode)
51
+ else if (platform === 'win32') picked = pickWindows(mode)
52
+ else picked = pickLinux(mode)
53
+
54
+ if (!picked) return NextResponse.json({ directory: null, file: null })
55
+
56
+ const directory = mode === 'folder' ? picked : path.dirname(picked)
57
+ const file = mode === 'file' ? picked : null
58
+
59
+ return NextResponse.json({ directory, file })
60
+ }
@@ -0,0 +1,29 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import os from 'os'
5
+
6
+ export async function GET(req: NextRequest) {
7
+ const rawPath = req.nextUrl.searchParams.get('path')
8
+ const targetDir = rawPath || path.join(os.homedir(), 'Dev')
9
+
10
+ // Resolve ~ to home dir
11
+ const resolved = targetDir.startsWith('~')
12
+ ? path.join(os.homedir(), targetDir.slice(1))
13
+ : path.resolve(targetDir)
14
+
15
+ let dirs: Array<{ name: string; path: string }> = []
16
+ try {
17
+ dirs = fs.readdirSync(resolved)
18
+ .filter(d => {
19
+ if (d.startsWith('.')) return false
20
+ try { return fs.statSync(path.join(resolved, d)).isDirectory() } catch { return false }
21
+ })
22
+ .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
23
+ .map(d => ({ name: d, path: path.join(resolved, d) }))
24
+ } catch {}
25
+
26
+ const parentPath = resolved === '/' ? null : path.dirname(resolved)
27
+
28
+ return NextResponse.json({ dirs, currentPath: resolved, parentPath })
29
+ }
@@ -0,0 +1,47 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadDocuments, saveDocuments } from '@/lib/server/storage'
3
+
4
+ function normalizeObject(value: unknown): Record<string, unknown> {
5
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
6
+ return value as Record<string, unknown>
7
+ }
8
+
9
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
10
+ const { id } = await params
11
+ const docs = loadDocuments()
12
+ const doc = docs[id]
13
+ if (!doc) return NextResponse.json({ error: 'Document not found' }, { status: 404 })
14
+ return NextResponse.json(doc)
15
+ }
16
+
17
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
18
+ const { id } = await params
19
+ const body = await req.json().catch(() => ({}))
20
+ const docs = loadDocuments()
21
+ const doc = docs[id]
22
+ if (!doc) return NextResponse.json({ error: 'Document not found' }, { status: 404 })
23
+
24
+ if (body.title !== undefined) doc.title = body.title
25
+ if (body.fileName !== undefined) doc.fileName = body.fileName
26
+ if (body.sourcePath !== undefined) doc.sourcePath = body.sourcePath
27
+ if (body.content !== undefined) doc.content = body.content
28
+ if (body.method !== undefined) doc.method = body.method
29
+ if (body.metadata !== undefined) doc.metadata = normalizeObject(body.metadata)
30
+ doc.textLength = typeof body.textLength === 'number'
31
+ ? body.textLength
32
+ : String(doc.content || '').length
33
+ doc.updatedAt = Date.now()
34
+
35
+ docs[id] = doc
36
+ saveDocuments(docs)
37
+ return NextResponse.json(doc)
38
+ }
39
+
40
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
41
+ const { id } = await params
42
+ const docs = loadDocuments()
43
+ if (!docs[id]) return NextResponse.json({ error: 'Document not found' }, { status: 404 })
44
+ delete docs[id]
45
+ saveDocuments(docs)
46
+ return NextResponse.json({ ok: true })
47
+ }
@@ -0,0 +1,93 @@
1
+ import crypto from 'crypto'
2
+ import { NextResponse } from 'next/server'
3
+ import { loadDocuments, saveDocuments } from '@/lib/server/storage'
4
+
5
+ function normalizeLimit(raw: string | null, fallback = 10, max = 200): number {
6
+ if (!raw) return fallback
7
+ const parsed = Number.parseInt(raw, 10)
8
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback
9
+ return Math.min(parsed, max)
10
+ }
11
+
12
+ function normalizeObject(value: unknown): Record<string, unknown> {
13
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
14
+ return value as Record<string, unknown>
15
+ }
16
+
17
+ export async function GET(req: Request) {
18
+ const docs = loadDocuments()
19
+ const { searchParams } = new URL(req.url)
20
+ const q = (searchParams.get('q') || '').trim().toLowerCase()
21
+
22
+ if (!q) {
23
+ return NextResponse.json(docs)
24
+ }
25
+
26
+ const terms = q.split(/\s+/).filter(Boolean)
27
+ const limit = normalizeLimit(searchParams.get('limit'), 10, 100)
28
+ const rows = Object.values(docs)
29
+ .map((doc: any) => {
30
+ const title = String(doc.title || '')
31
+ const content = String(doc.content || '')
32
+ const hay = `${title}\n${content}`.toLowerCase()
33
+ if (!terms.every((term) => hay.includes(term))) return null
34
+
35
+ let score = hay.includes(q) ? 10 : 0
36
+ for (const term of terms) {
37
+ let idx = hay.indexOf(term)
38
+ while (idx !== -1) {
39
+ score += 1
40
+ idx = hay.indexOf(term, idx + term.length)
41
+ }
42
+ }
43
+
44
+ const first = terms[0] || q
45
+ const at = hay.indexOf(first)
46
+ const snippetStart = at >= 0 ? Math.max(0, at - 120) : 0
47
+ const snippetEnd = Math.min(content.length, snippetStart + 320)
48
+ const snippet = content.slice(snippetStart, snippetEnd).replace(/\s+/g, ' ').trim()
49
+
50
+ return {
51
+ id: doc.id,
52
+ title: doc.title,
53
+ fileName: doc.fileName,
54
+ sourcePath: doc.sourcePath,
55
+ textLength: doc.textLength || content.length,
56
+ updatedAt: doc.updatedAt,
57
+ score,
58
+ snippet,
59
+ }
60
+ })
61
+ .filter(Boolean)
62
+ .sort((a: any, b: any) => b.score - a.score)
63
+ .slice(0, limit)
64
+
65
+ return NextResponse.json(rows)
66
+ }
67
+
68
+ export async function POST(req: Request) {
69
+ const body = await req.json().catch(() => ({}))
70
+ const now = Date.now()
71
+ const docs = loadDocuments()
72
+ const id = body.id || crypto.randomBytes(6).toString('hex')
73
+ const fileName = body.fileName || body.filename || ''
74
+ const title = body.title || fileName || 'Untitled Document'
75
+ const content = typeof body.content === 'string' ? body.content : ''
76
+ const metadata = normalizeObject(body.metadata)
77
+
78
+ docs[id] = {
79
+ id,
80
+ title,
81
+ fileName,
82
+ sourcePath: body.sourcePath || body.path || '',
83
+ method: body.method || 'manual',
84
+ textLength: body.textLength || content.length,
85
+ content,
86
+ metadata,
87
+ createdAt: body.createdAt || now,
88
+ updatedAt: now,
89
+ }
90
+
91
+ saveDocuments(docs)
92
+ return NextResponse.json(docs[id])
93
+ }
@@ -0,0 +1,69 @@
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const MIME_MAP: Record<string, string> = {
6
+ '.html': 'text/html',
7
+ '.htm': 'text/html',
8
+ '.css': 'text/css',
9
+ '.js': 'application/javascript',
10
+ '.json': 'application/json',
11
+ '.svg': 'image/svg+xml',
12
+ '.png': 'image/png',
13
+ '.jpg': 'image/jpeg',
14
+ '.jpeg': 'image/jpeg',
15
+ '.gif': 'image/gif',
16
+ '.webp': 'image/webp',
17
+ '.txt': 'text/plain',
18
+ '.md': 'text/markdown',
19
+ '.ts': 'text/plain',
20
+ '.tsx': 'text/plain',
21
+ '.jsx': 'text/plain',
22
+ '.py': 'text/plain',
23
+ '.sh': 'text/plain',
24
+ }
25
+
26
+ const MAX_SIZE = 10 * 1024 * 1024 // 10MB
27
+
28
+ export async function GET(req: Request) {
29
+ const url = new URL(req.url)
30
+ const filePath = url.searchParams.get('path')
31
+
32
+ if (!filePath) {
33
+ return NextResponse.json({ error: 'Missing path parameter' }, { status: 400 })
34
+ }
35
+
36
+ // Resolve and normalize the path
37
+ const resolved = path.resolve(filePath)
38
+
39
+ // Block access to sensitive paths
40
+ const blocked = ['.env', 'credentials', '.ssh', '.gnupg', '.aws']
41
+ if (blocked.some((b) => resolved.includes(b))) {
42
+ return NextResponse.json({ error: 'Access denied' }, { status: 403 })
43
+ }
44
+
45
+ if (!fs.existsSync(resolved)) {
46
+ return NextResponse.json({ error: 'File not found' }, { status: 404 })
47
+ }
48
+
49
+ const stat = fs.statSync(resolved)
50
+ if (!stat.isFile()) {
51
+ return NextResponse.json({ error: 'Not a file' }, { status: 400 })
52
+ }
53
+ if (stat.size > MAX_SIZE) {
54
+ return NextResponse.json({ error: 'File too large' }, { status: 413 })
55
+ }
56
+
57
+ const ext = path.extname(resolved).toLowerCase()
58
+ const contentType = MIME_MAP[ext] || 'application/octet-stream'
59
+ const content = fs.readFileSync(resolved)
60
+
61
+ return new NextResponse(content, {
62
+ headers: {
63
+ 'Content-Type': contentType,
64
+ 'Content-Disposition': contentType.startsWith('text/') || contentType.startsWith('image/')
65
+ ? 'inline'
66
+ : `attachment; filename="${path.basename(resolved)}"`,
67
+ },
68
+ })
69
+ }
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { buildLLM } from '@/lib/server/build-llm'
3
+
4
+ /** Returns which provider/model the generate endpoints will use */
5
+ export async function GET() {
6
+ try {
7
+ const { provider, model } = await buildLLM()
8
+ return NextResponse.json({ provider, model })
9
+ } catch (err: any) {
10
+ return NextResponse.json({ provider: 'none', model: '', error: err.message })
11
+ }
12
+ }
@@ -0,0 +1,106 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { buildLLM } from '@/lib/server/build-llm'
4
+
5
+ const scheduleSchema = z.object({
6
+ name: z.string().describe('Short descriptive name for the schedule'),
7
+ taskPrompt: z.string().describe('The prompt/instructions the agent should execute when the schedule fires'),
8
+ scheduleType: z.enum(['cron', 'interval', 'once']).describe('Type of schedule'),
9
+ cron: z.string().optional().describe('Cron expression if scheduleType is cron, e.g. "0 9 * * 1" for every Monday at 9am'),
10
+ intervalMs: z.number().optional().describe('Interval in milliseconds if scheduleType is interval'),
11
+ })
12
+
13
+ const taskSchema = z.object({
14
+ title: z.string().describe('Short descriptive title for the task'),
15
+ description: z.string().describe('Detailed description of what needs to be done'),
16
+ })
17
+
18
+ const skillSchema = z.object({
19
+ name: z.string().describe('Short name for the skill'),
20
+ description: z.string().describe('One sentence describing what this skill does'),
21
+ content: z.string().describe('Full markdown content of the skill — detailed instructions, at least 3-4 paragraphs'),
22
+ })
23
+
24
+ const providerSchema = z.object({
25
+ name: z.string().describe('Display name for the provider, e.g. "Together AI", "Groq", "z.ai"'),
26
+ baseUrl: z.string().describe('The OpenAI-compatible API base URL, e.g. "https://api.together.xyz/v1" or "https://api.groq.com/openai/v1"'),
27
+ models: z.string().describe('Comma-separated list of available model IDs, e.g. "llama-3-70b,mixtral-8x7b"'),
28
+ requiresApiKey: z.boolean().describe('Whether this provider requires an API key'),
29
+ })
30
+
31
+ const SCHEMAS: Record<string, { schema: z.ZodObject<any>; prompt: string }> = {
32
+ schedule: {
33
+ schema: scheduleSchema,
34
+ prompt: `You are a schedule generator for SwarmClaw, an AI agent orchestration platform. The user will describe what they want scheduled. Generate a complete schedule definition.
35
+
36
+ Choose the appropriate scheduleType:
37
+ - "cron" for recurring schedules (provide a cron expression)
38
+ - "interval" for periodic execution (provide intervalMs in milliseconds)
39
+ - "once" for one-time execution
40
+
41
+ Make the taskPrompt detailed and specific — it should contain everything the agent needs to know to complete the task.`,
42
+ },
43
+ task: {
44
+ schema: taskSchema,
45
+ prompt: `You are a task generator for SwarmClaw, an AI agent orchestration platform. The user will describe a task they want to create. Generate a clear task definition.
46
+
47
+ Make the description thorough and actionable — include specific goals, acceptance criteria, and any relevant context. The description should give an AI agent enough information to complete the task autonomously.`,
48
+ },
49
+ skill: {
50
+ schema: skillSchema,
51
+ prompt: `You are a skill generator for SwarmClaw, an AI agent orchestration platform. Skills are reusable markdown instruction sets that get injected into agent system prompts.
52
+
53
+ The user will describe what skill they want. Generate a comprehensive skill definition with detailed markdown content. The content should be:
54
+ - Written as instructions/guidelines for an AI agent
55
+ - Thorough and specific (at least 3-4 paragraphs)
56
+ - Structured with markdown headings, lists, and examples
57
+ - Focused on actionable guidance the agent can follow`,
58
+ },
59
+ provider: {
60
+ schema: providerSchema,
61
+ prompt: `You are a provider configuration generator for SwarmClaw, an AI agent orchestration platform. The user will name an LLM provider they want to add.
62
+
63
+ Generate the correct OpenAI-compatible API configuration. You should know the base URLs and model names for popular providers:
64
+ - Together AI: https://api.together.xyz/v1
65
+ - Groq: https://api.groq.com/openai/v1
66
+ - Fireworks: https://api.fireworks.ai/inference/v1
67
+ - Perplexity: https://api.perplexity.ai
68
+ - Mistral: https://api.mistral.ai/v1
69
+ - DeepSeek: https://api.deepseek.com/v1
70
+ - OpenRouter: https://openrouter.ai/api/v1
71
+ - xAI/Grok: https://api.x.ai/v1
72
+ - z.ai: https://api.z.ai/v1
73
+
74
+ List the most popular/recommended models for the provider as comma-separated values. Most providers require an API key.
75
+ If you don't know the exact URL, make your best guess based on common patterns (provider domain + /v1).`,
76
+ },
77
+ }
78
+
79
+ export async function POST(req: Request) {
80
+ const { type, prompt } = await req.json()
81
+ if (!prompt?.trim()) {
82
+ return NextResponse.json({ error: 'prompt is required' }, { status: 400 })
83
+ }
84
+ const config = SCHEMAS[type]
85
+ if (!config) {
86
+ return NextResponse.json({ error: `Invalid type: ${type}. Must be one of: ${Object.keys(SCHEMAS).join(', ')}` }, { status: 400 })
87
+ }
88
+
89
+ try {
90
+ const { llm, provider } = await buildLLM()
91
+ const structured = provider === 'anthropic'
92
+ ? llm.withStructuredOutput(config.schema)
93
+ : (llm as any).withStructuredOutput(config.schema, {
94
+ name: `${type}_definition`,
95
+ method: 'functionCalling',
96
+ })
97
+ const result = await structured.invoke([
98
+ { role: 'system' as const, content: config.prompt },
99
+ { role: 'user' as const, content: prompt },
100
+ ], { signal: AbortSignal.timeout(60_000) })
101
+ return NextResponse.json(result)
102
+ } catch (err: unknown) {
103
+ const message = err instanceof Error ? err.message : 'Generation failed'
104
+ return NextResponse.json({ error: message }, { status: 500 })
105
+ }
106
+ }
@@ -0,0 +1,6 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { localIP } from '@/lib/server/storage'
3
+
4
+ export async function GET() {
5
+ return NextResponse.json({ ip: localIP(), port: parseInt(process.env.PORT || '3000') })
6
+ }
@@ -0,0 +1,61 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getMemoryDb } from '@/lib/server/memory-db'
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params
6
+ const db = getMemoryDb()
7
+ const entry = db.get(id)
8
+ if (!entry || entry.category !== 'knowledge') {
9
+ return new NextResponse(null, { status: 404 })
10
+ }
11
+ return NextResponse.json(entry)
12
+ }
13
+
14
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
15
+ const { id } = await params
16
+ const db = getMemoryDb()
17
+ const existing = db.get(id)
18
+ if (!existing || existing.category !== 'knowledge') {
19
+ return new NextResponse(null, { status: 404 })
20
+ }
21
+
22
+ const body = await req.json().catch(() => null)
23
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
24
+ return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
25
+ }
26
+
27
+ const { title, content, tags } = body as Record<string, unknown>
28
+
29
+ const updates: Record<string, unknown> = {}
30
+ if (typeof title === 'string' && title.trim()) {
31
+ updates.title = title.trim()
32
+ }
33
+ if (typeof content === 'string') {
34
+ updates.content = content
35
+ }
36
+
37
+ const existingMeta = (existing.metadata || {}) as Record<string, unknown>
38
+ if (Array.isArray(tags)) {
39
+ const normalizedTags = (tags as unknown[]).filter(
40
+ (t): t is string => typeof t === 'string' && t.trim().length > 0,
41
+ )
42
+ updates.metadata = { ...existingMeta, tags: normalizedTags }
43
+ }
44
+
45
+ const updated = db.update(id, updates)
46
+ if (!updated) {
47
+ return new NextResponse(null, { status: 404 })
48
+ }
49
+ return NextResponse.json(updated)
50
+ }
51
+
52
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
53
+ const { id } = await params
54
+ const db = getMemoryDb()
55
+ const existing = db.get(id)
56
+ if (!existing || existing.category !== 'knowledge') {
57
+ return new NextResponse(null, { status: 404 })
58
+ }
59
+ db.delete(id)
60
+ return NextResponse.json({ deleted: id })
61
+ }