gigaclaw 1.4.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 (249) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +237 -0
  3. package/api/CLAUDE.md +19 -0
  4. package/api/index.js +265 -0
  5. package/bin/cli.js +823 -0
  6. package/bin/local.sh +85 -0
  7. package/bin/postinstall.js +63 -0
  8. package/config/index.js +26 -0
  9. package/config/instrumentation.js +62 -0
  10. package/drizzle/0000_initial.sql +52 -0
  11. package/drizzle/0001_nostalgic_sersi.sql +11 -0
  12. package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
  13. package/drizzle/0003_rename_code_workspaces.sql +5 -0
  14. package/drizzle/meta/0000_snapshot.json +321 -0
  15. package/drizzle/meta/0001_snapshot.json +390 -0
  16. package/drizzle/meta/0002_snapshot.json +411 -0
  17. package/drizzle/meta/0003_snapshot.json +419 -0
  18. package/drizzle/meta/_journal.json +34 -0
  19. package/lib/actions.js +44 -0
  20. package/lib/ai/agent.js +86 -0
  21. package/lib/ai/index.js +342 -0
  22. package/lib/ai/model.js +180 -0
  23. package/lib/ai/tools.js +269 -0
  24. package/lib/ai/web-search.js +42 -0
  25. package/lib/auth/actions.js +28 -0
  26. package/lib/auth/config.js +27 -0
  27. package/lib/auth/edge-config.js +27 -0
  28. package/lib/auth/index.js +27 -0
  29. package/lib/auth/middleware.js +62 -0
  30. package/lib/channels/base.js +56 -0
  31. package/lib/channels/index.js +15 -0
  32. package/lib/channels/telegram.js +148 -0
  33. package/lib/chat/actions.js +579 -0
  34. package/lib/chat/api.js +140 -0
  35. package/lib/chat/components/app-sidebar.js +213 -0
  36. package/lib/chat/components/app-sidebar.jsx +279 -0
  37. package/lib/chat/components/chat-header.js +192 -0
  38. package/lib/chat/components/chat-header.jsx +223 -0
  39. package/lib/chat/components/chat-input.js +236 -0
  40. package/lib/chat/components/chat-input.jsx +249 -0
  41. package/lib/chat/components/chat-nav-context.js +11 -0
  42. package/lib/chat/components/chat-nav-context.jsx +11 -0
  43. package/lib/chat/components/chat-page.js +99 -0
  44. package/lib/chat/components/chat-page.jsx +121 -0
  45. package/lib/chat/components/chat.js +153 -0
  46. package/lib/chat/components/chat.jsx +199 -0
  47. package/lib/chat/components/chats-page.js +367 -0
  48. package/lib/chat/components/chats-page.jsx +394 -0
  49. package/lib/chat/components/code-mode-toggle.js +132 -0
  50. package/lib/chat/components/code-mode-toggle.jsx +163 -0
  51. package/lib/chat/components/crons-page.js +172 -0
  52. package/lib/chat/components/crons-page.jsx +244 -0
  53. package/lib/chat/components/greeting.js +11 -0
  54. package/lib/chat/components/greeting.jsx +16 -0
  55. package/lib/chat/components/icons.js +805 -0
  56. package/lib/chat/components/icons.jsx +751 -0
  57. package/lib/chat/components/index.js +20 -0
  58. package/lib/chat/components/message.js +363 -0
  59. package/lib/chat/components/message.jsx +422 -0
  60. package/lib/chat/components/messages.js +65 -0
  61. package/lib/chat/components/messages.jsx +74 -0
  62. package/lib/chat/components/notifications-page.js +56 -0
  63. package/lib/chat/components/notifications-page.jsx +87 -0
  64. package/lib/chat/components/page-layout.js +21 -0
  65. package/lib/chat/components/page-layout.jsx +28 -0
  66. package/lib/chat/components/pull-requests-page.js +103 -0
  67. package/lib/chat/components/pull-requests-page.jsx +113 -0
  68. package/lib/chat/components/settings-layout.js +39 -0
  69. package/lib/chat/components/settings-layout.jsx +53 -0
  70. package/lib/chat/components/settings-secrets-page.js +216 -0
  71. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  72. package/lib/chat/components/sidebar-history-item.js +138 -0
  73. package/lib/chat/components/sidebar-history-item.jsx +119 -0
  74. package/lib/chat/components/sidebar-history.js +167 -0
  75. package/lib/chat/components/sidebar-history.jsx +220 -0
  76. package/lib/chat/components/sidebar-user-nav.js +61 -0
  77. package/lib/chat/components/sidebar-user-nav.jsx +77 -0
  78. package/lib/chat/components/swarm-page.js +157 -0
  79. package/lib/chat/components/swarm-page.jsx +210 -0
  80. package/lib/chat/components/tool-call.js +89 -0
  81. package/lib/chat/components/tool-call.jsx +107 -0
  82. package/lib/chat/components/triggers-page.js +153 -0
  83. package/lib/chat/components/triggers-page.jsx +221 -0
  84. package/lib/chat/components/ui/combobox.js +98 -0
  85. package/lib/chat/components/ui/combobox.jsx +114 -0
  86. package/lib/chat/components/ui/confirm-dialog.js +53 -0
  87. package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
  88. package/lib/chat/components/ui/dropdown-menu.js +194 -0
  89. package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
  90. package/lib/chat/components/ui/rename-dialog.js +78 -0
  91. package/lib/chat/components/ui/rename-dialog.jsx +74 -0
  92. package/lib/chat/components/ui/scroll-area.js +13 -0
  93. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  94. package/lib/chat/components/ui/separator.js +21 -0
  95. package/lib/chat/components/ui/separator.jsx +18 -0
  96. package/lib/chat/components/ui/sheet.js +75 -0
  97. package/lib/chat/components/ui/sheet.jsx +95 -0
  98. package/lib/chat/components/ui/sidebar.js +228 -0
  99. package/lib/chat/components/ui/sidebar.jsx +246 -0
  100. package/lib/chat/components/ui/tooltip.js +56 -0
  101. package/lib/chat/components/ui/tooltip.jsx +66 -0
  102. package/lib/chat/components/upgrade-dialog.js +151 -0
  103. package/lib/chat/components/upgrade-dialog.jsx +170 -0
  104. package/lib/chat/utils.js +11 -0
  105. package/lib/code/actions.js +153 -0
  106. package/lib/code/code-page.js +22 -0
  107. package/lib/code/code-page.jsx +25 -0
  108. package/lib/code/index.js +1 -0
  109. package/lib/code/terminal-view.js +201 -0
  110. package/lib/code/terminal-view.jsx +224 -0
  111. package/lib/code/ws-proxy.js +80 -0
  112. package/lib/cron.js +246 -0
  113. package/lib/db/api-keys.js +163 -0
  114. package/lib/db/chats.js +168 -0
  115. package/lib/db/code-workspaces.js +110 -0
  116. package/lib/db/index.js +52 -0
  117. package/lib/db/notifications.js +99 -0
  118. package/lib/db/schema.js +66 -0
  119. package/lib/db/update-check.js +96 -0
  120. package/lib/db/users.js +89 -0
  121. package/lib/paths.js +42 -0
  122. package/lib/tools/create-job.js +97 -0
  123. package/lib/tools/docker.js +146 -0
  124. package/lib/tools/github.js +271 -0
  125. package/lib/tools/openai.js +35 -0
  126. package/lib/tools/telegram.js +292 -0
  127. package/lib/triggers.js +104 -0
  128. package/lib/utils/render-md.js +111 -0
  129. package/package.json +118 -0
  130. package/setup/lib/auth.mjs +81 -0
  131. package/setup/lib/env.mjs +21 -0
  132. package/setup/lib/fs-utils.mjs +20 -0
  133. package/setup/lib/github.mjs +149 -0
  134. package/setup/lib/prerequisites.mjs +155 -0
  135. package/setup/lib/prompts.mjs +267 -0
  136. package/setup/lib/providers.mjs +105 -0
  137. package/setup/lib/sync.mjs +125 -0
  138. package/setup/lib/targets.mjs +45 -0
  139. package/setup/lib/telegram-verify.mjs +63 -0
  140. package/setup/lib/telegram.mjs +76 -0
  141. package/setup/setup-cloud.mjs +833 -0
  142. package/setup/setup-local.mjs +377 -0
  143. package/setup/setup-telegram.mjs +265 -0
  144. package/setup/setup.mjs +87 -0
  145. package/templates/.dockerignore +5 -0
  146. package/templates/.env.example +104 -0
  147. package/templates/.github/workflows/auto-merge.yml +117 -0
  148. package/templates/.github/workflows/notify-job-failed.yml +64 -0
  149. package/templates/.github/workflows/notify-pr-complete.yml +119 -0
  150. package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
  151. package/templates/.github/workflows/run-job.yml +89 -0
  152. package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
  153. package/templates/.gitignore.template +45 -0
  154. package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
  155. package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
  156. package/templates/CLAUDE.md +29 -0
  157. package/templates/CLAUDE.md.template +308 -0
  158. package/templates/app/api/[...gigaclaw]/route.js +1 -0
  159. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  160. package/templates/app/chat/[chatId]/page.js +9 -0
  161. package/templates/app/chats/page.js +7 -0
  162. package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
  163. package/templates/app/components/ascii-logo.jsx +12 -0
  164. package/templates/app/components/login-form.jsx +92 -0
  165. package/templates/app/components/setup-form.jsx +82 -0
  166. package/templates/app/components/theme-provider.jsx +11 -0
  167. package/templates/app/components/theme-toggle.jsx +38 -0
  168. package/templates/app/components/ui/button.jsx +21 -0
  169. package/templates/app/components/ui/card.jsx +23 -0
  170. package/templates/app/components/ui/input.jsx +10 -0
  171. package/templates/app/components/ui/label.jsx +10 -0
  172. package/templates/app/crons/page.js +5 -0
  173. package/templates/app/globals.css +90 -0
  174. package/templates/app/layout.js +33 -0
  175. package/templates/app/login/page.js +15 -0
  176. package/templates/app/notifications/page.js +7 -0
  177. package/templates/app/page.js +7 -0
  178. package/templates/app/pull-requests/page.js +7 -0
  179. package/templates/app/settings/crons/page.js +5 -0
  180. package/templates/app/settings/layout.js +7 -0
  181. package/templates/app/settings/page.js +5 -0
  182. package/templates/app/settings/secrets/page.js +5 -0
  183. package/templates/app/settings/triggers/page.js +5 -0
  184. package/templates/app/stream/chat/route.js +1 -0
  185. package/templates/app/swarm/page.js +7 -0
  186. package/templates/app/triggers/page.js +5 -0
  187. package/templates/config/CODE_PLANNING.md +14 -0
  188. package/templates/config/CRONS.json +56 -0
  189. package/templates/config/HEARTBEAT.md +3 -0
  190. package/templates/config/JOB_AGENT.md +30 -0
  191. package/templates/config/JOB_PLANNING.md +240 -0
  192. package/templates/config/JOB_SUMMARY.md +130 -0
  193. package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
  194. package/templates/config/SOUL.md +48 -0
  195. package/templates/config/TRIGGERS.json +58 -0
  196. package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
  197. package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
  198. package/templates/docker/claude-code-job/Dockerfile +34 -0
  199. package/templates/docker/claude-code-job/entrypoint.sh +149 -0
  200. package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
  201. package/templates/docker/claude-code-workspace/Dockerfile +61 -0
  202. package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
  203. package/templates/docker/event-handler/Dockerfile +20 -0
  204. package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
  205. package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
  206. package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
  207. package/templates/docker-compose.local.yml +78 -0
  208. package/templates/docker-compose.yml +64 -0
  209. package/templates/instrumentation.js +6 -0
  210. package/templates/middleware.js +23 -0
  211. package/templates/next.config.mjs +3 -0
  212. package/templates/postcss.config.mjs +5 -0
  213. package/templates/public/favicon.ico +0 -0
  214. package/templates/server.js +25 -0
  215. package/templates/skills/LICENSE +21 -0
  216. package/templates/skills/README.md +119 -0
  217. package/templates/skills/brave-search/SKILL.md +79 -0
  218. package/templates/skills/brave-search/content.js +86 -0
  219. package/templates/skills/brave-search/package-lock.json +621 -0
  220. package/templates/skills/brave-search/package.json +14 -0
  221. package/templates/skills/brave-search/search.js +199 -0
  222. package/templates/skills/browser-tools/SKILL.md +196 -0
  223. package/templates/skills/browser-tools/browser-content.js +103 -0
  224. package/templates/skills/browser-tools/browser-cookies.js +35 -0
  225. package/templates/skills/browser-tools/browser-eval.js +53 -0
  226. package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
  227. package/templates/skills/browser-tools/browser-nav.js +44 -0
  228. package/templates/skills/browser-tools/browser-pick.js +162 -0
  229. package/templates/skills/browser-tools/browser-screenshot.js +34 -0
  230. package/templates/skills/browser-tools/browser-start.js +87 -0
  231. package/templates/skills/browser-tools/package-lock.json +2556 -0
  232. package/templates/skills/browser-tools/package.json +19 -0
  233. package/templates/skills/google-docs/SKILL.md +23 -0
  234. package/templates/skills/google-docs/create.sh +69 -0
  235. package/templates/skills/google-drive/SKILL.md +47 -0
  236. package/templates/skills/google-drive/delete.sh +47 -0
  237. package/templates/skills/google-drive/download.sh +50 -0
  238. package/templates/skills/google-drive/list.sh +41 -0
  239. package/templates/skills/google-drive/upload.sh +76 -0
  240. package/templates/skills/kie-ai/SKILL.md +38 -0
  241. package/templates/skills/kie-ai/generate-image.sh +77 -0
  242. package/templates/skills/kie-ai/generate-video.sh +69 -0
  243. package/templates/skills/llm-secrets/SKILL.md +34 -0
  244. package/templates/skills/llm-secrets/llm-secrets.js +33 -0
  245. package/templates/skills/modify-self/SKILL.md +12 -0
  246. package/templates/skills/youtube-transcript/SKILL.md +41 -0
  247. package/templates/skills/youtube-transcript/package-lock.json +24 -0
  248. package/templates/skills/youtube-transcript/package.json +8 -0
  249. package/templates/skills/youtube-transcript/transcript.js +84 -0
