@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,336 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState, useMemo } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { createSchedule, updateSchedule, deleteSchedule } from '@/lib/schedules'
6
+ import { api } from '@/lib/api-client'
7
+ import { BottomSheet } from '@/components/shared/bottom-sheet'
8
+ import { AiGenBlock } from '@/components/shared/ai-gen-block'
9
+ import type { ScheduleType, ScheduleStatus } from '@/types'
10
+ import cronstrue from 'cronstrue'
11
+
12
+ const CRON_PRESETS = [
13
+ { label: 'Every hour', cron: '0 * * * *' },
14
+ { label: 'Every 6 hours', cron: '0 */6 * * *' },
15
+ { label: 'Daily at 9am', cron: '0 9 * * *' },
16
+ { label: 'Weekly Mon 9am', cron: '0 9 * * 1' },
17
+ ]
18
+
19
+ function getNextRuns(cron: string, count: number = 3): Date[] {
20
+ try {
21
+ // Simple cron parser for next N runs
22
+ const { parseExpression } = require('cron-parser')
23
+ const interval = parseExpression(cron)
24
+ const runs: Date[] = []
25
+ for (let i = 0; i < count; i++) {
26
+ runs.push(interval.next().toDate())
27
+ }
28
+ return runs
29
+ } catch {
30
+ return []
31
+ }
32
+ }
33
+
34
+ function formatCronHuman(cron: string): string {
35
+ try {
36
+ return cronstrue.toString(cron, { use24HourTimeFormat: false })
37
+ } catch {
38
+ return 'Invalid cron expression'
39
+ }
40
+ }
41
+
42
+ function formatDate(d: Date): string {
43
+ return d.toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' }) +
44
+ ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
45
+ }
46
+
47
+ export function ScheduleSheet() {
48
+ const open = useAppStore((s) => s.scheduleSheetOpen)
49
+ const setOpen = useAppStore((s) => s.setScheduleSheetOpen)
50
+ const editingId = useAppStore((s) => s.editingScheduleId)
51
+ const setEditingId = useAppStore((s) => s.setEditingScheduleId)
52
+ const schedules = useAppStore((s) => s.schedules)
53
+ const loadSchedules = useAppStore((s) => s.loadSchedules)
54
+ const agents = useAppStore((s) => s.agents)
55
+ const loadAgents = useAppStore((s) => s.loadAgents)
56
+
57
+ const [name, setName] = useState('')
58
+ const [agentId, setAgentId] = useState('')
59
+ const [taskPrompt, setTaskPrompt] = useState('')
60
+ const [scheduleType, setScheduleType] = useState<ScheduleType>('cron')
61
+ const [cron, setCron] = useState('0 * * * *')
62
+ const [intervalMs, setIntervalMs] = useState(3600000)
63
+ const [status, setStatus] = useState<ScheduleStatus>('active')
64
+ const [customCron, setCustomCron] = useState(false)
65
+
66
+ // AI generation state
67
+ const [aiPrompt, setAiPrompt] = useState('')
68
+ const [generating, setGenerating] = useState(false)
69
+ const [generated, setGenerated] = useState(false)
70
+ const [genError, setGenError] = useState('')
71
+ const appSettings = useAppStore((s) => s.appSettings)
72
+ const loadSettings = useAppStore((s) => s.loadSettings)
73
+
74
+ const editing = editingId ? schedules[editingId] : null
75
+ const agentList = Object.values(agents)
76
+
77
+ const handleGenerate = async () => {
78
+ if (!aiPrompt.trim()) return
79
+ setGenerating(true)
80
+ setGenError('')
81
+ try {
82
+ const result = await api<{ name?: string; taskPrompt?: string; scheduleType?: ScheduleType; cron?: string; intervalMs?: number; error?: string }>('POST', '/generate', { type: 'schedule', prompt: aiPrompt })
83
+ if (result.error) {
84
+ setGenError(result.error)
85
+ } else if (result.name || result.taskPrompt) {
86
+ if (result.name) setName(result.name)
87
+ if (result.taskPrompt) setTaskPrompt(result.taskPrompt)
88
+ if (result.scheduleType) setScheduleType(result.scheduleType)
89
+ if (result.cron) { setCron(result.cron); setCustomCron(true) }
90
+ if (result.intervalMs) setIntervalMs(result.intervalMs)
91
+ setGenerated(true)
92
+ } else {
93
+ setGenError('AI returned empty response — try again')
94
+ }
95
+ } catch (err: unknown) {
96
+ setGenError(err instanceof Error ? err.message : 'Generation failed')
97
+ }
98
+ setGenerating(false)
99
+ }
100
+
101
+ useEffect(() => {
102
+ if (open) {
103
+ loadAgents()
104
+ loadSettings()
105
+ setAiPrompt('')
106
+ setGenerating(false)
107
+ setGenerated(false)
108
+ setGenError('')
109
+ if (editing) {
110
+ setName(editing.name || '')
111
+ setAgentId(editing.agentId)
112
+ setTaskPrompt(editing.taskPrompt)
113
+ setScheduleType(editing.scheduleType)
114
+ setCron(editing.cron || '0 * * * *')
115
+ setIntervalMs(editing.intervalMs || 3600000)
116
+ setStatus(editing.status)
117
+ setCustomCron(!CRON_PRESETS.some((p) => p.cron === editing.cron))
118
+ } else {
119
+ setName('')
120
+ setAgentId('')
121
+ setTaskPrompt('')
122
+ setScheduleType('cron')
123
+ setCron('0 * * * *')
124
+ setIntervalMs(3600000)
125
+ setStatus('active')
126
+ setCustomCron(false)
127
+ }
128
+ }
129
+ }, [open, editingId])
130
+
131
+ const cronHuman = useMemo(() => formatCronHuman(cron), [cron])
132
+ const nextRuns = useMemo(() => getNextRuns(cron), [cron])
133
+
134
+ const onClose = () => {
135
+ setOpen(false)
136
+ setEditingId(null)
137
+ }
138
+
139
+ const handleSave = async () => {
140
+ const data = {
141
+ name: name.trim(),
142
+ agentId,
143
+ taskPrompt,
144
+ scheduleType,
145
+ cron: scheduleType === 'cron' ? cron : undefined,
146
+ intervalMs: scheduleType === 'interval' ? intervalMs : undefined,
147
+ runAt: scheduleType === 'once' ? Date.now() + intervalMs : undefined,
148
+ status,
149
+ }
150
+ if (editing) {
151
+ await updateSchedule(editing.id, data)
152
+ } else {
153
+ await createSchedule(data)
154
+ }
155
+ await loadSchedules()
156
+ onClose()
157
+ }
158
+
159
+ const handleDelete = async () => {
160
+ if (editing) {
161
+ await deleteSchedule(editing.id)
162
+ await loadSchedules()
163
+ onClose()
164
+ }
165
+ }
166
+
167
+ const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
168
+
169
+ return (
170
+ <BottomSheet open={open} onClose={onClose} wide>
171
+ <div className="mb-10">
172
+ <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
173
+ {editing ? 'Edit Schedule' : 'New Schedule'}
174
+ </h2>
175
+ <p className="text-[14px] text-text-3">Automate agent tasks on a schedule</p>
176
+ </div>
177
+
178
+ {/* AI Generation */}
179
+ {!editing && <AiGenBlock
180
+ aiPrompt={aiPrompt} setAiPrompt={setAiPrompt}
181
+ generating={generating} generated={generated} genError={genError}
182
+ onGenerate={handleGenerate} appSettings={appSettings}
183
+ placeholder='Describe the schedule, e.g. "Run keyword research every Monday at 9am"'
184
+ />}
185
+
186
+ <div className="mb-8">
187
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
188
+ <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Daily keyword research" className={inputClass} style={{ fontFamily: 'inherit' }} />
189
+ </div>
190
+
191
+ <div className="mb-8">
192
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Agent</label>
193
+ <select value={agentId || ''} onChange={(e) => setAgentId(e.target.value)} className={`${inputClass} appearance-none cursor-pointer`} style={{ fontFamily: 'inherit' }}>
194
+ <option value="">Select agent...</option>
195
+ {agentList.map((p) => (
196
+ <option key={p.id} value={p.id}>{p.name}{p.isOrchestrator ? ' (Orchestrator)' : ''}</option>
197
+ ))}
198
+ </select>
199
+ </div>
200
+
201
+ <div className="mb-8">
202
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Task Prompt</label>
203
+ <textarea
204
+ value={taskPrompt}
205
+ onChange={(e) => setTaskPrompt(e.target.value)}
206
+ placeholder="What should the agent do when triggered?"
207
+ rows={4}
208
+ className={`${inputClass} resize-y min-h-[100px]`}
209
+ style={{ fontFamily: 'inherit' }}
210
+ />
211
+ </div>
212
+
213
+ <div className="mb-8">
214
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Schedule Type</label>
215
+ <div className="grid grid-cols-3 gap-3">
216
+ {(['cron', 'interval', 'once'] as ScheduleType[]).map((t) => (
217
+ <button
218
+ key={t}
219
+ onClick={() => setScheduleType(t)}
220
+ className={`py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
221
+ active:scale-[0.97] text-[14px] font-600 capitalize border
222
+ ${scheduleType === t
223
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
224
+ : 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
225
+ style={{ fontFamily: 'inherit' }}
226
+ >
227
+ {t}
228
+ </button>
229
+ ))}
230
+ </div>
231
+ </div>
232
+
233
+ {scheduleType === 'cron' && (
234
+ <div className="mb-8">
235
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Schedule</label>
236
+
237
+ {/* Preset buttons */}
238
+ <div className="flex flex-wrap gap-2 mb-4">
239
+ {CRON_PRESETS.map((p) => (
240
+ <button
241
+ key={p.cron}
242
+ onClick={() => { setCron(p.cron); setCustomCron(false) }}
243
+ className={`px-3.5 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
244
+ ${cron === p.cron && !customCron
245
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
246
+ : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
247
+ style={{ fontFamily: 'inherit' }}
248
+ >
249
+ {p.label}
250
+ </button>
251
+ ))}
252
+ <button
253
+ onClick={() => setCustomCron(true)}
254
+ className={`px-3.5 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
255
+ ${customCron
256
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
257
+ : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
258
+ style={{ fontFamily: 'inherit' }}
259
+ >
260
+ Custom
261
+ </button>
262
+ </div>
263
+
264
+ {/* Custom cron input */}
265
+ {customCron && (
266
+ <input type="text" value={cron} onChange={(e) => setCron(e.target.value)} placeholder="0 * * * *" className={`${inputClass} font-mono text-[14px] mb-3`} />
267
+ )}
268
+
269
+ {/* Human-readable preview */}
270
+ <div className="p-4 rounded-[14px] bg-surface border border-white/[0.06]">
271
+ <div className="text-[14px] text-text-2 font-600 mb-2">{cronHuman}</div>
272
+ {cron && (
273
+ <div className="font-mono text-[12px] text-text-3/50 mb-3">{cron}</div>
274
+ )}
275
+ {nextRuns.length > 0 && (
276
+ <div className="space-y-1.5">
277
+ <div className="text-[11px] text-text-3/60 uppercase tracking-wider font-600">Next runs</div>
278
+ {nextRuns.map((d, i) => (
279
+ <div key={i} className="text-[12px] text-text-3 font-mono">{formatDate(d)}</div>
280
+ ))}
281
+ </div>
282
+ )}
283
+ </div>
284
+ </div>
285
+ )}
286
+
287
+ {scheduleType === 'interval' && (
288
+ <div className="mb-8">
289
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Interval (minutes)</label>
290
+ <input
291
+ type="number"
292
+ value={Math.round(intervalMs / 60000)}
293
+ onChange={(e) => setIntervalMs(Math.max(1, parseInt(e.target.value) || 1) * 60000)}
294
+ className={inputClass}
295
+ style={{ fontFamily: 'inherit' }}
296
+ />
297
+ </div>
298
+ )}
299
+
300
+ {editing && (
301
+ <div className="mb-8">
302
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Status</label>
303
+ <div className="flex gap-2">
304
+ {(['active', 'paused'] as ScheduleStatus[]).map((s) => (
305
+ <button
306
+ key={s}
307
+ onClick={() => setStatus(s)}
308
+ className={`px-4 py-2 rounded-[10px] text-[13px] font-600 capitalize cursor-pointer transition-all border
309
+ ${status === s
310
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
311
+ : 'bg-surface border-white/[0.06] text-text-3'}`}
312
+ style={{ fontFamily: 'inherit' }}
313
+ >
314
+ {s}
315
+ </button>
316
+ ))}
317
+ </div>
318
+ </div>
319
+ )}
320
+
321
+ <div className="flex gap-3 pt-2 border-t border-white/[0.04]">
322
+ {editing && (
323
+ <button onClick={handleDelete} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
324
+ Delete
325
+ </button>
326
+ )}
327
+ <button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
328
+ Cancel
329
+ </button>
330
+ <button onClick={handleSave} disabled={!name.trim() || !agentId} className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110" style={{ fontFamily: 'inherit' }}>
331
+ {editing ? 'Save' : 'Create'}
332
+ </button>
333
+ </div>
334
+ </BottomSheet>
335
+ )
336
+ }
@@ -0,0 +1,180 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } 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
+
8
+ const inputClass = 'w-full px-4 py-3 rounded-[14px] bg-bg border border-white/[0.06] text-text text-[14px] outline-none focus:border-accent-bright/40 transition-colors placeholder:text-text-3/70'
9
+
10
+ export function SecretSheet() {
11
+ const open = useAppStore((s) => s.secretSheetOpen)
12
+ const setOpen = useAppStore((s) => s.setSecretSheetOpen)
13
+ const editingId = useAppStore((s) => s.editingSecretId)
14
+ const setEditingId = useAppStore((s) => s.setEditingSecretId)
15
+ const secrets = useAppStore((s) => s.secrets)
16
+ const loadSecrets = useAppStore((s) => s.loadSecrets)
17
+ const agents = useAppStore((s) => s.agents)
18
+ const loadAgents = useAppStore((s) => s.loadAgents)
19
+
20
+ const [name, setName] = useState('')
21
+ const [service, setService] = useState('')
22
+ const [value, setValue] = useState('')
23
+ const [scope, setScope] = useState<'global' | 'agent'>('global')
24
+ const [agentIds, setAgentIds] = useState<string[]>([])
25
+ const [saving, setSaving] = useState(false)
26
+
27
+ const editing = editingId ? secrets[editingId] : null
28
+ const orchestrators = Object.values(agents).filter((p) => p.isOrchestrator)
29
+
30
+ useEffect(() => {
31
+ if (open) loadAgents()
32
+ }, [open])
33
+
34
+ useEffect(() => {
35
+ if (editing) {
36
+ setName(editing.name)
37
+ setService(editing.service)
38
+ setValue('')
39
+ setScope(editing.scope)
40
+ setAgentIds(editing.agentIds || [])
41
+ } else {
42
+ setName('')
43
+ setService('')
44
+ setValue('')
45
+ setScope('global')
46
+ setAgentIds([])
47
+ }
48
+ }, [editing, open])
49
+
50
+ const handleClose = () => {
51
+ setOpen(false)
52
+ setEditingId(null)
53
+ }
54
+
55
+ const handleSave = async () => {
56
+ if (!name.trim() || (!editing && !value.trim())) return
57
+ setSaving(true)
58
+ try {
59
+ if (editing) {
60
+ await api('PUT', `/secrets/${editing.id}`, {
61
+ name: name.trim(),
62
+ service: service.trim(),
63
+ scope,
64
+ agentIds: scope === 'agent' ? agentIds : [],
65
+ })
66
+ } else {
67
+ await api('POST', '/secrets', {
68
+ name: name.trim(),
69
+ service: service.trim(),
70
+ value: value.trim(),
71
+ scope,
72
+ agentIds: scope === 'agent' ? agentIds : [],
73
+ })
74
+ }
75
+ await loadSecrets()
76
+ handleClose()
77
+ } catch (err: any) {
78
+ console.error('Failed to save secret:', err.message)
79
+ } finally {
80
+ setSaving(false)
81
+ }
82
+ }
83
+
84
+ const handleDelete = async () => {
85
+ if (!editing) return
86
+ try {
87
+ await api('DELETE', `/secrets/${editing.id}`)
88
+ await loadSecrets()
89
+ handleClose()
90
+ } catch (err: any) {
91
+ console.error('Failed to delete secret:', err.message)
92
+ }
93
+ }
94
+
95
+ return (
96
+ <BottomSheet open={open} onClose={handleClose}>
97
+ <div className="space-y-5">
98
+ <h2 className="font-display text-[20px] font-700 tracking-[-0.02em]">{editing ? 'Edit Secret' : 'New Secret'}</h2>
99
+ <div>
100
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Name</label>
101
+ <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. My Gmail API Key" className={inputClass} style={{ fontFamily: 'inherit' }} />
102
+ </div>
103
+
104
+ <div>
105
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Service</label>
106
+ <input type="text" value={service} onChange={(e) => setService(e.target.value)} placeholder="e.g. gmail, ahrefs, custom" className={inputClass} style={{ fontFamily: 'inherit' }} />
107
+ </div>
108
+
109
+ <div>
110
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">
111
+ {editing ? 'Value (leave blank to keep current)' : 'Value'}
112
+ </label>
113
+ <input type="password" value={value} onChange={(e) => setValue(e.target.value)} placeholder="API key, password, token..." className={inputClass} style={{ fontFamily: 'inherit' }} />
114
+ </div>
115
+
116
+ <div>
117
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Scope</label>
118
+ <div className="flex p-1 rounded-[12px] bg-bg border border-white/[0.06]">
119
+ {(['global', 'agent'] as const).map((s) => (
120
+ <button
121
+ key={s}
122
+ onClick={() => setScope(s)}
123
+ className={`flex-1 py-2.5 rounded-[10px] text-center cursor-pointer transition-all text-[13px] font-600 border-none ${
124
+ scope === s ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'
125
+ }`}
126
+ style={{ fontFamily: 'inherit' }}
127
+ >
128
+ {s === 'global' ? 'All Orchestrators' : 'Specific'}
129
+ </button>
130
+ ))}
131
+ </div>
132
+ </div>
133
+
134
+ {scope === 'agent' && orchestrators.length > 0 && (
135
+ <div>
136
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Orchestrators</label>
137
+ <div className="flex flex-wrap gap-2">
138
+ {orchestrators.map((p) => (
139
+ <button
140
+ key={p.id}
141
+ onClick={() => setAgentIds((prev) => prev.includes(p.id) ? prev.filter((x) => x !== p.id) : [...prev, p.id])}
142
+ className={`px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border ${
143
+ agentIds.includes(p.id)
144
+ ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
145
+ : 'bg-bg border-white/[0.06] text-text-3 hover:text-text-2'
146
+ }`}
147
+ style={{ fontFamily: 'inherit' }}
148
+ >
149
+ {p.name}
150
+ </button>
151
+ ))}
152
+ </div>
153
+ </div>
154
+ )}
155
+
156
+ <div className="flex gap-3 pt-3">
157
+ {editing && (
158
+ <button
159
+ onClick={handleDelete}
160
+ className="px-5 py-3 rounded-[14px] border border-danger/30 bg-transparent text-danger text-[14px] font-600 cursor-pointer hover:bg-danger/10 transition-colors"
161
+ style={{ fontFamily: 'inherit' }}
162
+ >
163
+ Delete
164
+ </button>
165
+ )}
166
+ <div className="flex-1" />
167
+ <button onClick={handleClose} className="px-5 py-3 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-colors" style={{ fontFamily: 'inherit' }}>Cancel</button>
168
+ <button
169
+ onClick={handleSave}
170
+ disabled={saving || !name.trim() || (!editing && !value.trim())}
171
+ className="px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
172
+ style={{ fontFamily: 'inherit' }}
173
+ >
174
+ {saving ? 'Saving...' : editing ? 'Update' : 'Save'}
175
+ </button>
176
+ </div>
177
+ </div>
178
+ </BottomSheet>
179
+ )
180
+ }
@@ -0,0 +1,91 @@
1
+ 'use client'
2
+
3
+ import { useEffect } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+
6
+ interface Props {
7
+ inSidebar?: boolean
8
+ }
9
+
10
+ export function SecretsList({ inSidebar }: Props) {
11
+ const secrets = useAppStore((s) => s.secrets)
12
+ const loadSecrets = useAppStore((s) => s.loadSecrets)
13
+ const agents = useAppStore((s) => s.agents)
14
+ const setSecretSheetOpen = useAppStore((s) => s.setSecretSheetOpen)
15
+ const setEditingSecretId = useAppStore((s) => s.setEditingSecretId)
16
+
17
+ useEffect(() => {
18
+ loadSecrets()
19
+ }, [])
20
+
21
+ const secretList = Object.values(secrets)
22
+
23
+ if (!secretList.length) {
24
+ return (
25
+ <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 text-center">
26
+ <div className="w-12 h-12 rounded-[14px] bg-white/[0.03] border border-white/[0.06] flex items-center justify-center mb-4">
27
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3">
28
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
29
+ <path d="M7 11V7a5 5 0 0 1 10 0v4" />
30
+ </svg>
31
+ </div>
32
+ <p className="text-[13px] text-text-3 mb-1 font-600">No secrets yet</p>
33
+ <p className="text-[12px] text-text-3/60">Add API keys & credentials for orchestrators</p>
34
+ <button
35
+ onClick={() => { setEditingSecretId(null); setSecretSheetOpen(true) }}
36
+ className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[13px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
37
+ style={{ fontFamily: 'inherit' }}
38
+ >
39
+ + Add Secret
40
+ </button>
41
+ </div>
42
+ )
43
+ }
44
+
45
+ return (
46
+ <div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
47
+ <div className="space-y-2">
48
+ {secretList.map((secret) => {
49
+ const scopeLabel = secret.scope === 'global'
50
+ ? 'All orchestrators'
51
+ : `${secret.agentIds.length} orchestrator(s)`
52
+ const scopedNames = secret.scope === 'agent'
53
+ ? secret.agentIds.map((id) => agents[id]?.name).filter(Boolean).join(', ')
54
+ : null
55
+ return (
56
+ <button
57
+ key={secret.id}
58
+ onClick={() => {
59
+ setEditingSecretId(secret.id)
60
+ setSecretSheetOpen(true)
61
+ }}
62
+ className="w-full text-left p-4 rounded-[14px] bg-surface border border-white/[0.06]
63
+ hover:border-white/[0.1] cursor-pointer transition-all group"
64
+ style={{ fontFamily: 'inherit' }}
65
+ >
66
+ <div className="flex items-center gap-2.5 mb-1">
67
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3 shrink-0">
68
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
69
+ <path d="M7 11V7a5 5 0 0 1 10 0v4" />
70
+ </svg>
71
+ <span className="text-[14px] font-600 text-text truncate">{secret.name}</span>
72
+ </div>
73
+ <div className="flex items-center gap-2 pl-[22px]">
74
+ <span className="text-[11px] font-mono text-text-3">{secret.service}</span>
75
+ <span className="text-[11px] text-text-3/60">·</span>
76
+ <span className={`text-[10px] font-600 ${
77
+ secret.scope === 'global' ? 'text-emerald-400' : 'text-amber-400'
78
+ }`}>
79
+ {scopeLabel}
80
+ </span>
81
+ </div>
82
+ {scopedNames && (
83
+ <div className="text-[10px] text-text-3/50 mt-1 pl-[22px] truncate">{scopedNames}</div>
84
+ )}
85
+ </button>
86
+ )
87
+ })}
88
+ </div>
89
+ </div>
90
+ )
91
+ }