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,220 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { SidebarHistoryItem } from './sidebar-history-item.js';
5
+ import { SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu } from './ui/sidebar.js';
6
+ import { useChatNav } from './chat-nav-context.js';
7
+ import { getChats, deleteChat, renameChat, starChat } from '../actions.js';
8
+ import { cn } from '../utils.js';
9
+ import { MessageIcon, CodeIcon } from './icons.js';
10
+
11
+ function groupChatsByDate(chats) {
12
+ const now = new Date();
13
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
14
+ const yesterday = new Date(today.getTime() - 86400000);
15
+ const last7Days = new Date(today.getTime() - 7 * 86400000);
16
+ const last30Days = new Date(today.getTime() - 30 * 86400000);
17
+
18
+ const groups = {
19
+ Starred: [],
20
+ Today: [],
21
+ Yesterday: [],
22
+ 'Last 7 Days': [],
23
+ 'Last 30 Days': [],
24
+ Older: [],
25
+ };
26
+
27
+ for (const chat of chats) {
28
+ if (chat.starred) {
29
+ groups.Starred.push(chat);
30
+ continue;
31
+ }
32
+ const date = new Date(chat.updatedAt);
33
+ if (date >= today) {
34
+ groups.Today.push(chat);
35
+ } else if (date >= yesterday) {
36
+ groups.Yesterday.push(chat);
37
+ } else if (date >= last7Days) {
38
+ groups['Last 7 Days'].push(chat);
39
+ } else if (date >= last30Days) {
40
+ groups['Last 30 Days'].push(chat);
41
+ } else {
42
+ groups.Older.push(chat);
43
+ }
44
+ }
45
+
46
+ return groups;
47
+ }
48
+
49
+ const FILTERS = [
50
+ { value: 'all', label: 'All', icon: null },
51
+ { value: 'chat', label: 'Chat', icon: MessageIcon },
52
+ { value: 'code', label: 'Code', icon: CodeIcon },
53
+ ];
54
+
55
+ function ChatTypeFilter({ filter, setFilter }) {
56
+ return (
57
+ <div className="flex items-center gap-0.5 px-2 mb-1">
58
+ {FILTERS.map(({ value, label, icon: Icon }) => (
59
+ <button
60
+ key={value}
61
+ onClick={() => setFilter(value)}
62
+ className={cn(
63
+ 'flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium transition-colors',
64
+ filter === value
65
+ ? 'bg-background text-foreground shadow-sm'
66
+ : 'text-muted-foreground hover:text-foreground'
67
+ )}
68
+ >
69
+ {Icon && <Icon size={12} />}
70
+ {label}
71
+ </button>
72
+ ))}
73
+ </div>
74
+ );
75
+ }
76
+
77
+ const isCodeChat = (chat) => Boolean(chat.codeWorkspaceId && chat.containerName);
78
+
79
+ export function SidebarHistory() {
80
+ const [chats, setChats] = useState([]);
81
+ const [loading, setLoading] = useState(true);
82
+ // Always initialise with 'all' so SSR and client render the same HTML (prevents hydration mismatch).
83
+ // After mount, read the persisted value from localStorage and update state.
84
+ const [filter, setFilter] = useState('all');
85
+ const updateFilter = (v) => { setFilter(v); try { localStorage.setItem('sidebar-chat-filter', v); } catch {} };
86
+ useEffect(() => {
87
+ try {
88
+ const v = localStorage.getItem('sidebar-chat-filter');
89
+ if (v === 'chat' || v === 'code') setFilter(v);
90
+ } catch {}
91
+ }, []);
92
+ const { activeChatId, navigateToChat } = useChatNav();
93
+
94
+ const loadChats = async () => {
95
+ try {
96
+ const result = await getChats();
97
+ setChats(result);
98
+ } catch (err) {
99
+ console.error('Failed to load chats:', err);
100
+ } finally {
101
+ setLoading(false);
102
+ }
103
+ };
104
+
105
+ // Load chats on mount and refresh when navigating between pages
106
+ useEffect(() => {
107
+ loadChats();
108
+ }, [activeChatId]);
109
+
110
+ // Reload when chats change (new chat created or title updated)
111
+ useEffect(() => {
112
+ const handler = () => loadChats();
113
+ window.addEventListener('chatsupdated', handler);
114
+ return () => window.removeEventListener('chatsupdated', handler);
115
+ }, []);
116
+
117
+ const handleDelete = async (chatId) => {
118
+ setChats((prev) => prev.filter((c) => c.id !== chatId));
119
+ const { success } = await deleteChat(chatId);
120
+ if (success) {
121
+ if (chatId === activeChatId) {
122
+ navigateToChat(null);
123
+ }
124
+ } else {
125
+ loadChats();
126
+ }
127
+ };
128
+
129
+ const handleStar = async (chatId) => {
130
+ setChats((prev) =>
131
+ prev.map((c) => (c.id === chatId ? { ...c, starred: c.starred ? 0 : 1 } : c))
132
+ );
133
+ const { success } = await starChat(chatId);
134
+ if (!success) loadChats();
135
+ };
136
+
137
+ const handleRename = async (chatId, title) => {
138
+ setChats((prev) =>
139
+ prev.map((c) => (c.id === chatId ? { ...c, title } : c))
140
+ );
141
+ const { success } = await renameChat(chatId, title);
142
+ if (!success) loadChats();
143
+ };
144
+
145
+ if (loading && chats.length === 0) {
146
+ return (
147
+ <SidebarGroup>
148
+ <SidebarGroupContent>
149
+ <ChatTypeFilter filter={filter} setFilter={updateFilter} />
150
+ <div className="flex flex-col gap-2 px-2">
151
+ {[...Array(5)].map((_, i) => (
152
+ <div key={i} className="h-8 animate-pulse rounded-md bg-border/50" />
153
+ ))}
154
+ </div>
155
+ </SidebarGroupContent>
156
+ </SidebarGroup>
157
+ );
158
+ }
159
+
160
+ if (chats.length === 0) {
161
+ return (
162
+ <SidebarGroup>
163
+ <SidebarGroupContent>
164
+ <p className="px-4 py-2 text-sm text-muted-foreground">
165
+ No chats yet. Start a conversation!
166
+ </p>
167
+ </SidebarGroupContent>
168
+ </SidebarGroup>
169
+ );
170
+ }
171
+
172
+ const filteredChats = filter === 'all' ? chats
173
+ : filter === 'code' ? chats.filter(isCodeChat)
174
+ : chats.filter((c) => !isCodeChat(c));
175
+ const grouped = groupChatsByDate(filteredChats);
176
+
177
+ const hasResults = Object.values(grouped).some((g) => g.length > 0);
178
+
179
+ return (
180
+ <>
181
+ <SidebarGroup>
182
+ <SidebarGroupContent>
183
+ <ChatTypeFilter filter={filter} setFilter={updateFilter} />
184
+ </SidebarGroupContent>
185
+ </SidebarGroup>
186
+ {hasResults ? (
187
+ Object.entries(grouped).map(
188
+ ([label, groupChats]) =>
189
+ groupChats.length > 0 && (
190
+ <SidebarGroup key={label}>
191
+ <SidebarGroupLabel>{label}</SidebarGroupLabel>
192
+ <SidebarGroupContent>
193
+ <SidebarMenu>
194
+ {groupChats.map((chat) => (
195
+ <SidebarHistoryItem
196
+ key={chat.id}
197
+ chat={chat}
198
+ isActive={chat.id === activeChatId}
199
+ onDelete={handleDelete}
200
+ onStar={handleStar}
201
+ onRename={handleRename}
202
+ />
203
+ ))}
204
+ </SidebarMenu>
205
+ </SidebarGroupContent>
206
+ </SidebarGroup>
207
+ )
208
+ )
209
+ ) : (
210
+ <SidebarGroup>
211
+ <SidebarGroupContent>
212
+ <p className="px-4 py-2 text-sm text-muted-foreground">
213
+ No {filter === 'code' ? 'code' : 'chat'} chats yet.
214
+ </p>
215
+ </SidebarGroupContent>
216
+ </SidebarGroup>
217
+ )}
218
+ </>
219
+ );
220
+ }
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { signOut } from "next-auth/react";
4
+ import { useTheme } from "next-themes";
5
+ import { useEffect, useState } from "react";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger
12
+ } from "./ui/dropdown-menu.js";
13
+ import { SidebarMenu, SidebarMenuItem, SidebarMenuButton } from "./ui/sidebar.js";
14
+ import { SettingsIcon, SunIcon, MoonIcon, BugIcon, LogOutIcon } from "./icons.js";
15
+ import { cn } from "../utils.js";
16
+ function SidebarUserNav({ user, collapsed }) {
17
+ const { theme, setTheme } = useTheme();
18
+ const [mounted, setMounted] = useState(false);
19
+ useEffect(() => setMounted(true), []);
20
+ return /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
21
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(SidebarMenuButton, { className: cn("bg-background text-foreground hover:bg-muted", collapsed ? "justify-center" : "justify-between"), children: [
22
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center overflow-hidden", collapsed ? "" : "gap-2"), children: [
23
+ /* @__PURE__ */ jsx("div", { className: "flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-medium", children: (user?.email?.[0] || "U").toUpperCase() }),
24
+ !collapsed && /* @__PURE__ */ jsx("span", { className: "truncate text-sm", children: user?.email || "User" })
25
+ ] }),
26
+ !collapsed && /* @__PURE__ */ jsxs("svg", { className: "size-4 text-muted-foreground", width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
27
+ /* @__PURE__ */ jsx("path", { d: "m7 15 5 5 5-5" }),
28
+ /* @__PURE__ */ jsx("path", { d: "m7 9 5-5 5 5" })
29
+ ] })
30
+ ] }) }),
31
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", side: "top", className: "w-56", children: [
32
+ /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsxs("a", { href: "/settings", className: "flex items-center gap-2", style: { textDecoration: "inherit", color: "inherit" }, children: [
33
+ /* @__PURE__ */ jsx(SettingsIcon, { size: 14 }),
34
+ /* @__PURE__ */ jsx("span", { children: "Settings" })
35
+ ] }) }),
36
+ mounted && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => setTheme(theme === "dark" ? "light" : "dark"), children: [
37
+ theme === "dark" ? /* @__PURE__ */ jsx(SunIcon, { size: 14 }) : /* @__PURE__ */ jsx(MoonIcon, { size: 14 }),
38
+ /* @__PURE__ */ jsx("span", { children: theme === "dark" ? "Light Mode" : "Dark Mode" })
39
+ ] }),
40
+ /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsxs("a", { href: "https://github.com/gignaati/gigaclaw/issues", target: "_blank", rel: "noopener noreferrer", className: "flex items-center gap-2", style: { textDecoration: "inherit", color: "inherit" }, children: [
41
+ /* @__PURE__ */ jsx(BugIcon, { size: 14 }),
42
+ /* @__PURE__ */ jsx("span", { children: "Report Issues" })
43
+ ] }) }),
44
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
45
+ /* @__PURE__ */ jsxs(
46
+ DropdownMenuItem,
47
+ {
48
+ onClick: () => signOut({ callbackUrl: "/" }),
49
+ className: "text-destructive",
50
+ children: [
51
+ /* @__PURE__ */ jsx(LogOutIcon, { size: 14 }),
52
+ /* @__PURE__ */ jsx("span", { children: "Sign Out" })
53
+ ]
54
+ }
55
+ )
56
+ ] })
57
+ ] }) }) });
58
+ }
59
+ export {
60
+ SidebarUserNav
61
+ };
@@ -0,0 +1,77 @@
1
+ 'use client';
2
+
3
+ import { signOut } from 'next-auth/react';
4
+ import { useTheme } from 'next-themes';
5
+ import { useEffect, useState } from 'react';
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from './ui/dropdown-menu.js';
13
+ import { SidebarMenu, SidebarMenuItem, SidebarMenuButton } from './ui/sidebar.js';
14
+ import { SettingsIcon, SunIcon, MoonIcon, BugIcon, LogOutIcon } from './icons.js';
15
+ import { cn } from '../utils.js';
16
+
17
+ export function SidebarUserNav({ user, collapsed }) {
18
+ const { theme, setTheme } = useTheme();
19
+ const [mounted, setMounted] = useState(false);
20
+
21
+ useEffect(() => setMounted(true), []);
22
+
23
+ return (
24
+ <SidebarMenu>
25
+ <SidebarMenuItem>
26
+ <DropdownMenu>
27
+ <DropdownMenuTrigger asChild>
28
+ <SidebarMenuButton className={cn('bg-background text-foreground hover:bg-muted', collapsed ? 'justify-center' : 'justify-between')}>
29
+ <div className={cn('flex items-center overflow-hidden', collapsed ? '' : 'gap-2')}>
30
+ <div className="flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-medium">
31
+ {(user?.email?.[0] || 'U').toUpperCase()}
32
+ </div>
33
+ {!collapsed && (
34
+ <span className="truncate text-sm">{user?.email || 'User'}</span>
35
+ )}
36
+ </div>
37
+ {!collapsed && (
38
+ <svg className="size-4 text-muted-foreground" width={16} height={16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
39
+ <path d="m7 15 5 5 5-5" />
40
+ <path d="m7 9 5-5 5 5" />
41
+ </svg>
42
+ )}
43
+ </SidebarMenuButton>
44
+ </DropdownMenuTrigger>
45
+ <DropdownMenuContent align="start" side="top" className="w-56">
46
+ <DropdownMenuItem asChild>
47
+ <a href="/settings" className="flex items-center gap-2" style={{ textDecoration: 'inherit', color: 'inherit' }}>
48
+ <SettingsIcon size={14} />
49
+ <span>Settings</span>
50
+ </a>
51
+ </DropdownMenuItem>
52
+ {mounted && (
53
+ <DropdownMenuItem onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
54
+ {theme === 'dark' ? <SunIcon size={14} /> : <MoonIcon size={14} />}
55
+ <span>{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>
56
+ </DropdownMenuItem>
57
+ )}
58
+ <DropdownMenuItem asChild>
59
+ <a href="https://github.com/gignaati/gigaclaw/issues" target="_blank" rel="noopener noreferrer" className="flex items-center gap-2" style={{ textDecoration: 'inherit', color: 'inherit' }}>
60
+ <BugIcon size={14} />
61
+ <span>Report Issues</span>
62
+ </a>
63
+ </DropdownMenuItem>
64
+ <DropdownMenuSeparator />
65
+ <DropdownMenuItem
66
+ onClick={() => signOut({ callbackUrl: '/' })}
67
+ className="text-destructive"
68
+ >
69
+ <LogOutIcon size={14} />
70
+ <span>Sign Out</span>
71
+ </DropdownMenuItem>
72
+ </DropdownMenuContent>
73
+ </DropdownMenu>
74
+ </SidebarMenuItem>
75
+ </SidebarMenu>
76
+ );
77
+ }
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { PageLayout } from "./page-layout.js";
5
+ import { SpinnerIcon, RefreshIcon } from "./icons.js";
6
+ import { getSwarmStatus } from "../actions.js";
7
+ function formatDuration(seconds) {
8
+ if (seconds < 60) return `${seconds}s`;
9
+ const minutes = Math.floor(seconds / 60);
10
+ const secs = seconds % 60;
11
+ if (minutes < 60) return `${minutes}m ${secs}s`;
12
+ const hours = Math.floor(minutes / 60);
13
+ const mins = minutes % 60;
14
+ return `${hours}h ${mins}m`;
15
+ }
16
+ function timeAgo(timestamp) {
17
+ const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1e3);
18
+ if (seconds < 60) return "just now";
19
+ const minutes = Math.floor(seconds / 60);
20
+ if (minutes < 60) return `${minutes}m ago`;
21
+ const hours = Math.floor(minutes / 60);
22
+ if (hours < 24) return `${hours}h ago`;
23
+ const days = Math.floor(hours / 24);
24
+ if (days < 30) return `${days}d ago`;
25
+ const months = Math.floor(days / 30);
26
+ return `${months}mo ago`;
27
+ }
28
+ function LoadingSkeleton() {
29
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-md bg-border/50" }, i)) });
30
+ }
31
+ const conclusionBadgeStyles = {
32
+ success: "bg-green-500/10 text-green-500",
33
+ failure: "bg-red-500/10 text-red-500",
34
+ cancelled: "bg-yellow-500/10 text-yellow-500",
35
+ skipped: "bg-muted text-muted-foreground"
36
+ };
37
+ function SwarmWorkflowList({ runs }) {
38
+ if (!runs || runs.length === 0) {
39
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground py-4 text-center", children: "No workflow runs." });
40
+ }
41
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col divide-y divide-border", children: runs.map((run) => {
42
+ const isActive = run.status === "in_progress" || run.status === "queued";
43
+ const isRunning = run.status === "in_progress";
44
+ const isQueued = run.status === "queued";
45
+ return /* @__PURE__ */ jsxs(
46
+ "a",
47
+ {
48
+ href: run.html_url,
49
+ target: "_blank",
50
+ rel: "noopener noreferrer",
51
+ className: "flex items-center gap-3 py-3 px-2 -mx-2 rounded-md hover:bg-accent transition-colors no-underline text-inherit",
52
+ children: [
53
+ isRunning && /* @__PURE__ */ jsx("span", { className: "inline-block h-2.5 w-2.5 shrink-0 rounded-full bg-green-500 animate-pulse" }),
54
+ isQueued && /* @__PURE__ */ jsx("span", { className: "inline-block h-2.5 w-2.5 shrink-0 rounded-full bg-yellow-500" }),
55
+ !isActive && /* @__PURE__ */ jsx(
56
+ "span",
57
+ {
58
+ className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium uppercase shrink-0 ${conclusionBadgeStyles[run.conclusion] || "bg-muted text-muted-foreground"}`,
59
+ children: run.conclusion || "unknown"
60
+ }
61
+ ),
62
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: run.workflow_name || run.branch }),
63
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground shrink-0", children: isActive ? formatDuration(run.duration_seconds) : timeAgo(run.updated_at || run.started_at) }),
64
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
65
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-blue-500 shrink-0", children: "View" })
66
+ ]
67
+ },
68
+ run.run_id
69
+ );
70
+ }) });
71
+ }
72
+ function SwarmPage({ session }) {
73
+ const [runs, setRuns] = useState([]);
74
+ const [hasMore, setHasMore] = useState(false);
75
+ const [page, setPage] = useState(1);
76
+ const [loading, setLoading] = useState(true);
77
+ const [refreshing, setRefreshing] = useState(false);
78
+ const fetchPage = useCallback(async (p) => {
79
+ try {
80
+ const data = await getSwarmStatus(p);
81
+ setRuns(data.runs || []);
82
+ setHasMore(data.hasMore || false);
83
+ setPage(p);
84
+ } catch (err) {
85
+ console.error("Failed to fetch swarm status:", err);
86
+ } finally {
87
+ setLoading(false);
88
+ setRefreshing(false);
89
+ }
90
+ }, []);
91
+ useEffect(() => {
92
+ fetchPage(1);
93
+ }, [fetchPage]);
94
+ useEffect(() => {
95
+ const interval = setInterval(() => fetchPage(page), 1e4);
96
+ return () => clearInterval(interval);
97
+ }, [fetchPage, page]);
98
+ return /* @__PURE__ */ jsxs(PageLayout, { session, children: [
99
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
100
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Swarm" }),
101
+ !loading && /* @__PURE__ */ jsx(
102
+ "button",
103
+ {
104
+ onClick: () => {
105
+ setRefreshing(true);
106
+ fetchPage(1);
107
+ },
108
+ disabled: refreshing,
109
+ className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:pointer-events-none",
110
+ children: refreshing ? /* @__PURE__ */ jsxs(Fragment, { children: [
111
+ /* @__PURE__ */ jsx(SpinnerIcon, { size: 14 }),
112
+ "Refreshing..."
113
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ /* @__PURE__ */ jsx(RefreshIcon, { size: 14 }),
115
+ "Refresh"
116
+ ] })
117
+ }
118
+ )
119
+ ] }),
120
+ loading ? /* @__PURE__ */ jsx(LoadingSkeleton, {}) : /* @__PURE__ */ jsxs("div", { children: [
121
+ /* @__PURE__ */ jsx(SwarmWorkflowList, { runs }),
122
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-4 pt-4 border-t border-border", children: [
123
+ /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ onClick: () => {
127
+ setRefreshing(true);
128
+ fetchPage(page - 1);
129
+ },
130
+ disabled: page <= 1 || refreshing,
131
+ className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:pointer-events-none",
132
+ children: "Previous"
133
+ }
134
+ ),
135
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
136
+ "Page ",
137
+ page
138
+ ] }),
139
+ /* @__PURE__ */ jsx(
140
+ "button",
141
+ {
142
+ onClick: () => {
143
+ setRefreshing(true);
144
+ fetchPage(page + 1);
145
+ },
146
+ disabled: !hasMore || refreshing,
147
+ className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:pointer-events-none",
148
+ children: "Next"
149
+ }
150
+ )
151
+ ] })
152
+ ] })
153
+ ] });
154
+ }
155
+ export {
156
+ SwarmPage
157
+ };