@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,34 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1771448716757,
9
+ "tag": "0000_initial",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "6",
15
+ "when": 1772228400000,
16
+ "tag": "0001_bounty_and_registry",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "6",
22
+ "when": 1772315400000,
23
+ "tag": "0002_sync_columns",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1772287739089,
30
+ "tag": "0003_graceful_bloodscream",
31
+ "breakpoints": true
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,3 @@
1
+ import { relations } from "drizzle-orm/relations";
2
+ import { } from "./schema";
3
+
@@ -0,0 +1,145 @@
1
+ import { sqliteTable, AnySQLiteColumn, text, integer, uniqueIndex } from "drizzle-orm/sqlite-core"
2
+ import { sql } from "drizzle-orm"
3
+
4
+ export const programs = sqliteTable("programs", {
5
+ id: text().primaryKey().notNull(),
6
+ name: text().notNull(),
7
+ platform: text().default("custom").notNull(),
8
+ url: text(),
9
+ scopeUrl: text("scope_url"),
10
+ minBounty: integer("min_bounty"),
11
+ maxBounty: integer("max_bounty"),
12
+ status: text().default("active").notNull(),
13
+ notes: text(),
14
+ createdAt: integer("created_at").notNull(),
15
+ updatedAt: integer("updated_at").notNull(),
16
+ syncHandle: text("sync_handle"),
17
+ lastSyncedAt: integer("last_synced_at"),
18
+ });
19
+
20
+ export const targets = sqliteTable("targets", {
21
+ id: text().primaryKey().notNull(),
22
+ programId: text("program_id"),
23
+ type: text().default("domain").notNull(),
24
+ value: text().notNull(),
25
+ status: text().default("in_scope").notNull(),
26
+ technologies: text(),
27
+ notes: text(),
28
+ lastScannedAt: integer("last_scanned_at"),
29
+ createdAt: integer("created_at").notNull(),
30
+ updatedAt: integer("updated_at").notNull(),
31
+ syncSource: text("sync_source"),
32
+ syncProgramHandle: text("sync_program_handle"),
33
+ });
34
+
35
+ export const findings = sqliteTable("findings", {
36
+ id: text().primaryKey().notNull(),
37
+ targetId: text("target_id"),
38
+ title: text().notNull(),
39
+ severity: text().default("info").notNull(),
40
+ type: text().notNull(),
41
+ status: text().default("new").notNull(),
42
+ description: text(),
43
+ stepsToReproduce: text("steps_to_reproduce"),
44
+ impact: text(),
45
+ evidence: text(),
46
+ bountyAmount: integer("bounty_amount"),
47
+ reportUrl: text("report_url"),
48
+ agentId: text("agent_id"),
49
+ toolId: text("tool_id"),
50
+ rawOutput: text("raw_output"),
51
+ reportedAt: integer("reported_at"),
52
+ createdAt: integer("created_at").notNull(),
53
+ updatedAt: integer("updated_at").notNull(),
54
+ });
55
+
56
+ export const tools = sqliteTable("tools", {
57
+ id: text().primaryKey().notNull(),
58
+ catalogId: text("catalog_id"),
59
+ name: text().notNull(),
60
+ slug: text().notNull(),
61
+ category: text().notNull(),
62
+ description: text(),
63
+ dockerImage: text("docker_image"),
64
+ installCmd: text("install_cmd"),
65
+ sourceUrl: text("source_url"),
66
+ version: text(),
67
+ installed: integer().default(0).notNull(),
68
+ enabled: integer().default(1).notNull(),
69
+ config: text(),
70
+ mcpServerId: text("mcp_server_id"),
71
+ createdAt: integer("created_at").notNull(),
72
+ updatedAt: integer("updated_at").notNull(),
73
+ },
74
+ (table) => [
75
+ uniqueIndex("tools_slug_unique").on(table.slug),
76
+ ]);
77
+
78
+ export const dockerContainers = sqliteTable("docker_containers", {
79
+ id: text().primaryKey().notNull(),
80
+ toolId: text("tool_id"),
81
+ containerId: text("container_id"),
82
+ imageName: text("image_name").notNull(),
83
+ status: text().default("created").notNull(),
84
+ agentId: text("agent_id"),
85
+ ports: text(),
86
+ env: text(),
87
+ logs: text(),
88
+ createdAt: integer("created_at").notNull(),
89
+ stoppedAt: integer("stopped_at"),
90
+ });
91
+
92
+ export const chats = sqliteTable("chats", {
93
+ id: text().primaryKey().notNull(),
94
+ userId: text("user_id").notNull(),
95
+ title: text().default("New Chat").notNull(),
96
+ starred: integer().default(0).notNull(),
97
+ createdAt: integer("created_at").notNull(),
98
+ updatedAt: integer("updated_at").notNull(),
99
+ });
100
+
101
+ export const messages = sqliteTable("messages", {
102
+ id: text().primaryKey().notNull(),
103
+ chatId: text("chat_id").notNull(),
104
+ role: text().notNull(),
105
+ content: text().notNull(),
106
+ createdAt: integer("created_at").notNull(),
107
+ });
108
+
109
+ export const notifications = sqliteTable("notifications", {
110
+ id: text().primaryKey().notNull(),
111
+ notification: text().notNull(),
112
+ payload: text().notNull(),
113
+ read: integer().default(0).notNull(),
114
+ createdAt: integer("created_at").notNull(),
115
+ });
116
+
117
+ export const settings = sqliteTable("settings", {
118
+ id: text().primaryKey().notNull(),
119
+ type: text().notNull(),
120
+ key: text().notNull(),
121
+ value: text().notNull(),
122
+ createdBy: text("created_by"),
123
+ createdAt: integer("created_at").notNull(),
124
+ updatedAt: integer("updated_at").notNull(),
125
+ });
126
+
127
+ export const subscriptions = sqliteTable("subscriptions", {
128
+ id: text().primaryKey().notNull(),
129
+ platform: text().notNull(),
130
+ channelId: text("channel_id").notNull(),
131
+ createdAt: integer("created_at").notNull(),
132
+ });
133
+
134
+ export const users = sqliteTable("users", {
135
+ id: text().primaryKey().notNull(),
136
+ email: text().notNull(),
137
+ passwordHash: text("password_hash").notNull(),
138
+ role: text().default("admin").notNull(),
139
+ createdAt: integer("created_at").notNull(),
140
+ updatedAt: integer("updated_at").notNull(),
141
+ },
142
+ (table) => [
143
+ uniqueIndex("users_email_unique").on(table.email),
144
+ ]);
145
+
package/lib/actions.js ADDED
@@ -0,0 +1,47 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { createJob } from './tools/create-job.js';
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ /**
8
+ * Execute a single action
9
+ * @param {Object} action - { type, job, command, url, method, headers, vars } (type: agent|command|webhook)
10
+ * @param {Object} opts - { cwd, data }
11
+ * @returns {Promise<string>} Result description for logging
12
+ */
13
+ async function executeAction(action, opts = {}) {
14
+ const type = action.type || 'agent';
15
+
16
+ if (type === 'command') {
17
+ const { stdout, stderr } = await execAsync(action.command, {
18
+ cwd: opts.cwd,
19
+ timeout: 30000,
20
+ });
21
+ return (stdout || stderr || '').trim();
22
+ }
23
+
24
+ if (type === 'webhook') {
25
+ const method = (action.method || 'POST').toUpperCase();
26
+ const headers = { 'Content-Type': 'application/json', ...action.headers };
27
+ const fetchOpts = { method, headers };
28
+
29
+ if (method !== 'GET') {
30
+ const body = { ...action.vars };
31
+ if (opts.data) body.data = opts.data;
32
+ fetchOpts.body = JSON.stringify(body);
33
+ }
34
+
35
+ const res = await fetch(action.url, fetchOpts);
36
+ return `${method} ${action.url} → ${res.status}`;
37
+ }
38
+
39
+ // Default: agent
40
+ const options = {};
41
+ if (action.llm_provider) options.llmProvider = action.llm_provider;
42
+ if (action.llm_model) options.llmModel = action.llm_model;
43
+ const result = await createJob(action.job, options);
44
+ return `job ${result.job_id} — ${result.title}`;
45
+ }
46
+
47
+ export { executeAction };
package/lib/agents.js ADDED
@@ -0,0 +1,166 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { agentsDir } from './paths.js';
4
+
5
+ const SKIP_DIRS = ['shared', '_template', 'node_modules'];
6
+
7
+ /**
8
+ * Parse flat YAML (key: value per line, simple arrays with [a, b]).
9
+ * No external yaml dependency needed — CONFIG.yaml files are flat key-value.
10
+ */
11
+ function parseSimpleYaml(content) {
12
+ const result = {};
13
+ for (const line of content.split('\n')) {
14
+ const trimmed = line.split('#')[0].trim(); // strip comments
15
+ if (!trimmed || trimmed.startsWith('-')) continue;
16
+
17
+ const colonIdx = trimmed.indexOf(':');
18
+ if (colonIdx === -1) continue;
19
+
20
+ const key = trimmed.slice(0, colonIdx).trim();
21
+ let value = trimmed.slice(colonIdx + 1).trim();
22
+
23
+ // Parse arrays: [a, b, c]
24
+ if (value.startsWith('[') && value.endsWith(']')) {
25
+ value = value.slice(1, -1).split(',').map(v => v.trim()).filter(Boolean);
26
+ }
27
+ // Parse numbers
28
+ else if (/^\d+(\.\d+)?$/.test(value)) {
29
+ value = parseFloat(value);
30
+ }
31
+ // Parse booleans
32
+ else if (value === 'true') value = true;
33
+ else if (value === 'false') value = false;
34
+
35
+ result[key] = value;
36
+ }
37
+ return result;
38
+ }
39
+
40
+ /**
41
+ * Parse IDENTITY.md into structured fields.
42
+ * Supports multiple formats:
43
+ * - Single-line: "Name: X. Codename: Y. Role: Z. Specialization: W."
44
+ * - Multi-line: "Name: X\nCodename: Y\nRole: Z\n..."
45
+ * - Markdown: "**Codename:** X\n**Role:** Y\n..."
46
+ */
47
+ function parseIdentity(content) {
48
+ const identity = {};
49
+ const text = content.trim();
50
+ const fields = ['name', 'codename', 'role', 'specialization'];
51
+
52
+ // Extract each field by scanning the full text with a regex
53
+ // This handles both single-line ("Name: X. Codename: Y.") and multi-line formats
54
+ for (const field of fields) {
55
+ // Match "Field: value" or "**Field:** value" (case-insensitive)
56
+ // Value ends at next field label, period-space-capital, newline, or end of string
57
+ const pattern = new RegExp(
58
+ `\\*{0,2}${field}\\*{0,2}[:\\s]+\\s*(.+?)(?=\\s*\\.\\s*(?:Name|Codename|Role|Specialization)\\b|\\n|$)`,
59
+ 'i'
60
+ );
61
+ const match = text.match(pattern);
62
+ if (match) {
63
+ identity[field] = match[1].trim().replace(/\.$/, '').replace(/^\*+\s*/, '').replace(/\s*\*+$/, '');
64
+ }
65
+ }
66
+
67
+ // Fallback for markdown title: "# MAINTAINER — Code Quality Specialist"
68
+ if (!identity.codename) {
69
+ const titleMatch = text.match(/^#\s+(\w+)\s+[—–-]\s+(.+)/m);
70
+ if (titleMatch) {
71
+ identity.codename = titleMatch[1].trim();
72
+ if (!identity.role) identity.role = titleMatch[2].trim();
73
+ }
74
+ }
75
+
76
+ return identity;
77
+ }
78
+
79
+ /**
80
+ * Load a single agent profile by directory name.
81
+ * Returns null if the directory doesn't exist or is invalid.
82
+ */
83
+ export function loadAgentProfile(agentDirName) {
84
+ const agentPath = path.join(agentsDir, agentDirName);
85
+
86
+ if (!fs.existsSync(agentPath)) return null;
87
+ const stat = fs.statSync(agentPath);
88
+ if (!stat.isDirectory()) return null;
89
+
90
+ const profile = { id: agentDirName };
91
+
92
+ // IDENTITY.md
93
+ const identityPath = path.join(agentPath, 'IDENTITY.md');
94
+ if (fs.existsSync(identityPath)) {
95
+ const identityContent = fs.readFileSync(identityPath, 'utf8');
96
+ Object.assign(profile, parseIdentity(identityContent));
97
+ }
98
+
99
+ // CONFIG.yaml
100
+ const configPath = path.join(agentPath, 'CONFIG.yaml');
101
+ if (fs.existsSync(configPath)) {
102
+ const configContent = fs.readFileSync(configPath, 'utf8');
103
+ profile.config = parseSimpleYaml(configContent);
104
+ }
105
+
106
+ // SOUL.md (raw content — used as system prompt)
107
+ const soulPath = path.join(agentPath, 'SOUL.md');
108
+ if (fs.existsSync(soulPath)) {
109
+ profile.soul = fs.readFileSync(soulPath, 'utf8');
110
+ }
111
+
112
+ // SKILLS.md
113
+ const skillsPath = path.join(agentPath, 'SKILLS.md');
114
+ if (fs.existsSync(skillsPath)) {
115
+ profile.skills = fs.readFileSync(skillsPath, 'utf8');
116
+ }
117
+
118
+ // TOOLS.md
119
+ const toolsPath = path.join(agentPath, 'TOOLS.md');
120
+ if (fs.existsSync(toolsPath)) {
121
+ profile.tools = fs.readFileSync(toolsPath, 'utf8');
122
+ }
123
+
124
+ // HEARTBEAT.md
125
+ const heartbeatPath = path.join(agentPath, 'HEARTBEAT.md');
126
+ if (fs.existsSync(heartbeatPath)) {
127
+ profile.heartbeat = fs.readFileSync(heartbeatPath, 'utf8');
128
+ }
129
+
130
+ return profile;
131
+ }
132
+
133
+ /**
134
+ * Discover all agent profiles from agents/ directory.
135
+ * Returns array of profile objects with id, name, codename, role, etc.
136
+ */
137
+ export function discoverAgents() {
138
+ if (!fs.existsSync(agentsDir)) return [];
139
+
140
+ const entries = fs.readdirSync(agentsDir, { withFileTypes: true });
141
+ const agents = [];
142
+
143
+ for (const entry of entries) {
144
+ if (!entry.isDirectory()) continue;
145
+ if (SKIP_DIRS.includes(entry.name)) continue;
146
+
147
+ const profile = loadAgentProfile(entry.name);
148
+ if (profile) agents.push(profile);
149
+ }
150
+
151
+ return agents;
152
+ }
153
+
154
+ /**
155
+ * Get a bullet-list summary of all discovered agents (for {{agents}} variable).
156
+ */
157
+ export function loadAgentDescriptions() {
158
+ const agents = discoverAgents();
159
+ if (agents.length === 0) return 'No agent profiles configured.';
160
+
161
+ return agents.map(a => {
162
+ const name = a.codename || a.name || a.id;
163
+ const role = a.role || 'general';
164
+ return `- **${name}**: ${role}`;
165
+ }).join('\n');
166
+ }
@@ -0,0 +1,96 @@
1
+ import { createReactAgent } from '@langchain/langgraph/prebuilt';
2
+ import { SystemMessage } from '@langchain/core/messages';
3
+ import { createModel } from './model.js';
4
+ import { createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool } from './tools.js';
5
+ import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
6
+ import { eventHandlerMd, thepopebotDb } from '../paths.js';
7
+ import { render_md } from '../utils/render-md.js';
8
+ import { loadAgentProfile } from '../agents.js';
9
+
10
+ let _agent = null;
11
+
12
+ /**
13
+ * Get or create the LangGraph agent singleton.
14
+ * Uses createReactAgent which handles the tool loop automatically.
15
+ * Prompt is a function so {{datetime}} resolves fresh each invocation.
16
+ */
17
+ export async function getAgent() {
18
+ if (!_agent) {
19
+ const model = await createModel();
20
+ let mcpTools = [];
21
+ try {
22
+ const { loadMcpTools } = await import('../mcp/client.js');
23
+ mcpTools = await loadMcpTools();
24
+ } catch (err) {
25
+ console.error('[getAgent] Failed to load MCP tools:', err.message);
26
+ }
27
+ const tools = [createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool, ...mcpTools];
28
+ const checkpointer = SqliteSaver.fromConnString(thepopebotDb);
29
+
30
+ _agent = createReactAgent({
31
+ llm: model,
32
+ tools,
33
+ checkpointSaver: checkpointer,
34
+ prompt: (state) => [new SystemMessage(render_md(eventHandlerMd)), ...state.messages],
35
+ });
36
+ }
37
+ return _agent;
38
+ }
39
+
40
+ /**
41
+ * Reset the agent singleton (e.g., when config changes).
42
+ */
43
+ export function resetAgent() {
44
+ _agent = null;
45
+ }
46
+
47
+ // Per-profile agent instances
48
+ const _agents = new Map();
49
+
50
+ /**
51
+ * Get or create an agent for a specific agent profile.
52
+ * Uses the agent's SOUL.md as the system prompt instead of EVENT_HANDLER.md.
53
+ * Falls back to getAgent() if no profile found.
54
+ *
55
+ * @param {string} profileName - Directory name of the agent profile (e.g. 'recon-scout')
56
+ */
57
+ export async function getAgentForProfile(profileName) {
58
+ if (_agents.has(profileName)) return _agents.get(profileName);
59
+
60
+ const profile = loadAgentProfile(profileName);
61
+ if (!profile || !profile.soul) return getAgent(); // fallback to default
62
+
63
+ const model = await createModel();
64
+ let mcpTools = [];
65
+ try {
66
+ const { loadMcpTools } = await import('../mcp/client.js');
67
+ mcpTools = await loadMcpTools();
68
+ } catch (err) {
69
+ console.error(`[getAgentForProfile:${profileName}] Failed to load MCP tools:`, err.message);
70
+ }
71
+ const tools = [createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool, ...mcpTools];
72
+ const checkpointer = SqliteSaver.fromConnString(thepopebotDb);
73
+
74
+ // Build the system prompt from the agent's SOUL + optional SKILLS + TOOLS
75
+ const systemParts = [profile.soul];
76
+ if (profile.skills) systemParts.push('\n\n## Skills\n\n' + profile.skills);
77
+ if (profile.tools) systemParts.push('\n\n## Tools\n\n' + profile.tools);
78
+ const systemPrompt = systemParts.join('');
79
+
80
+ const agent = createReactAgent({
81
+ llm: model,
82
+ tools,
83
+ checkpointSaver: checkpointer,
84
+ prompt: (state) => [new SystemMessage(systemPrompt), ...state.messages],
85
+ });
86
+
87
+ _agents.set(profileName, agent);
88
+ return agent;
89
+ }
90
+
91
+ /**
92
+ * Reset a specific profile agent (e.g., when config changes).
93
+ */
94
+ export function resetAgentForProfile(profileName) {
95
+ _agents.delete(profileName);
96
+ }