@@ -0,0 +1,172 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import { ClockIcon, SpinnerIcon, ChevronDownIcon } from "./icons.js";
5
+ import { getSwarmConfig } from "../actions.js";
6
+ function describeCron(schedule) {
7
+ const parts = schedule.trim().split(/\s+/);
8
+ if (parts.length !== 5) return schedule;
9
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
10
+ if (minute.startsWith("*/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
11
+ const n = parseInt(minute.slice(2), 10);
12
+ if (n === 1) return "Every minute";
13
+ return `Every ${n} minutes`;
14
+ }
15
+ if (hour.startsWith("*/") && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
16
+ const n = parseInt(hour.slice(2), 10);
17
+ if (n === 1) return "Every hour";
18
+ return `Every ${n} hours`;
19
+ }
20
+ if (minute !== "*" && hour !== "*" && !hour.includes("/") && !minute.includes("/") && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
21
+ const h = parseInt(hour, 10);
22
+ const m = parseInt(minute, 10);
23
+ const period = h >= 12 ? "PM" : "AM";
24
+ const displayH = h === 0 ? 12 : h > 12 ? h - 12 : h;
25
+ return `Daily at ${displayH}:${String(m).padStart(2, "0")} ${period}`;
26
+ }
27
+ if (minute !== "*" && hour !== "*" && dayOfMonth === "*" && month === "*" && dayOfWeek !== "*") {
28
+ const dayNames = { "0": "Sun", "1": "Mon", "2": "Tue", "3": "Wed", "4": "Thu", "5": "Fri", "6": "Sat" };
29
+ const days = dayOfWeek.split(",").map((d) => dayNames[d] || d).join(", ");
30
+ const h = parseInt(hour, 10);
31
+ const m = parseInt(minute, 10);
32
+ const period = h >= 12 ? "PM" : "AM";
33
+ const displayH = h === 0 ? 12 : h > 12 ? h - 12 : h;
34
+ return `${days} at ${displayH}:${String(m).padStart(2, "0")} ${period}`;
35
+ }
36
+ return schedule;
37
+ }
38
+ const typeBadgeStyles = {
39
+ agent: "bg-purple-500/10 text-purple-500",
40
+ command: "bg-blue-500/10 text-blue-500",
41
+ webhook: "bg-orange-500/10 text-orange-500"
42
+ };
43
+ const typeOrder = { agent: 0, command: 1, webhook: 2 };
44
+ function sortByType(items) {
45
+ return [...items].sort((a, b) => {
46
+ const ta = typeOrder[a.type || "agent"] ?? 99;
47
+ const tb = typeOrder[b.type || "agent"] ?? 99;
48
+ return ta - tb;
49
+ });
50
+ }
51
+ function GroupHeader({ label, count }) {
52
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2 pb-1", children: [
53
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: label }),
54
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
55
+ "(",
56
+ count,
57
+ ")"
58
+ ] })
59
+ ] });
60
+ }
61
+ function CronCard({ cron }) {
62
+ const [expanded, setExpanded] = useState(false);
63
+ const type = cron.type || "agent";
64
+ const disabled = cron.enabled === false;
65
+ return /* @__PURE__ */ jsxs(
66
+ "div",
67
+ {
68
+ className: `rounded-lg border bg-card transition-opacity ${disabled ? "opacity-60" : ""}`,
69
+ children: [
70
+ /* @__PURE__ */ jsxs(
71
+ "button",
72
+ {
73
+ onClick: () => setExpanded(!expanded),
74
+ className: "flex items-center gap-3 w-full text-left p-4 hover:bg-accent/50 rounded-lg",
75
+ children: [
76
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(ClockIcon, { size: 16 }) }),
77
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
78
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: cron.name }),
79
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
80
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: cron.schedule }),
81
+ /* @__PURE__ */ jsx("span", { className: "mx-1.5 text-border", children: "|" }),
82
+ describeCron(cron.schedule)
83
+ ] })
84
+ ] }),
85
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
86
+ /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${typeBadgeStyles[type] || typeBadgeStyles.agent}`, children: type }),
87
+ /* @__PURE__ */ jsx(
88
+ "span",
89
+ {
90
+ className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${disabled ? "bg-muted text-muted-foreground" : "bg-green-500/10 text-green-500"}`,
91
+ children: disabled ? "disabled" : "enabled"
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
95
+ ] })
96
+ ]
97
+ }
98
+ ),
99
+ expanded && /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-3", children: [
100
+ type === "agent" && cron.job && /* @__PURE__ */ jsxs("div", { children: [
101
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Job prompt" }),
102
+ /* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: cron.job }),
103
+ (cron.llm_provider || cron.llm_model) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
104
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "LLM:" }),
105
+ /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-purple-500/10 text-purple-500 px-2 py-0.5 text-[10px] font-medium", children: [cron.llm_provider, cron.llm_model].filter(Boolean).join(" / ") })
106
+ ] })
107
+ ] }),
108
+ type === "command" && cron.command && /* @__PURE__ */ jsxs("div", { children: [
109
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Command" }),
110
+ /* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: cron.command })
111
+ ] }),
112
+ type === "webhook" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
113
+ /* @__PURE__ */ jsxs("div", { children: [
114
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "URL" }),
115
+ /* @__PURE__ */ jsxs("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto", children: [
116
+ cron.method && cron.method !== "POST" ? `${cron.method} ` : "",
117
+ cron.url
118
+ ] })
119
+ ] }),
120
+ cron.vars && Object.keys(cron.vars).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
121
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Variables" }),
122
+ /* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: JSON.stringify(cron.vars, null, 2) })
123
+ ] })
124
+ ] })
125
+ ] })
126
+ ]
127
+ }
128
+ );
129
+ }
130
+ function CronsPage() {
131
+ const [crons, setCrons] = useState([]);
132
+ const [loading, setLoading] = useState(true);
133
+ useEffect(() => {
134
+ getSwarmConfig().then((data) => {
135
+ if (data?.crons) setCrons(data.crons);
136
+ }).catch(() => {
137
+ }).finally(() => setLoading(false));
138
+ }, []);
139
+ const enabled = sortByType(crons.filter((c) => c.enabled !== false));
140
+ const disabled = sortByType(crons.filter((c) => c.enabled === false));
141
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
142
+ !loading && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-4", children: [
143
+ crons.length,
144
+ " job",
145
+ crons.length !== 1 ? "s" : "",
146
+ " configured, ",
147
+ enabled.length,
148
+ " enabled"
149
+ ] }),
150
+ loading ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-pulse rounded-lg bg-border/50" }, i)) }) : crons.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
151
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(ClockIcon, { size: 24 }) }),
152
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No cron jobs configured" }),
153
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
154
+ "Add scheduled jobs by editing ",
155
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: "config/CRONS.json" }),
156
+ " in your project."
157
+ ] })
158
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
159
+ enabled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
160
+ /* @__PURE__ */ jsx(GroupHeader, { label: "Enabled", count: enabled.length }),
161
+ enabled.map((cron, i) => /* @__PURE__ */ jsx(CronCard, { cron }, `enabled-${i}`))
162
+ ] }),
163
+ disabled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
164
+ /* @__PURE__ */ jsx(GroupHeader, { label: "Disabled", count: disabled.length }),
165
+ disabled.map((cron, i) => /* @__PURE__ */ jsx(CronCard, { cron }, `disabled-${i}`))
166
+ ] })
167
+ ] })
168
+ ] });
169
+ }
170
+ export {
171
+ CronsPage
172
+ };
@@ -0,0 +1,244 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { ClockIcon, SpinnerIcon, ChevronDownIcon } from './icons.js';
5
+ import { getSwarmConfig } from '../actions.js';
6
+
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+ // Utilities
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+
11
+ function describeCron(schedule) {
12
+ const parts = schedule.trim().split(/\s+/);
13
+ if (parts.length !== 5) return schedule;
14
+
15
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
16
+
17
+ // Every N minutes
18
+ if (minute.startsWith('*/') && hour === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
19
+ const n = parseInt(minute.slice(2), 10);
20
+ if (n === 1) return 'Every minute';
21
+ return `Every ${n} minutes`;
22
+ }
23
+
24
+ // Every N hours
25
+ if (hour.startsWith('*/') && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
26
+ const n = parseInt(hour.slice(2), 10);
27
+ if (n === 1) return 'Every hour';
28
+ return `Every ${n} hours`;
29
+ }
30
+
31
+ // Specific time daily
32
+ if (minute !== '*' && hour !== '*' && !hour.includes('/') && !minute.includes('/') && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
33
+ const h = parseInt(hour, 10);
34
+ const m = parseInt(minute, 10);
35
+ const period = h >= 12 ? 'PM' : 'AM';
36
+ const displayH = h === 0 ? 12 : h > 12 ? h - 12 : h;
37
+ return `Daily at ${displayH}:${String(m).padStart(2, '0')} ${period}`;
38
+ }
39
+
40
+ // Specific time on specific weekdays
41
+ if (minute !== '*' && hour !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek !== '*') {
42
+ const dayNames = { '0': 'Sun', '1': 'Mon', '2': 'Tue', '3': 'Wed', '4': 'Thu', '5': 'Fri', '6': 'Sat' };
43
+ const days = dayOfWeek.split(',').map(d => dayNames[d] || d).join(', ');
44
+ const h = parseInt(hour, 10);
45
+ const m = parseInt(minute, 10);
46
+ const period = h >= 12 ? 'PM' : 'AM';
47
+ const displayH = h === 0 ? 12 : h > 12 ? h - 12 : h;
48
+ return `${days} at ${displayH}:${String(m).padStart(2, '0')} ${period}`;
49
+ }
50
+
51
+ return schedule;
52
+ }
53
+
54
+ const typeBadgeStyles = {
55
+ agent: 'bg-purple-500/10 text-purple-500',
56
+ command: 'bg-blue-500/10 text-blue-500',
57
+ webhook: 'bg-orange-500/10 text-orange-500',
58
+ };
59
+
60
+ const typeOrder = { agent: 0, command: 1, webhook: 2 };
61
+
62
+ function sortByType(items) {
63
+ return [...items].sort((a, b) => {
64
+ const ta = typeOrder[a.type || 'agent'] ?? 99;
65
+ const tb = typeOrder[b.type || 'agent'] ?? 99;
66
+ return ta - tb;
67
+ });
68
+ }
69
+
70
+ // ─────────────────────────────────────────────────────────────────────────────
71
+ // Group Header
72
+ // ─────────────────────────────────────────────────────────────────────────────
73
+
74
+ function GroupHeader({ label, count }) {
75
+ return (
76
+ <div className="flex items-center gap-2 pt-2 pb-1">
77
+ <span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{label}</span>
78
+ <span className="text-xs text-muted-foreground">({count})</span>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ // ─────────────────────────────────────────────────────────────────────────────
84
+ // Cron Card
85
+ // ─────────────────────────────────────────────────────────────────────────────
86
+
87
+ function CronCard({ cron }) {
88
+ const [expanded, setExpanded] = useState(false);
89
+ const type = cron.type || 'agent';
90
+ const disabled = cron.enabled === false;
91
+
92
+ return (
93
+ <div
94
+ className={`rounded-lg border bg-card transition-opacity ${disabled ? 'opacity-60' : ''}`}
95
+ >
96
+ <button
97
+ onClick={() => setExpanded(!expanded)}
98
+ className="flex items-center gap-3 w-full text-left p-4 hover:bg-accent/50 rounded-lg"
99
+ >
100
+ <div className="shrink-0 rounded-md bg-muted p-2">
101
+ <ClockIcon size={16} />
102
+ </div>
103
+ <div className="flex-1 min-w-0">
104
+ <p className="text-sm font-medium truncate">{cron.name}</p>
105
+ <p className="text-xs text-muted-foreground mt-0.5">
106
+ <span className="font-mono">{cron.schedule}</span>
107
+ <span className="mx-1.5 text-border">|</span>
108
+ {describeCron(cron.schedule)}
109
+ </p>
110
+ </div>
111
+ <div className="flex items-center gap-2 shrink-0">
112
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${typeBadgeStyles[type] || typeBadgeStyles.agent}`}>
113
+ {type}
114
+ </span>
115
+ <span
116
+ className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${
117
+ disabled ? 'bg-muted text-muted-foreground' : 'bg-green-500/10 text-green-500'
118
+ }`}
119
+ >
120
+ {disabled ? 'disabled' : 'enabled'}
121
+ </span>
122
+ <span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}>
123
+ <ChevronDownIcon size={14} />
124
+ </span>
125
+ </div>
126
+ </button>
127
+
128
+ {expanded && (
129
+ <div className="border-t px-4 py-3">
130
+ {type === 'agent' && cron.job && (
131
+ <div>
132
+ <p className="text-xs font-medium text-muted-foreground mb-1.5">Job prompt</p>
133
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
134
+ {cron.job}
135
+ </pre>
136
+ {(cron.llm_provider || cron.llm_model) && (
137
+ <div className="flex items-center gap-2 mt-2">
138
+ <span className="text-xs font-medium text-muted-foreground">LLM:</span>
139
+ <span className="inline-flex items-center rounded-full bg-purple-500/10 text-purple-500 px-2 py-0.5 text-[10px] font-medium">
140
+ {[cron.llm_provider, cron.llm_model].filter(Boolean).join(' / ')}
141
+ </span>
142
+ </div>
143
+ )}
144
+ </div>
145
+ )}
146
+ {type === 'command' && cron.command && (
147
+ <div>
148
+ <p className="text-xs font-medium text-muted-foreground mb-1.5">Command</p>
149
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
150
+ {cron.command}
151
+ </pre>
152
+ </div>
153
+ )}
154
+ {type === 'webhook' && (
155
+ <div className="flex flex-col gap-2">
156
+ <div>
157
+ <p className="text-xs font-medium text-muted-foreground mb-1.5">URL</p>
158
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto">
159
+ {cron.method && cron.method !== 'POST' ? `${cron.method} ` : ''}{cron.url}
160
+ </pre>
161
+ </div>
162
+ {cron.vars && Object.keys(cron.vars).length > 0 && (
163
+ <div>
164
+ <p className="text-xs font-medium text-muted-foreground mb-1.5">Variables</p>
165
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
166
+ {JSON.stringify(cron.vars, null, 2)}
167
+ </pre>
168
+ </div>
169
+ )}
170
+ </div>
171
+ )}
172
+ </div>
173
+ )}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ // ─────────────────────────────────────────────────────────────────────────────
179
+ // Main Page
180
+ // ─────────────────────────────────────────────────────────────────────────────
181
+
182
+ export function CronsPage() {
183
+ const [crons, setCrons] = useState([]);
184
+ const [loading, setLoading] = useState(true);
185
+
186
+ useEffect(() => {
187
+ getSwarmConfig()
188
+ .then((data) => {
189
+ if (data?.crons) setCrons(data.crons);
190
+ })
191
+ .catch(() => {})
192
+ .finally(() => setLoading(false));
193
+ }, []);
194
+
195
+ const enabled = sortByType(crons.filter((c) => c.enabled !== false));
196
+ const disabled = sortByType(crons.filter((c) => c.enabled === false));
197
+
198
+ return (
199
+ <>
200
+ {!loading && (
201
+ <p className="text-sm text-muted-foreground mb-4">
202
+ {crons.length} job{crons.length !== 1 ? 's' : ''} configured, {enabled.length} enabled
203
+ </p>
204
+ )}
205
+
206
+ {loading ? (
207
+ <div className="flex flex-col gap-3">
208
+ {[...Array(3)].map((_, i) => (
209
+ <div key={i} className="h-20 animate-pulse rounded-lg bg-border/50" />
210
+ ))}
211
+ </div>
212
+ ) : crons.length === 0 ? (
213
+ <div className="flex flex-col items-center justify-center py-16 text-center">
214
+ <div className="rounded-full bg-muted p-4 mb-4">
215
+ <ClockIcon size={24} />
216
+ </div>
217
+ <p className="text-sm font-medium mb-1">No cron jobs configured</p>
218
+ <p className="text-xs text-muted-foreground max-w-sm">
219
+ Add scheduled jobs by editing <span className="font-mono">config/CRONS.json</span> in your project.
220
+ </p>
221
+ </div>
222
+ ) : (
223
+ <div className="flex flex-col gap-3">
224
+ {enabled.length > 0 && (
225
+ <>
226
+ <GroupHeader label="Enabled" count={enabled.length} />
227
+ {enabled.map((cron, i) => (
228
+ <CronCard key={`enabled-${i}`} cron={cron} />
229
+ ))}
230
+ </>
231
+ )}
232
+ {disabled.length > 0 && (
233
+ <>
234
+ <GroupHeader label="Disabled" count={disabled.length} />
235
+ {disabled.map((cron, i) => (
236
+ <CronCard key={`disabled-${i}`} cron={cron} />
237
+ ))}
238
+ </>
239
+ )}
240
+ </div>
241
+ )}
242
+ </>
243
+ );
244
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ function Greeting({ codeMode = false }) {
4
+ return /* @__PURE__ */ jsxs("div", { className: "w-full text-center", children: [
5
+ /* @__PURE__ */ jsx("div", { className: "font-semibold text-2xl md:text-3xl text-foreground", children: codeMode ? "How can I help you code today?" : "Hello! How can I help?" }),
6
+ codeMode && /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "Discuss your project and let me know when you're ready to start coding." })
7
+ ] });
8
+ }
9
+ export {
10
+ Greeting
11
+ };
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+
3
+ export function Greeting({ codeMode = false }) {
4
+ return (
5
+ <div className="w-full text-center">
6
+ <div className="font-semibold text-2xl md:text-3xl text-foreground">
7
+ {codeMode ? 'How can I help you code today?' : 'Hello! How can I help?'}
8
+ </div>
9
+ {codeMode && (
10
+ <div className="mt-2 text-sm text-muted-foreground">
11
+ Discuss your project and let me know when you're ready to start coding.
12
+ </div>
13
+ )}
14
+ </div>
15
+ );
16
+ }