@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,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,103 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { motion } from "framer-motion";
5
+ import { cn } from "../utils.js";
6
+ import { SpinnerIcon, CheckIcon, XIcon } from "./icons.js";
7
+ const TOOL_DISPLAY_NAMES = {
8
+ create_job: "Create Job",
9
+ get_job_status: "Check Job Status",
10
+ get_system_technical_specs: "Read Tech Docs",
11
+ get_skill_building_guide: "Read Skill Docs"
12
+ };
13
+ function getToolDisplayName(toolName) {
14
+ return TOOL_DISPLAY_NAMES[toolName] || toolName.replace(/_/g, " ");
15
+ }
16
+ function formatContent(content) {
17
+ if (content == null) return null;
18
+ if (typeof content === "string") {
19
+ try {
20
+ const parsed = JSON.parse(content);
21
+ return JSON.stringify(parsed, null, 2);
22
+ } catch {
23
+ return content;
24
+ }
25
+ }
26
+ return JSON.stringify(content, null, 2);
27
+ }
28
+ function EnhancedToolCall({ part }) {
29
+ const [expanded, setExpanded] = useState(false);
30
+ const toolName = part.toolName || (part.type?.startsWith("tool-") ? part.type.slice(5) : "tool");
31
+ const displayName = getToolDisplayName(toolName);
32
+ const state = part.state || "input-available";
33
+ const isRunning = state === "input-streaming" || state === "input-available";
34
+ const isDone = state === "output-available";
35
+ const isError = state === "output-error";
36
+ return /* @__PURE__ */ jsxs(
37
+ motion.div,
38
+ {
39
+ initial: { opacity: 0, y: 8 },
40
+ animate: { opacity: 1, y: 0 },
41
+ transition: { duration: 0.3, ease: "easeOut" },
42
+ className: "my-2 rounded-lg border border-white/[0.06] bg-[--card] overflow-hidden",
43
+ children: [
44
+ /* @__PURE__ */ jsxs(
45
+ "button",
46
+ {
47
+ onClick: () => setExpanded(!expanded),
48
+ className: "flex w-full items-center gap-3 px-3 py-2.5 text-left hover:bg-white/[0.02] transition-colors",
49
+ children: [
50
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0", children: [
51
+ /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#ff5f57]" }),
52
+ /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#febc2e]" }),
53
+ /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#28c840]" })
54
+ ] }),
55
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
56
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "text-[--cyan] shrink-0", children: /* @__PURE__ */ jsx("path", { d: "M13 2 3 14h9l-1 10 10-12h-9l1-10z" }) }),
57
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs font-medium text-[--cyan] truncate", children: displayName })
58
+ ] }),
59
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs shrink-0", children: [
60
+ isRunning && /* @__PURE__ */ jsxs(Fragment, { children: [
61
+ /* @__PURE__ */ jsx(SpinnerIcon, { size: 12, className: "text-[--cyan]" }),
62
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono", children: "running" })
63
+ ] }),
64
+ isDone && /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ /* @__PURE__ */ jsx(CheckIcon, { size: 12, className: "text-[--success]" }),
66
+ /* @__PURE__ */ jsx("span", { className: "text-[--success] font-mono", children: "done" })
67
+ ] }),
68
+ isError && /* @__PURE__ */ jsxs(Fragment, { children: [
69
+ /* @__PURE__ */ jsx(XIcon, { size: 12, className: "text-[--destructive]" }),
70
+ /* @__PURE__ */ jsx("span", { className: "text-[--destructive] font-mono", children: "error" })
71
+ ] })
72
+ ] })
73
+ ]
74
+ }
75
+ ),
76
+ /* @__PURE__ */ jsxs("div", { className: "px-3 pb-2 flex items-center gap-1.5 text-[11px] font-mono text-muted-foreground", children: [
77
+ /* @__PURE__ */ jsx("span", { className: "text-[--cyan]", children: "harbinger:~$" }),
78
+ /* @__PURE__ */ jsx("span", { className: "text-foreground/60", children: toolName }),
79
+ isRunning && /* @__PURE__ */ jsx("span", { className: "w-1.5 h-3.5 bg-[--cyan] animate-terminal-cursor" })
80
+ ] }),
81
+ expanded && /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-3 py-3 text-xs", children: [
82
+ part.input != null && /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
83
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Input" }),
84
+ /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap break-all rounded-md bg-black/30 p-2.5 text-foreground/80 overflow-x-auto font-mono text-[11px] border border-white/[0.04]", children: formatContent(part.input) })
85
+ ] }),
86
+ part.output != null && /* @__PURE__ */ jsxs("div", { children: [
87
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Output" }),
88
+ /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap break-all rounded-md bg-black/30 p-2.5 text-foreground/80 overflow-x-auto max-h-64 overflow-y-auto font-mono text-[11px] border border-white/[0.04] scrollbar-thin", children: formatContent(part.output) })
89
+ ] }),
90
+ part.input == null && part.output == null && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground italic font-mono", children: "Waiting for data..." })
91
+ ] }),
92
+ /* @__PURE__ */ jsxs("div", { className: "h-0.5 w-full bg-white/[0.04]", children: [
93
+ isRunning && /* @__PURE__ */ jsx("div", { className: "h-full w-1/3 bg-[--cyan] rounded-full animate-progress-shimmer" }),
94
+ isDone && /* @__PURE__ */ jsx("div", { className: "h-full w-full bg-[--success] transition-all duration-500" }),
95
+ isError && /* @__PURE__ */ jsx("div", { className: "h-full w-full bg-[--destructive] transition-all duration-500" })
96
+ ] })
97
+ ]
98
+ }
99
+ );
100
+ }
101
+ export {
102
+ EnhancedToolCall
103
+ };
@@ -0,0 +1,139 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import { cn } from '../utils.js';
6
+ import { SpinnerIcon, CheckIcon, XIcon } from './icons.js';
7
+
8
+ const TOOL_DISPLAY_NAMES = {
9
+ create_job: 'Create Job',
10
+ get_job_status: 'Check Job Status',
11
+ get_system_technical_specs: 'Read Tech Docs',
12
+ get_skill_building_guide: 'Read Skill Docs',
13
+ };
14
+
15
+ function getToolDisplayName(toolName) {
16
+ return TOOL_DISPLAY_NAMES[toolName] || toolName.replace(/_/g, ' ');
17
+ }
18
+
19
+ function formatContent(content) {
20
+ if (content == null) return null;
21
+ if (typeof content === 'string') {
22
+ try {
23
+ const parsed = JSON.parse(content);
24
+ return JSON.stringify(parsed, null, 2);
25
+ } catch {
26
+ return content;
27
+ }
28
+ }
29
+ return JSON.stringify(content, null, 2);
30
+ }
31
+
32
+ export function EnhancedToolCall({ part }) {
33
+ const [expanded, setExpanded] = useState(false);
34
+
35
+ const toolName = part.toolName || (part.type?.startsWith('tool-') ? part.type.slice(5) : 'tool');
36
+ const displayName = getToolDisplayName(toolName);
37
+ const state = part.state || 'input-available';
38
+
39
+ const isRunning = state === 'input-streaming' || state === 'input-available';
40
+ const isDone = state === 'output-available';
41
+ const isError = state === 'output-error';
42
+
43
+ return (
44
+ <motion.div
45
+ initial={{ opacity: 0, y: 8 }}
46
+ animate={{ opacity: 1, y: 0 }}
47
+ transition={{ duration: 0.3, ease: 'easeOut' }}
48
+ className="my-2 rounded-lg border border-white/[0.06] bg-[--card] overflow-hidden"
49
+ >
50
+ {/* Terminal header */}
51
+ <button
52
+ onClick={() => setExpanded(!expanded)}
53
+ className="flex w-full items-center gap-3 px-3 py-2.5 text-left hover:bg-white/[0.02] transition-colors"
54
+ >
55
+ {/* Traffic light dots */}
56
+ <div className="flex items-center gap-1.5 shrink-0">
57
+ <div className="w-2.5 h-2.5 rounded-full bg-[#ff5f57]" />
58
+ <div className="w-2.5 h-2.5 rounded-full bg-[#febc2e]" />
59
+ <div className="w-2.5 h-2.5 rounded-full bg-[#28c840]" />
60
+ </div>
61
+
62
+ {/* Tool name */}
63
+ <div className="flex items-center gap-2 flex-1 min-w-0">
64
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="text-[--cyan] shrink-0">
65
+ <path d="M13 2 3 14h9l-1 10 10-12h-9l1-10z" />
66
+ </svg>
67
+ <span className="font-mono text-xs font-medium text-[--cyan] truncate">{displayName}</span>
68
+ </div>
69
+
70
+ {/* Status */}
71
+ <span className="flex items-center gap-1.5 text-xs shrink-0">
72
+ {isRunning && (
73
+ <>
74
+ <SpinnerIcon size={12} className="text-[--cyan]" />
75
+ <span className="text-muted-foreground font-mono">running</span>
76
+ </>
77
+ )}
78
+ {isDone && (
79
+ <>
80
+ <CheckIcon size={12} className="text-[--success]" />
81
+ <span className="text-[--success] font-mono">done</span>
82
+ </>
83
+ )}
84
+ {isError && (
85
+ <>
86
+ <XIcon size={12} className="text-[--destructive]" />
87
+ <span className="text-[--destructive] font-mono">error</span>
88
+ </>
89
+ )}
90
+ </span>
91
+ </button>
92
+
93
+ {/* Terminal prompt line */}
94
+ <div className="px-3 pb-2 flex items-center gap-1.5 text-[11px] font-mono text-muted-foreground">
95
+ <span className="text-[--cyan]">harbinger:~$</span>
96
+ <span className="text-foreground/60">{toolName}</span>
97
+ {isRunning && <span className="w-1.5 h-3.5 bg-[--cyan] animate-terminal-cursor" />}
98
+ </div>
99
+
100
+ {/* Expandable detail */}
101
+ {expanded && (
102
+ <div className="border-t border-white/[0.06] px-3 py-3 text-xs">
103
+ {part.input != null && (
104
+ <div className="mb-3">
105
+ <div className="font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1.5">Input</div>
106
+ <pre className="whitespace-pre-wrap break-all rounded-md bg-black/30 p-2.5 text-foreground/80 overflow-x-auto font-mono text-[11px] border border-white/[0.04]">
107
+ {formatContent(part.input)}
108
+ </pre>
109
+ </div>
110
+ )}
111
+ {part.output != null && (
112
+ <div>
113
+ <div className="font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1.5">Output</div>
114
+ <pre className="whitespace-pre-wrap break-all rounded-md bg-black/30 p-2.5 text-foreground/80 overflow-x-auto max-h-64 overflow-y-auto font-mono text-[11px] border border-white/[0.04] scrollbar-thin">
115
+ {formatContent(part.output)}
116
+ </pre>
117
+ </div>
118
+ )}
119
+ {part.input == null && part.output == null && (
120
+ <div className="text-muted-foreground italic font-mono">Waiting for data...</div>
121
+ )}
122
+ </div>
123
+ )}
124
+
125
+ {/* Progress bar */}
126
+ <div className="h-0.5 w-full bg-white/[0.04]">
127
+ {isRunning && (
128
+ <div className="h-full w-1/3 bg-[--cyan] rounded-full animate-progress-shimmer" />
129
+ )}
130
+ {isDone && (
131
+ <div className="h-full w-full bg-[--success] transition-all duration-500" />
132
+ )}
133
+ {isError && (
134
+ <div className="h-full w-full bg-[--destructive] transition-all duration-500" />
135
+ )}
136
+ </div>
137
+ </motion.div>
138
+ );
139
+ }