@harbinger-ai/harbinger 0.1.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 (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +406 -0
  3. package/agents/README.md +76 -0
  4. package/agents/_template/CONFIG.yaml +7 -0
  5. package/agents/_template/HEARTBEAT.md +59 -0
  6. package/agents/_template/IDENTITY.md +4 -0
  7. package/agents/_template/SKILLS.md +1 -0
  8. package/agents/_template/SOUL.md +25 -0
  9. package/agents/_template/TOOLS.md +3 -0
  10. package/agents/binary-reverser/CONFIG.yaml +21 -0
  11. package/agents/binary-reverser/HEARTBEAT.md +65 -0
  12. package/agents/binary-reverser/IDENTITY.md +1 -0
  13. package/agents/binary-reverser/SKILLS.md +1 -0
  14. package/agents/binary-reverser/SOUL.md +23 -0
  15. package/agents/binary-reverser/TOOLS.md +99 -0
  16. package/agents/browser-agent/CONFIG.yaml +20 -0
  17. package/agents/browser-agent/HEARTBEAT.md +79 -0
  18. package/agents/browser-agent/IDENTITY.md +5 -0
  19. package/agents/browser-agent/SKILLS.md +86 -0
  20. package/agents/browser-agent/SOUL.md +23 -0
  21. package/agents/browser-agent/TOOLS.md +186 -0
  22. package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
  23. package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
  24. package/agents/cloud-infiltrator/IDENTITY.md +1 -0
  25. package/agents/cloud-infiltrator/SKILLS.md +1 -0
  26. package/agents/cloud-infiltrator/SOUL.md +23 -0
  27. package/agents/cloud-infiltrator/TOOLS.md +68 -0
  28. package/agents/coding-assistant/CONFIG.yaml +22 -0
  29. package/agents/coding-assistant/HEARTBEAT.md +57 -0
  30. package/agents/coding-assistant/IDENTITY.md +5 -0
  31. package/agents/coding-assistant/SKILLS.md +69 -0
  32. package/agents/coding-assistant/SOUL.md +60 -0
  33. package/agents/coding-assistant/TOOLS.md +168 -0
  34. package/agents/learning-agent/CONFIG.yaml +21 -0
  35. package/agents/learning-agent/HEARTBEAT.md +63 -0
  36. package/agents/learning-agent/IDENTITY.md +5 -0
  37. package/agents/learning-agent/SKILLS.md +86 -0
  38. package/agents/learning-agent/SOUL.md +77 -0
  39. package/agents/learning-agent/TOOLS.md +145 -0
  40. package/agents/maintainer/CONFIG.yaml +31 -0
  41. package/agents/maintainer/HEARTBEAT.md +28 -0
  42. package/agents/maintainer/IDENTITY.md +33 -0
  43. package/agents/maintainer/SKILLS.md +24 -0
  44. package/agents/maintainer/SOUL.md +61 -0
  45. package/agents/maintainer/TOOLS.md +29 -0
  46. package/agents/maintainer/lib/engine.js +279 -0
  47. package/agents/maintainer/lib/safe-fixer.js +183 -0
  48. package/agents/morning-brief/CONFIG.yaml +22 -0
  49. package/agents/morning-brief/HEARTBEAT.md +60 -0
  50. package/agents/morning-brief/IDENTITY.md +5 -0
  51. package/agents/morning-brief/SKILLS.md +56 -0
  52. package/agents/morning-brief/SOUL.md +64 -0
  53. package/agents/morning-brief/TOOLS.md +112 -0
  54. package/agents/osint-detective/CONFIG.yaml +24 -0
  55. package/agents/osint-detective/HEARTBEAT.md +66 -0
  56. package/agents/osint-detective/IDENTITY.md +1 -0
  57. package/agents/osint-detective/SKILLS.md +1 -0
  58. package/agents/osint-detective/SOUL.md +23 -0
  59. package/agents/osint-detective/TOOLS.md +81 -0
  60. package/agents/recon-scout/CONFIG.yaml +22 -0
  61. package/agents/recon-scout/HEARTBEAT.md +79 -0
  62. package/agents/recon-scout/IDENTITY.md +1 -0
  63. package/agents/recon-scout/SKILLS.md +1 -0
  64. package/agents/recon-scout/SOUL.md +23 -0
  65. package/agents/recon-scout/TOOLS.md +93 -0
  66. package/agents/report-writer/CONFIG.yaml +21 -0
  67. package/agents/report-writer/HEARTBEAT.md +63 -0
  68. package/agents/report-writer/IDENTITY.md +1 -0
  69. package/agents/report-writer/SKILLS.md +1 -0
  70. package/agents/report-writer/SOUL.md +23 -0
  71. package/agents/report-writer/TOOLS.md +69 -0
  72. package/agents/shared/README.md +13 -0
  73. package/agents/web-hacker/CONFIG.yaml +24 -0
  74. package/agents/web-hacker/HEARTBEAT.md +78 -0
  75. package/agents/web-hacker/IDENTITY.md +1 -0
  76. package/agents/web-hacker/SKILLS.md +1 -0
  77. package/agents/web-hacker/SOUL.md +23 -0
  78. package/agents/web-hacker/TOOLS.md +86 -0
  79. package/api/CLAUDE.md +19 -0
  80. package/api/index.js +274 -0
  81. package/bin/cli.js +620 -0
  82. package/bin/local.sh +31 -0
  83. package/bin/postinstall.js +63 -0
  84. package/config/index.js +24 -0
  85. package/config/instrumentation.js +93 -0
  86. package/drizzle/0000_initial.sql +52 -0
  87. package/drizzle/0001_bounty_and_registry.sql +82 -0
  88. package/drizzle/0002_sync_columns.sql +7 -0
  89. package/drizzle/0003_graceful_bloodscream.sql +86 -0
  90. package/drizzle/meta/0000_snapshot.json +321 -0
  91. package/drizzle/meta/0003_snapshot.json +878 -0
  92. package/drizzle/meta/_journal.json +34 -0
  93. package/drizzle/relations.ts +3 -0
  94. package/drizzle/schema.ts +145 -0
  95. package/lib/actions.js +47 -0
  96. package/lib/agents.js +166 -0
  97. package/lib/ai/agent.js +96 -0
  98. package/lib/ai/autonomous-engine.js +261 -0
  99. package/lib/ai/index.js +359 -0
  100. package/lib/ai/model-router.js +254 -0
  101. package/lib/ai/model.js +73 -0
  102. package/lib/ai/tools.js +84 -0
  103. package/lib/auth/actions.js +28 -0
  104. package/lib/auth/config.js +27 -0
  105. package/lib/auth/edge-config.js +27 -0
  106. package/lib/auth/index.js +27 -0
  107. package/lib/auth/middleware.js +53 -0
  108. package/lib/bounty/actions.js +119 -0
  109. package/lib/bounty/findings.js +64 -0
  110. package/lib/bounty/programs.js +34 -0
  111. package/lib/bounty/sync-targets.js +267 -0
  112. package/lib/bounty/targets.js +33 -0
  113. package/lib/channels/base.js +56 -0
  114. package/lib/channels/index.js +15 -0
  115. package/lib/channels/telegram.js +148 -0
  116. package/lib/chat/actions.js +288 -0
  117. package/lib/chat/api.js +135 -0
  118. package/lib/chat/components/app-sidebar.js +237 -0
  119. package/lib/chat/components/app-sidebar.jsx +289 -0
  120. package/lib/chat/components/chat-header.js +27 -0
  121. package/lib/chat/components/chat-header.jsx +37 -0
  122. package/lib/chat/components/chat-input.js +230 -0
  123. package/lib/chat/components/chat-input.jsx +228 -0
  124. package/lib/chat/components/chat-nav-context.js +11 -0
  125. package/lib/chat/components/chat-nav-context.jsx +11 -0
  126. package/lib/chat/components/chat-page.js +81 -0
  127. package/lib/chat/components/chat-page.jsx +100 -0
  128. package/lib/chat/components/chat.js +150 -0
  129. package/lib/chat/components/chat.jsx +182 -0
  130. package/lib/chat/components/chats-page.js +302 -0
  131. package/lib/chat/components/chats-page.jsx +330 -0
  132. package/lib/chat/components/crons-page.js +172 -0
  133. package/lib/chat/components/crons-page.jsx +244 -0
  134. package/lib/chat/components/enhanced-tool-call.js +103 -0
  135. package/lib/chat/components/enhanced-tool-call.jsx +139 -0
  136. package/lib/chat/components/findings-page.js +175 -0
  137. package/lib/chat/components/findings-page.jsx +214 -0
  138. package/lib/chat/components/greeting.js +22 -0
  139. package/lib/chat/components/greeting.jsx +26 -0
  140. package/lib/chat/components/icons.js +777 -0
  141. package/lib/chat/components/icons.jsx +741 -0
  142. package/lib/chat/components/index.js +26 -0
  143. package/lib/chat/components/mcp-page.js +260 -0
  144. package/lib/chat/components/mcp-page.jsx +355 -0
  145. package/lib/chat/components/message.js +289 -0
  146. package/lib/chat/components/message.jsx +315 -0
  147. package/lib/chat/components/messages.js +66 -0
  148. package/lib/chat/components/messages.jsx +77 -0
  149. package/lib/chat/components/notifications-page.js +56 -0
  150. package/lib/chat/components/notifications-page.jsx +87 -0
  151. package/lib/chat/components/page-layout.js +21 -0
  152. package/lib/chat/components/page-layout.jsx +28 -0
  153. package/lib/chat/components/registry-page.js +222 -0
  154. package/lib/chat/components/registry-page.jsx +255 -0
  155. package/lib/chat/components/settings-layout.js +40 -0
  156. package/lib/chat/components/settings-layout.jsx +54 -0
  157. package/lib/chat/components/settings-secrets-page.js +216 -0
  158. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  159. package/lib/chat/components/sidebar-history-item.js +132 -0
  160. package/lib/chat/components/sidebar-history-item.jsx +113 -0
  161. package/lib/chat/components/sidebar-history.js +115 -0
  162. package/lib/chat/components/sidebar-history.jsx +157 -0
  163. package/lib/chat/components/sidebar-user-nav.js +63 -0
  164. package/lib/chat/components/sidebar-user-nav.jsx +73 -0
  165. package/lib/chat/components/status-bar.js +39 -0
  166. package/lib/chat/components/status-bar.jsx +51 -0
  167. package/lib/chat/components/swarm-page.js +157 -0
  168. package/lib/chat/components/swarm-page.jsx +210 -0
  169. package/lib/chat/components/targets-page.js +376 -0
  170. package/lib/chat/components/targets-page.jsx +389 -0
  171. package/lib/chat/components/tool-call.js +86 -0
  172. package/lib/chat/components/tool-call.jsx +104 -0
  173. package/lib/chat/components/tool-panel.js +107 -0
  174. package/lib/chat/components/tool-panel.jsx +145 -0
  175. package/lib/chat/components/triggers-page.js +153 -0
  176. package/lib/chat/components/triggers-page.jsx +221 -0
  177. package/lib/chat/components/ui/confirm-dialog.js +53 -0
  178. package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
  179. package/lib/chat/components/ui/dropdown-menu.js +98 -0
  180. package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
  181. package/lib/chat/components/ui/rename-dialog.js +74 -0
  182. package/lib/chat/components/ui/rename-dialog.jsx +72 -0
  183. package/lib/chat/components/ui/scroll-area.js +13 -0
  184. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  185. package/lib/chat/components/ui/separator.js +21 -0
  186. package/lib/chat/components/ui/separator.jsx +18 -0
  187. package/lib/chat/components/ui/sheet.js +75 -0
  188. package/lib/chat/components/ui/sheet.jsx +95 -0
  189. package/lib/chat/components/ui/sidebar.js +227 -0
  190. package/lib/chat/components/ui/sidebar.jsx +245 -0
  191. package/lib/chat/components/ui/tooltip.js +56 -0
  192. package/lib/chat/components/ui/tooltip.jsx +66 -0
  193. package/lib/chat/components/upgrade-dialog.js +151 -0
  194. package/lib/chat/components/upgrade-dialog.jsx +170 -0
  195. package/lib/chat/utils.js +11 -0
  196. package/lib/cron.js +246 -0
  197. package/lib/db/api-keys.js +163 -0
  198. package/lib/db/chats.js +145 -0
  199. package/lib/db/index.js +52 -0
  200. package/lib/db/notifications.js +99 -0
  201. package/lib/db/schema.js +145 -0
  202. package/lib/db/update-check.js +96 -0
  203. package/lib/db/users.js +89 -0
  204. package/lib/mcp/actions.js +104 -0
  205. package/lib/mcp/client.js +79 -0
  206. package/lib/mcp/handler.js +57 -0
  207. package/lib/mcp/server.js +165 -0
  208. package/lib/paths.js +46 -0
  209. package/lib/registry/actions.js +164 -0
  210. package/lib/registry/catalog.js +137 -0
  211. package/lib/registry/tools.js +71 -0
  212. package/lib/tools/create-job.js +99 -0
  213. package/lib/tools/github.js +217 -0
  214. package/lib/tools/openai.js +35 -0
  215. package/lib/tools/telegram.js +292 -0
  216. package/lib/triggers.js +118 -0
  217. package/lib/utils/render-md.js +102 -0
  218. package/package.json +103 -0
  219. package/setup/lib/auth.mjs +81 -0
  220. package/setup/lib/env.mjs +21 -0
  221. package/setup/lib/fs-utils.mjs +20 -0
  222. package/setup/lib/github.mjs +149 -0
  223. package/setup/lib/prerequisites.mjs +155 -0
  224. package/setup/lib/prompts.mjs +267 -0
  225. package/setup/lib/providers.mjs +48 -0
  226. package/setup/lib/sync.mjs +125 -0
  227. package/setup/lib/targets.mjs +45 -0
  228. package/setup/lib/telegram-verify.mjs +63 -0
  229. package/setup/lib/telegram.mjs +76 -0
  230. package/setup/setup-telegram.mjs +264 -0
  231. package/setup/setup.mjs +842 -0
  232. package/templates/.dockerignore +5 -0
  233. package/templates/.env.example +63 -0
  234. package/templates/.github/workflows/auto-merge.yml +117 -0
  235. package/templates/.github/workflows/build-image.yml +36 -0
  236. package/templates/.github/workflows/notify-job-failed.yml +64 -0
  237. package/templates/.github/workflows/notify-pr-complete.yml +119 -0
  238. package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
  239. package/templates/.github/workflows/run-job.yml +89 -0
  240. package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
  241. package/templates/.gitignore.template +45 -0
  242. package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
  243. package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
  244. package/templates/CLAUDE.md +29 -0
  245. package/templates/CLAUDE.md.template +307 -0
  246. package/templates/app/api/[...thepopebot]/route.js +1 -0
  247. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  248. package/templates/app/chat/[chatId]/page.js +8 -0
  249. package/templates/app/chats/page.js +7 -0
  250. package/templates/app/components/ascii-logo.jsx +10 -0
  251. package/templates/app/components/login-form.jsx +92 -0
  252. package/templates/app/components/setup-form.jsx +82 -0
  253. package/templates/app/components/theme-provider.jsx +11 -0
  254. package/templates/app/components/theme-toggle.jsx +38 -0
  255. package/templates/app/components/ui/button.jsx +21 -0
  256. package/templates/app/components/ui/card.jsx +23 -0
  257. package/templates/app/components/ui/input.jsx +10 -0
  258. package/templates/app/components/ui/label.jsx +10 -0
  259. package/templates/app/crons/page.js +5 -0
  260. package/templates/app/findings/page.js +7 -0
  261. package/templates/app/globals.css +90 -0
  262. package/templates/app/layout.js +19 -0
  263. package/templates/app/login/page.js +15 -0
  264. package/templates/app/notifications/page.js +7 -0
  265. package/templates/app/page.js +7 -0
  266. package/templates/app/settings/crons/page.js +5 -0
  267. package/templates/app/settings/layout.js +7 -0
  268. package/templates/app/settings/mcp/page.js +5 -0
  269. package/templates/app/settings/page.js +5 -0
  270. package/templates/app/settings/secrets/page.js +5 -0
  271. package/templates/app/settings/triggers/page.js +5 -0
  272. package/templates/app/stream/chat/route.js +1 -0
  273. package/templates/app/swarm/page.js +7 -0
  274. package/templates/app/targets/page.js +7 -0
  275. package/templates/app/toolbox/page.js +7 -0
  276. package/templates/app/triggers/page.js +5 -0
  277. package/templates/config/AGENT.md +34 -0
  278. package/templates/config/CRONS.json +56 -0
  279. package/templates/config/EVENT_HANDLER.md +224 -0
  280. package/templates/config/HEARTBEAT.md +3 -0
  281. package/templates/config/JOB_SUMMARY.md +130 -0
  282. package/templates/config/MCP_SERVERS.json +1 -0
  283. package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
  284. package/templates/config/SOUL.md +17 -0
  285. package/templates/config/TRIGGERS.json +58 -0
  286. package/templates/docker/event-handler/Dockerfile +20 -0
  287. package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
  288. package/templates/docker/job-claude-code/Dockerfile +34 -0
  289. package/templates/docker/job-claude-code/entrypoint.sh +139 -0
  290. package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
  291. package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
  292. package/templates/docker-compose.yml +63 -0
  293. package/templates/instrumentation.js +6 -0
  294. package/templates/middleware.js +1 -0
  295. package/templates/next.config.mjs +3 -0
  296. package/templates/postcss.config.mjs +5 -0
  297. package/templates/skills/LICENSE +21 -0
  298. package/templates/skills/README.md +119 -0
  299. package/templates/skills/brave-search/SKILL.md +79 -0
  300. package/templates/skills/brave-search/content.js +86 -0
  301. package/templates/skills/brave-search/package-lock.json +621 -0
  302. package/templates/skills/brave-search/package.json +14 -0
  303. package/templates/skills/brave-search/search.js +199 -0
  304. package/templates/skills/browser-tools/SKILL.md +196 -0
  305. package/templates/skills/browser-tools/browser-content.js +103 -0
  306. package/templates/skills/browser-tools/browser-cookies.js +35 -0
  307. package/templates/skills/browser-tools/browser-eval.js +53 -0
  308. package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
  309. package/templates/skills/browser-tools/browser-nav.js +44 -0
  310. package/templates/skills/browser-tools/browser-pick.js +162 -0
  311. package/templates/skills/browser-tools/browser-screenshot.js +34 -0
  312. package/templates/skills/browser-tools/browser-start.js +87 -0
  313. package/templates/skills/browser-tools/package-lock.json +2556 -0
  314. package/templates/skills/browser-tools/package.json +19 -0
  315. package/templates/skills/llm-secrets/SKILL.md +34 -0
  316. package/templates/skills/llm-secrets/llm-secrets.js +33 -0
  317. package/templates/skills/modify-self/SKILL.md +12 -0
@@ -0,0 +1,157 @@
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
+
9
+ function groupChatsByDate(chats) {
10
+ const now = new Date();
11
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
12
+ const yesterday = new Date(today.getTime() - 86400000);
13
+ const last7Days = new Date(today.getTime() - 7 * 86400000);
14
+ const last30Days = new Date(today.getTime() - 30 * 86400000);
15
+
16
+ const groups = {
17
+ Starred: [],
18
+ Today: [],
19
+ Yesterday: [],
20
+ 'Last 7 Days': [],
21
+ 'Last 30 Days': [],
22
+ Older: [],
23
+ };
24
+
25
+ for (const chat of chats) {
26
+ if (chat.starred) {
27
+ groups.Starred.push(chat);
28
+ continue;
29
+ }
30
+ const date = new Date(chat.updatedAt);
31
+ if (date >= today) {
32
+ groups.Today.push(chat);
33
+ } else if (date >= yesterday) {
34
+ groups.Yesterday.push(chat);
35
+ } else if (date >= last7Days) {
36
+ groups['Last 7 Days'].push(chat);
37
+ } else if (date >= last30Days) {
38
+ groups['Last 30 Days'].push(chat);
39
+ } else {
40
+ groups.Older.push(chat);
41
+ }
42
+ }
43
+
44
+ return groups;
45
+ }
46
+
47
+ export function SidebarHistory() {
48
+ const [chats, setChats] = useState([]);
49
+ const [loading, setLoading] = useState(true);
50
+ const { activeChatId, navigateToChat } = useChatNav();
51
+
52
+ const loadChats = async () => {
53
+ try {
54
+ const result = await getChats();
55
+ setChats(result);
56
+ } catch (err) {
57
+ console.error('Failed to load chats:', err);
58
+ } finally {
59
+ setLoading(false);
60
+ }
61
+ };
62
+
63
+ // Load chats on mount and refresh when navigating between pages
64
+ useEffect(() => {
65
+ loadChats();
66
+ }, [activeChatId]);
67
+
68
+ // Reload when chats change (new chat created or title updated)
69
+ useEffect(() => {
70
+ const handler = () => loadChats();
71
+ window.addEventListener('chatsupdated', handler);
72
+ return () => window.removeEventListener('chatsupdated', handler);
73
+ }, []);
74
+
75
+ const handleDelete = async (chatId) => {
76
+ setChats((prev) => prev.filter((c) => c.id !== chatId));
77
+ const { success } = await deleteChat(chatId);
78
+ if (success) {
79
+ if (chatId === activeChatId) {
80
+ navigateToChat(null);
81
+ }
82
+ } else {
83
+ loadChats();
84
+ }
85
+ };
86
+
87
+ const handleStar = async (chatId) => {
88
+ setChats((prev) =>
89
+ prev.map((c) => (c.id === chatId ? { ...c, starred: c.starred ? 0 : 1 } : c))
90
+ );
91
+ const { success } = await starChat(chatId);
92
+ if (!success) loadChats();
93
+ };
94
+
95
+ const handleRename = async (chatId, title) => {
96
+ setChats((prev) =>
97
+ prev.map((c) => (c.id === chatId ? { ...c, title } : c))
98
+ );
99
+ const { success } = await renameChat(chatId, title);
100
+ if (!success) loadChats();
101
+ };
102
+
103
+ if (loading && chats.length === 0) {
104
+ return (
105
+ <SidebarGroup>
106
+ <SidebarGroupContent>
107
+ <div className="flex flex-col gap-2 px-2">
108
+ {[...Array(5)].map((_, i) => (
109
+ <div key={i} className="h-8 animate-pulse rounded-md bg-border/50" />
110
+ ))}
111
+ </div>
112
+ </SidebarGroupContent>
113
+ </SidebarGroup>
114
+ );
115
+ }
116
+
117
+ if (chats.length === 0) {
118
+ return (
119
+ <SidebarGroup>
120
+ <SidebarGroupContent>
121
+ <p className="px-4 py-2 text-sm text-muted-foreground">
122
+ No chats yet. Start a conversation!
123
+ </p>
124
+ </SidebarGroupContent>
125
+ </SidebarGroup>
126
+ );
127
+ }
128
+
129
+ const grouped = groupChatsByDate(chats);
130
+
131
+ return (
132
+ <>
133
+ {Object.entries(grouped).map(
134
+ ([label, groupChats]) =>
135
+ groupChats.length > 0 && (
136
+ <SidebarGroup key={label}>
137
+ <SidebarGroupLabel>{label}</SidebarGroupLabel>
138
+ <SidebarGroupContent>
139
+ <SidebarMenu>
140
+ {groupChats.map((chat) => (
141
+ <SidebarHistoryItem
142
+ key={chat.id}
143
+ chat={chat}
144
+ isActive={chat.id === activeChatId}
145
+ onDelete={handleDelete}
146
+ onStar={handleStar}
147
+ onRename={handleRename}
148
+ />
149
+ ))}
150
+ </SidebarMenu>
151
+ </SidebarGroupContent>
152
+ </SidebarGroup>
153
+ )
154
+ )}
155
+ </>
156
+ );
157
+ }
@@ -0,0 +1,63 @@
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(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__ */ jsxs(DropdownMenuItem, { onClick: () => {
33
+ window.location.href = "/settings";
34
+ }, children: [
35
+ /* @__PURE__ */ jsx(SettingsIcon, { size: 14 }),
36
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Settings" })
37
+ ] }),
38
+ mounted && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => setTheme(theme === "dark" ? "light" : "dark"), children: [
39
+ theme === "dark" ? /* @__PURE__ */ jsx(SunIcon, { size: 14 }) : /* @__PURE__ */ jsx(MoonIcon, { size: 14 }),
40
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: theme === "dark" ? "Light Mode" : "Dark Mode" })
41
+ ] }),
42
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => window.open("https://github.com/stephengpope/thepopebot/issues", "_blank"), children: [
43
+ /* @__PURE__ */ jsx(BugIcon, { size: 14 }),
44
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Report Issues" })
45
+ ] }),
46
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
47
+ /* @__PURE__ */ jsxs(
48
+ DropdownMenuItem,
49
+ {
50
+ onClick: () => signOut({ callbackUrl: "/" }),
51
+ className: "text-destructive",
52
+ children: [
53
+ /* @__PURE__ */ jsx(LogOutIcon, { size: 14 }),
54
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Sign Out" })
55
+ ]
56
+ }
57
+ )
58
+ ] })
59
+ ] }) }) });
60
+ }
61
+ export {
62
+ SidebarUserNav
63
+ };
@@ -0,0 +1,73 @@
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(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 onClick={() => { window.location.href = '/settings'; }}>
47
+ <SettingsIcon size={14} />
48
+ <span className="ml-2">Settings</span>
49
+ </DropdownMenuItem>
50
+ {mounted && (
51
+ <DropdownMenuItem onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
52
+ {theme === 'dark' ? <SunIcon size={14} /> : <MoonIcon size={14} />}
53
+ <span className="ml-2">{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>
54
+ </DropdownMenuItem>
55
+ )}
56
+ <DropdownMenuItem onClick={() => window.open('https://github.com/stephengpope/thepopebot/issues', '_blank')}>
57
+ <BugIcon size={14} />
58
+ <span className="ml-2">Report Issues</span>
59
+ </DropdownMenuItem>
60
+ <DropdownMenuSeparator />
61
+ <DropdownMenuItem
62
+ onClick={() => signOut({ callbackUrl: '/' })}
63
+ className="text-destructive"
64
+ >
65
+ <LogOutIcon size={14} />
66
+ <span className="ml-2">Sign Out</span>
67
+ </DropdownMenuItem>
68
+ </DropdownMenuContent>
69
+ </DropdownMenu>
70
+ </SidebarMenuItem>
71
+ </SidebarMenu>
72
+ );
73
+ }
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../utils.js";
4
+ function StatusBar({ status, toolCount = 0, showToolPanel, onToggleToolPanel }) {
5
+ const isActive = status === "streaming" || status === "submitted";
6
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-1.5 border-b border-white/[0.06] bg-[--card]/50 backdrop-blur-sm", children: [
7
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
8
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
9
+ /* @__PURE__ */ jsx("div", { className: cn(
10
+ "w-1.5 h-1.5 rounded-full",
11
+ isActive ? "bg-[--success] animate-pulse" : "bg-muted-foreground/40"
12
+ ) }),
13
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: isActive ? "Live" : "Ready" })
14
+ ] }),
15
+ toolCount > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
16
+ /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "text-[--cyan]", children: /* @__PURE__ */ jsx("path", { d: "M13 2 3 14h9l-1 10 10-12h-9l1-10z" }) }),
17
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
18
+ toolCount,
19
+ " tool",
20
+ toolCount !== 1 ? "s" : ""
21
+ ] })
22
+ ] })
23
+ ] }),
24
+ toolCount > 0 && /* @__PURE__ */ jsx(
25
+ "button",
26
+ {
27
+ onClick: onToggleToolPanel,
28
+ className: cn(
29
+ "font-mono text-[10px] uppercase tracking-wider px-2 py-0.5 rounded transition-colors",
30
+ showToolPanel ? "text-[--cyan] bg-[--cyan]/10" : "text-muted-foreground hover:text-foreground"
31
+ ),
32
+ children: showToolPanel ? "Hide Panel" : "Show Panel"
33
+ }
34
+ )
35
+ ] });
36
+ }
37
+ export {
38
+ StatusBar
39
+ };
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { cn } from '../utils.js';
4
+
5
+ export function StatusBar({ status, toolCount = 0, showToolPanel, onToggleToolPanel }) {
6
+ const isActive = status === 'streaming' || status === 'submitted';
7
+
8
+ return (
9
+ <div className="flex items-center justify-between px-4 py-1.5 border-b border-white/[0.06] bg-[--card]/50 backdrop-blur-sm">
10
+ <div className="flex items-center gap-3">
11
+ {/* Status indicator */}
12
+ <div className="flex items-center gap-1.5">
13
+ <div className={cn(
14
+ 'w-1.5 h-1.5 rounded-full',
15
+ isActive ? 'bg-[--success] animate-pulse' : 'bg-muted-foreground/40'
16
+ )} />
17
+ <span className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground">
18
+ {isActive ? 'Live' : 'Ready'}
19
+ </span>
20
+ </div>
21
+
22
+ {/* Tool count */}
23
+ {toolCount > 0 && (
24
+ <div className="flex items-center gap-1.5">
25
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="text-[--cyan]">
26
+ <path d="M13 2 3 14h9l-1 10 10-12h-9l1-10z" />
27
+ </svg>
28
+ <span className="font-mono text-[10px] text-muted-foreground">
29
+ {toolCount} tool{toolCount !== 1 ? 's' : ''}
30
+ </span>
31
+ </div>
32
+ )}
33
+ </div>
34
+
35
+ {/* Panel toggle */}
36
+ {toolCount > 0 && (
37
+ <button
38
+ onClick={onToggleToolPanel}
39
+ className={cn(
40
+ 'font-mono text-[10px] uppercase tracking-wider px-2 py-0.5 rounded transition-colors',
41
+ showToolPanel
42
+ ? 'text-[--cyan] bg-[--cyan]/10'
43
+ : 'text-muted-foreground hover:text-foreground'
44
+ )}
45
+ >
46
+ {showToolPanel ? 'Hide Panel' : 'Show Panel'}
47
+ </button>
48
+ )}
49
+ </div>
50
+ );
51
+ }
@@ -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
+ };