@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,164 @@
1
+ 'use server';
2
+
3
+ import { auth } from '../auth/index.js';
4
+
5
+ async function requireAuth() {
6
+ const session = await auth();
7
+ if (!session?.user?.id) throw new Error('Unauthorized');
8
+ return session.user;
9
+ }
10
+
11
+ // ── Tool Catalog ─────────────────────────────────────────────────────────────
12
+
13
+ export async function getCatalog() {
14
+ await requireAuth();
15
+ const { TOOL_CATALOG, TOOL_CATEGORIES } = await import('./catalog.js');
16
+ return { tools: TOOL_CATALOG, categories: TOOL_CATEGORIES };
17
+ }
18
+
19
+ export async function searchCatalog(query) {
20
+ await requireAuth();
21
+ const { searchCatalog: search } = await import('./catalog.js');
22
+ return search(query);
23
+ }
24
+
25
+ // ── Installed Tools ──────────────────────────────────────────────────────────
26
+
27
+ export async function getInstalledTools() {
28
+ await requireAuth();
29
+ const { getInstalledTools: dbGet } = await import('./tools.js');
30
+ return dbGet();
31
+ }
32
+
33
+ export async function installTool(catalogId) {
34
+ await requireAuth();
35
+ const { TOOL_CATALOG } = await import('./catalog.js');
36
+ const { installTool: dbInstall, getToolBySlug } = await import('./tools.js');
37
+
38
+ const catalogEntry = TOOL_CATALOG.find(t => t.id === catalogId);
39
+ if (!catalogEntry) return { error: 'Tool not found in catalog' };
40
+
41
+ const existing = getToolBySlug(catalogEntry.id);
42
+ if (existing) return { error: 'Tool already installed' };
43
+
44
+ return dbInstall({
45
+ catalogId: catalogEntry.id,
46
+ name: catalogEntry.name,
47
+ slug: catalogEntry.id,
48
+ category: catalogEntry.category,
49
+ description: catalogEntry.description,
50
+ dockerImage: catalogEntry.dockerImage || null,
51
+ installCmd: catalogEntry.installCmd || null,
52
+ sourceUrl: catalogEntry.sourceUrl || null,
53
+ });
54
+ }
55
+
56
+ export async function installCustomTool(data) {
57
+ await requireAuth();
58
+ const { installTool: dbInstall, getToolBySlug } = await import('./tools.js');
59
+
60
+ if (!data.name || !data.slug || !data.category) return { error: 'Name, slug, and category are required' };
61
+
62
+ const existing = getToolBySlug(data.slug);
63
+ if (existing) return { error: 'Tool with this slug already exists' };
64
+
65
+ return dbInstall(data);
66
+ }
67
+
68
+ export async function uninstallTool(id) {
69
+ await requireAuth();
70
+ const { uninstallTool: dbUninstall } = await import('./tools.js');
71
+ dbUninstall(id);
72
+ return { success: true };
73
+ }
74
+
75
+ export async function toggleTool(id) {
76
+ await requireAuth();
77
+ const { toggleTool: dbToggle } = await import('./tools.js');
78
+ const enabled = dbToggle(id);
79
+ return { success: true, enabled };
80
+ }
81
+
82
+ // ── Docker Containers ────────────────────────────────────────────────────────
83
+
84
+ export async function getContainers() {
85
+ await requireAuth();
86
+ const { getContainers: dbGet } = await import('./tools.js');
87
+ return dbGet();
88
+ }
89
+
90
+ export async function spawnContainer(toolId, options = {}) {
91
+ await requireAuth();
92
+ const { getToolById, createContainer } = await import('./tools.js');
93
+
94
+ const tool = getToolById(toolId);
95
+ if (!tool) return { error: 'Tool not found' };
96
+ if (!tool.dockerImage) return { error: 'Tool has no Docker image configured' };
97
+
98
+ // Create container record with pending status — actual Docker spawn is triggered
99
+ // by the agent/job system when it picks up the pending container
100
+ const container = createContainer({
101
+ toolId,
102
+ containerId: `pending-${Date.now()}`,
103
+ imageName: tool.dockerImage,
104
+ agentId: options.agentId || null,
105
+ ports: options.ports || null,
106
+ env: options.env || null,
107
+ });
108
+
109
+ return { success: true, container };
110
+ }
111
+
112
+ export async function stopContainer(id) {
113
+ await requireAuth();
114
+ const { stopContainer: dbStop } = await import('./tools.js');
115
+ dbStop(id);
116
+ return { success: true };
117
+ }
118
+
119
+ // ── GitHub Tool Installer ────────────────────────────────────────────────────
120
+
121
+ export async function installFromGithub(githubUrl) {
122
+ await requireAuth();
123
+
124
+ // Validate and parse GitHub URL strictly
125
+ let parsedUrl;
126
+ try {
127
+ parsedUrl = new URL(githubUrl);
128
+ if (parsedUrl.hostname !== 'github.com') return { error: 'Only github.com URLs are supported' };
129
+ } catch {
130
+ return { error: 'Invalid URL format' };
131
+ }
132
+
133
+ const parts = parsedUrl.pathname.split('/').filter(Boolean);
134
+ if (parts.length < 2) return { error: 'Invalid GitHub URL — expected github.com/owner/repo' };
135
+
136
+ const [owner, repo] = parts;
137
+ const cleanOwner = owner.replace(/[^a-zA-Z0-9_.-]/g, '');
138
+ const cleanRepo = repo.replace(/[^a-zA-Z0-9_.-]/g, '').replace(/\.git$/, '');
139
+ if (!cleanOwner || !cleanRepo) return { error: 'Invalid owner or repo name' };
140
+ const slug = cleanRepo.toLowerCase().replace(/[^a-z0-9-]/g, '-');
141
+ const safeUrl = `https://github.com/${cleanOwner}/${cleanRepo}`;
142
+
143
+ const { getToolBySlug, installTool: dbInstall } = await import('./tools.js');
144
+ const existing = getToolBySlug(slug);
145
+ if (existing) return { error: 'Tool already installed' };
146
+
147
+ // Fetch repo metadata from GitHub API
148
+ let repoData = {};
149
+ try {
150
+ const res = await fetch(`https://api.github.com/repos/${cleanOwner}/${cleanRepo}`, {
151
+ headers: process.env.GH_TOKEN ? { Authorization: `Bearer ${process.env.GH_TOKEN}` } : {},
152
+ });
153
+ if (res.ok) repoData = await res.json();
154
+ } catch {}
155
+
156
+ return dbInstall({
157
+ name: repoData.name || cleanRepo,
158
+ slug,
159
+ category: 'custom',
160
+ description: repoData.description || `Installed from ${safeUrl}`,
161
+ sourceUrl: safeUrl,
162
+ installCmd: `git clone ${safeUrl}`,
163
+ });
164
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Built-in tool catalog — security tools available for installation.
3
+ * Sourced from Harbinger HexStrike, RedTeam, and PentAGI MCP servers.
4
+ * Each tool has metadata for display, Docker image for execution, and GitHub source for installation.
5
+ */
6
+
7
+ export const TOOL_CATEGORIES = [
8
+ { id: 'recon', name: 'Recon & Subdomain', color: 'blue', description: 'Asset discovery, subdomain enumeration, DNS resolution' },
9
+ { id: 'scanning', name: 'Network Scanning', color: 'cyan', description: 'Port scanning, service detection, vulnerability scanning' },
10
+ { id: 'web', name: 'Web Application', color: 'orange', description: 'Web fuzzing, SQL injection, XSS, directory brute-forcing' },
11
+ { id: 'osint', name: 'OSINT', color: 'green', description: 'Email harvesting, social recon, breach data, Shodan' },
12
+ { id: 'cloud', name: 'Cloud Security', color: 'purple', description: 'AWS/GCP/Azure auditing, container scanning, IaC analysis' },
13
+ { id: 'credential', name: 'Credential Testing', color: 'red', description: 'Password cracking, brute-forcing, Kerberos attacks' },
14
+ { id: 'exploitation', name: 'Exploitation', color: 'red', description: 'Metasploit, Impacket, payload generation' },
15
+ { id: 'binary', name: 'Binary Analysis', color: 'gray', description: 'Reverse engineering, firmware analysis, debugging' },
16
+ { id: 'forensics', name: 'Forensics & CTF', color: 'yellow', description: 'Memory forensics, file carving, steganography' },
17
+ { id: 'automation', name: 'Automation & Pipelines', color: 'indigo', description: 'Recon pipelines, workflow tools, orchestration' },
18
+ ];
19
+
20
+ export const TOOL_CATALOG = [
21
+ // ── Recon & Subdomain ──────────────────────────────────────────────────────
22
+ { id: 'subfinder', name: 'Subfinder', category: 'recon', description: 'Fast passive subdomain enumeration using multiple sources', dockerImage: 'projectdiscovery/subfinder:latest', installCmd: 'go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest', sourceUrl: 'https://github.com/projectdiscovery/subfinder' },
23
+ { id: 'amass', name: 'Amass', category: 'recon', description: 'In-depth DNS enumeration and network mapping with OWASP backing', dockerImage: 'caffix/amass:latest', installCmd: 'go install -v github.com/owasp-amass/amass/v4/...@master', sourceUrl: 'https://github.com/owasp-amass/amass' },
24
+ { id: 'dnsx', name: 'dnsx', category: 'recon', description: 'Fast DNS resolution and brute-forcing toolkit', dockerImage: 'projectdiscovery/dnsx:latest', installCmd: 'go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest', sourceUrl: 'https://github.com/projectdiscovery/dnsx' },
25
+ { id: 'httpx', name: 'httpx', category: 'recon', description: 'HTTP probing — status codes, titles, tech detection, content-length', dockerImage: 'projectdiscovery/httpx:latest', installCmd: 'go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest', sourceUrl: 'https://github.com/projectdiscovery/httpx' },
26
+ { id: 'katana', name: 'Katana', category: 'recon', description: 'Next-gen web crawling framework for endpoint and parameter discovery', dockerImage: 'projectdiscovery/katana:latest', installCmd: 'go install github.com/projectdiscovery/katana/cmd/katana@latest', sourceUrl: 'https://github.com/projectdiscovery/katana' },
27
+ { id: 'waybackurls', name: 'Waybackurls', category: 'recon', description: 'Fetch all URLs from Wayback Machine for a domain', installCmd: 'go install github.com/tomnomnom/waybackurls@latest', sourceUrl: 'https://github.com/tomnomnom/waybackurls' },
28
+ { id: 'gau', name: 'GAU', category: 'recon', description: 'Get All URLs from AlienVault OTX, Wayback Machine, Common Crawl, URLScan', installCmd: 'go install github.com/lc/gau/v2/cmd/gau@latest', sourceUrl: 'https://github.com/lc/gau' },
29
+ { id: 'hakrawler', name: 'Hakrawler', category: 'recon', description: 'Web crawler for discovering links, endpoints, and forms', installCmd: 'go install github.com/hakluke/hakrawler@latest', sourceUrl: 'https://github.com/hakluke/hakrawler' },
30
+ { id: 'naabu', name: 'Naabu', category: 'recon', description: 'Fast port scanner with SYN/CONNECT scanning and Nmap integration', dockerImage: 'projectdiscovery/naabu:latest', installCmd: 'go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest', sourceUrl: 'https://github.com/projectdiscovery/naabu' },
31
+ { id: 'uncover', name: 'Uncover', category: 'recon', description: 'Quickly discover exposed hosts using Shodan, Censys, Fofa, Hunter', dockerImage: 'projectdiscovery/uncover:latest', installCmd: 'go install -v github.com/projectdiscovery/uncover/cmd/uncover@latest', sourceUrl: 'https://github.com/projectdiscovery/uncover' },
32
+
33
+ // ── Network Scanning ───────────────────────────────────────────────────────
34
+ { id: 'nmap', name: 'Nmap', category: 'scanning', description: 'Port scanning, service/OS fingerprinting, NSE scripts', dockerImage: 'instrumentisto/nmap:latest', installCmd: 'apt install -y nmap', sourceUrl: 'https://github.com/nmap/nmap' },
35
+ { id: 'masscan', name: 'Masscan', category: 'scanning', description: 'Internet-scale port scanner — 10M packets/sec', installCmd: 'apt install -y masscan', sourceUrl: 'https://github.com/robertdavidgraham/masscan' },
36
+ { id: 'rustscan', name: 'RustScan', category: 'scanning', description: 'Ultra-fast port scanner written in Rust, pipes to Nmap', dockerImage: 'rustscan/rustscan:latest', installCmd: 'cargo install rustscan', sourceUrl: 'https://github.com/RustScan/RustScan' },
37
+ { id: 'nuclei', name: 'Nuclei', category: 'scanning', description: 'Template-based vulnerability scanner with 8000+ community templates', dockerImage: 'projectdiscovery/nuclei:latest', installCmd: 'go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest', sourceUrl: 'https://github.com/projectdiscovery/nuclei' },
38
+ { id: 'nikto', name: 'Nikto', category: 'scanning', description: 'Web server vulnerability scanner — misconfigs, dangerous files, outdated software', installCmd: 'apt install -y nikto', sourceUrl: 'https://github.com/sullo/nikto' },
39
+ { id: 'testssl', name: 'testssl.sh', category: 'scanning', description: 'TLS/SSL cipher and protocol testing from the command line', installCmd: 'git clone --depth 1 https://github.com/drwetter/testssl.sh.git', sourceUrl: 'https://github.com/drwetter/testssl.sh' },
40
+
41
+ // ── Web Application ────────────────────────────────────────────────────────
42
+ { id: 'ffuf', name: 'FFuf', category: 'web', description: 'Fast web fuzzer for directories, vhosts, parameters, headers', dockerImage: 'ffuf/ffuf:latest', installCmd: 'go install github.com/ffuf/ffuf/v2@latest', sourceUrl: 'https://github.com/ffuf/ffuf' },
43
+ { id: 'gobuster', name: 'Gobuster', category: 'web', description: 'Directory/file/DNS/vhost brute-forcer written in Go', installCmd: 'go install github.com/OJ/gobuster/v3@latest', sourceUrl: 'https://github.com/OJ/gobuster' },
44
+ { id: 'feroxbuster', name: 'Feroxbuster', category: 'web', description: 'Recursive web content discovery with smart filtering', dockerImage: 'epi052/feroxbuster:latest', installCmd: 'cargo install feroxbuster', sourceUrl: 'https://github.com/epi052/feroxbuster' },
45
+ { id: 'sqlmap', name: 'SQLMap', category: 'web', description: 'Automated SQL injection detection and exploitation', installCmd: 'pip install sqlmap', sourceUrl: 'https://github.com/sqlmapproject/sqlmap' },
46
+ { id: 'dalfox', name: 'DalFox', category: 'web', description: 'XSS scanner and parameter analyzer with DOM-based detection', installCmd: 'go install github.com/hahwul/dalfox/v2@latest', sourceUrl: 'https://github.com/hahwul/dalfox' },
47
+ { id: 'wpscan', name: 'WPScan', category: 'web', description: 'WordPress vulnerability scanner — plugins, themes, users', dockerImage: 'wpscanteam/wpscan:latest', installCmd: 'gem install wpscan', sourceUrl: 'https://github.com/wpscanteam/wpscan' },
48
+ { id: 'arjun', name: 'Arjun', category: 'web', description: 'HTTP parameter discovery via brute-force and heuristics', installCmd: 'pip install arjun', sourceUrl: 'https://github.com/s0md3v/Arjun' },
49
+ { id: 'xsser', name: 'XSSer', category: 'web', description: 'XSS vulnerability detection framework with multiple vectors', installCmd: 'pip install xsser', sourceUrl: 'https://github.com/epsylon/xsser' },
50
+ { id: 'jwt_tool', name: 'jwt_tool', category: 'web', description: 'JWT token testing — alg:none, key confusion, brute-force', installCmd: 'pip install jwt_tool', sourceUrl: 'https://github.com/ticarpi/jwt_tool' },
51
+ { id: 'commix', name: 'Commix', category: 'web', description: 'Automated command injection and exploitation tool', installCmd: 'pip install commix', sourceUrl: 'https://github.com/commixproject/commix' },
52
+ { id: 'ssrfmap', name: 'SSRFMap', category: 'web', description: 'SSRF exploitation and detection framework', installCmd: 'git clone https://github.com/swisskyrepo/SSRFmap.git', sourceUrl: 'https://github.com/swisskyrepo/SSRFmap' },
53
+ { id: 'paramspider', name: 'ParamSpider', category: 'web', description: 'Mine parameters from web archives for all endpoints', installCmd: 'pip install paramspider', sourceUrl: 'https://github.com/devanshbatham/ParamSpider' },
54
+
55
+ // ── OSINT ──────────────────────────────────────────────────────────────────
56
+ { id: 'theharvester', name: 'theHarvester', category: 'osint', description: 'Email, subdomain, and name harvesting from public sources', installCmd: 'pip install theHarvester', sourceUrl: 'https://github.com/laramies/theHarvester' },
57
+ { id: 'shodan', name: 'Shodan CLI', category: 'osint', description: 'Search internet-connected devices, ICS, databases, webcams', installCmd: 'pip install shodan', sourceUrl: 'https://github.com/achillean/shodan-python' },
58
+ { id: 'sherlock', name: 'Sherlock', category: 'osint', description: 'Hunt usernames across 400+ social networks', installCmd: 'pip install sherlock-project', sourceUrl: 'https://github.com/sherlock-project/sherlock' },
59
+ { id: 'spiderfoot', name: 'SpiderFoot', category: 'osint', description: 'OSINT automation — 200+ data sources, correlation engine', dockerImage: 'spiderfoot/spiderfoot:latest', installCmd: 'pip install spiderfoot', sourceUrl: 'https://github.com/smicallef/spiderfoot' },
60
+ { id: 'dnsrecon', name: 'DNSRecon', category: 'osint', description: 'DNS enumeration — zone transfers, brute-force, cache snooping', installCmd: 'pip install dnsrecon', sourceUrl: 'https://github.com/darkoperator/dnsrecon' },
61
+ { id: 'whois', name: 'Whois', category: 'osint', description: 'Domain registration lookup — registrar, nameservers, contacts', installCmd: 'apt install -y whois' },
62
+ { id: 'trufflehog', name: 'TruffleHog', category: 'osint', description: 'Find leaked credentials in Git repos, S3, filesystems', installCmd: 'pip install trufflehog', sourceUrl: 'https://github.com/trufflesecurity/trufflehog' },
63
+ { id: 'gitleaks', name: 'Gitleaks', category: 'osint', description: 'Detect hardcoded secrets in Git repos using regex and entropy', installCmd: 'go install github.com/gitleaks/gitleaks/v8@latest', sourceUrl: 'https://github.com/gitleaks/gitleaks' },
64
+
65
+ // ── Cloud Security ─────────────────────────────────────────────────────────
66
+ { id: 'prowler', name: 'Prowler', category: 'cloud', description: 'AWS/Azure/GCP security assessment and CIS compliance', dockerImage: 'prowlercloud/prowler:latest', installCmd: 'pip install prowler', sourceUrl: 'https://github.com/prowler-cloud/prowler' },
67
+ { id: 'trivy', name: 'Trivy', category: 'cloud', description: 'Container, filesystem, and IaC vulnerability scanner', dockerImage: 'aquasec/trivy:latest', installCmd: 'apt install -y trivy', sourceUrl: 'https://github.com/aquasecurity/trivy' },
68
+ { id: 'checkov', name: 'Checkov', category: 'cloud', description: 'IaC security scanner — Terraform, Kubernetes, ARM, CloudFormation', installCmd: 'pip install checkov', sourceUrl: 'https://github.com/bridgecrewio/checkov' },
69
+ { id: 'scoutsuite', name: 'ScoutSuite', category: 'cloud', description: 'Multi-cloud security auditing for AWS, GCP, Azure, Alibaba, Oracle', installCmd: 'pip install scoutsuite', sourceUrl: 'https://github.com/nccgroup/ScoutSuite' },
70
+ { id: 'docker_bench', name: 'Docker Bench', category: 'cloud', description: 'CIS Docker Community Edition benchmark checks', sourceUrl: 'https://github.com/docker/docker-bench-security', installCmd: 'git clone https://github.com/docker/docker-bench-security.git' },
71
+ { id: 'kube_hunter', name: 'Kube-Hunter', category: 'cloud', description: 'Kubernetes cluster penetration testing from inside or outside', installCmd: 'pip install kube-hunter', sourceUrl: 'https://github.com/aquasecurity/kube-hunter' },
72
+ { id: 's3scanner', name: 'S3Scanner', category: 'cloud', description: 'Scan for misconfigured S3 buckets across AWS accounts', installCmd: 'pip install s3scanner', sourceUrl: 'https://github.com/sa7mon/S3Scanner' },
73
+ { id: 'cloudbrute', name: 'CloudBrute', category: 'cloud', description: 'Enumerate cloud resources across AWS, Azure, GCP', installCmd: 'go install github.com/0xsha/CloudBrute@latest', sourceUrl: 'https://github.com/0xsha/CloudBrute' },
74
+
75
+ // ── Credential Testing ─────────────────────────────────────────────────────
76
+ { id: 'hydra', name: 'Hydra', category: 'credential', description: 'Parallelized login brute-forcer for 50+ protocols', installCmd: 'apt install -y hydra', sourceUrl: 'https://github.com/vanhauser-thc/thc-hydra' },
77
+ { id: 'hashcat', name: 'Hashcat', category: 'credential', description: 'GPU-accelerated hash cracking — 300+ hash types', installCmd: 'apt install -y hashcat', sourceUrl: 'https://github.com/hashcat/hashcat' },
78
+ { id: 'john', name: 'John the Ripper', category: 'credential', description: 'Password cracker supporting many hash formats with wordlist and rules', installCmd: 'apt install -y john', sourceUrl: 'https://github.com/openwall/john' },
79
+ { id: 'kerbrute', name: 'Kerbrute', category: 'credential', description: 'Kerberos pre-auth brute-forcing for AD user enumeration', installCmd: 'go install github.com/ropnop/kerbrute@latest', sourceUrl: 'https://github.com/ropnop/kerbrute' },
80
+
81
+ // ── Exploitation ───────────────────────────────────────────────────────────
82
+ { id: 'metasploit', name: 'Metasploit', category: 'exploitation', description: 'Penetration testing framework — exploit development and execution', dockerImage: 'metasploitframework/metasploit-framework:latest', installCmd: 'curl https://raw.githubusercontent.com/rapid7/metasploit-framework/master/msfinstall | bash', sourceUrl: 'https://github.com/rapid7/metasploit-framework' },
83
+ { id: 'impacket', name: 'Impacket', category: 'exploitation', description: 'Windows protocol attack suite — psexec, secretsdump, ntlmrelayx', installCmd: 'pip install impacket', sourceUrl: 'https://github.com/fortra/impacket' },
84
+ { id: 'pwntools', name: 'pwntools', category: 'exploitation', description: 'CTF and exploit development library for Python', installCmd: 'pip install pwntools', sourceUrl: 'https://github.com/Gallopsled/pwntools' },
85
+
86
+ // ── Binary Analysis ────────────────────────────────────────────────────────
87
+ { id: 'binwalk', name: 'Binwalk', category: 'binary', description: 'Firmware analysis, extraction, and signature scanning', installCmd: 'pip install binwalk', sourceUrl: 'https://github.com/ReFirmLabs/binwalk' },
88
+ { id: 'checksec', name: 'Checksec', category: 'binary', description: 'Check binary security properties — NX, PIE, RELRO, canary', installCmd: 'apt install -y checksec', sourceUrl: 'https://github.com/slimm609/checksec.sh' },
89
+ { id: 'radare2', name: 'Radare2', category: 'binary', description: 'Reverse engineering framework — disassembly, debugging, analysis', installCmd: 'git clone https://github.com/radareorg/radare2.git && cd radare2 && sys/install.sh', sourceUrl: 'https://github.com/radareorg/radare2' },
90
+ { id: 'ghidra', name: 'Ghidra', category: 'binary', description: 'NSA reverse engineering and decompilation tool', sourceUrl: 'https://github.com/NationalSecurityAgency/ghidra' },
91
+
92
+ // ── Forensics & CTF ────────────────────────────────────────────────────────
93
+ { id: 'volatility', name: 'Volatility3', category: 'forensics', description: 'Memory forensics — process analysis, network connections, malware detection', installCmd: 'pip install volatility3', sourceUrl: 'https://github.com/volatilityfoundation/volatility3' },
94
+ { id: 'foremost', name: 'Foremost', category: 'forensics', description: 'File carving from raw disk images and memory dumps', installCmd: 'apt install -y foremost' },
95
+ { id: 'steghide', name: 'Steghide', category: 'forensics', description: 'Steganography detection and extraction from images and audio', installCmd: 'apt install -y steghide' },
96
+ { id: 'exiftool', name: 'ExifTool', category: 'forensics', description: 'Read, write, and analyze file metadata across all formats', installCmd: 'apt install -y libimage-exiftool-perl', sourceUrl: 'https://github.com/exiftool/exiftool' },
97
+ { id: 'tshark', name: 'Tshark', category: 'forensics', description: 'CLI packet capture and analysis (Wireshark engine)', installCmd: 'apt install -y tshark' },
98
+
99
+ // ── Automation & Pipelines ─────────────────────────────────────────────────
100
+ { id: 'reconftw', name: 'ReconFTW', category: 'automation', description: 'All-in-one recon pipeline: subdomains → ports → vulns → screenshots', dockerImage: 'six2dez/reconftw:latest', installCmd: 'git clone https://github.com/six2dez/reconftw.git && cd reconftw && ./install.sh', sourceUrl: 'https://github.com/six2dez/reconftw' },
101
+ { id: 'axiom', name: 'Axiom', category: 'automation', description: 'Dynamic infrastructure for distributed scanning across cloud VPS', installCmd: 'bash <(curl -s https://raw.githubusercontent.com/pry0cc/axiom/master/interact/axiom-configure)', sourceUrl: 'https://github.com/pry0cc/axiom' },
102
+ { id: 'notify', name: 'Notify', category: 'automation', description: 'Stream tool output to Slack, Discord, Telegram, email', installCmd: 'go install -v github.com/projectdiscovery/notify/cmd/notify@latest', sourceUrl: 'https://github.com/projectdiscovery/notify' },
103
+ { id: 'interactsh', name: 'Interactsh', category: 'automation', description: 'OOB interaction server for detecting blind vulnerabilities (SSRF, XXE, RCE)', dockerImage: 'projectdiscovery/interactsh:latest', installCmd: 'go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest', sourceUrl: 'https://github.com/projectdiscovery/interactsh' },
104
+ { id: 'gowitness', name: 'Gowitness', category: 'automation', description: 'Web screenshot utility for large-scale recon visualization', installCmd: 'go install github.com/sensepost/gowitness@latest', sourceUrl: 'https://github.com/sensepost/gowitness' },
105
+ { id: 'aquatone', name: 'Aquatone', category: 'automation', description: 'Visual recon — HTTP screenshot and report generation for subdomains', installCmd: 'go install github.com/michenriksen/aquatone@latest', sourceUrl: 'https://github.com/michenriksen/aquatone' },
106
+ ];
107
+
108
+ /**
109
+ * Get the full catalog as a lookup map by ID.
110
+ */
111
+ export function getCatalogMap() {
112
+ return Object.fromEntries(TOOL_CATALOG.map(t => [t.id, t]));
113
+ }
114
+
115
+ /**
116
+ * Get tools grouped by category.
117
+ */
118
+ export function getCatalogByCategory() {
119
+ const groups = {};
120
+ for (const tool of TOOL_CATALOG) {
121
+ if (!groups[tool.category]) groups[tool.category] = [];
122
+ groups[tool.category].push(tool);
123
+ }
124
+ return groups;
125
+ }
126
+
127
+ /**
128
+ * Search catalog by name/description.
129
+ */
130
+ export function searchCatalog(query) {
131
+ const q = query.toLowerCase();
132
+ return TOOL_CATALOG.filter(t =>
133
+ t.name.toLowerCase().includes(q) ||
134
+ t.description.toLowerCase().includes(q) ||
135
+ t.category.toLowerCase().includes(q)
136
+ );
137
+ }
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { eq, desc } from 'drizzle-orm';
3
+ import { getDb } from '../db/index.js';
4
+ import { tools, dockerContainers } from '../db/schema.js';
5
+
6
+ export function installTool({ catalogId, name, slug, category, description, dockerImage, installCmd, sourceUrl, version }) {
7
+ const db = getDb();
8
+ const now = Date.now();
9
+ const id = randomUUID();
10
+ db.insert(tools).values({ id, catalogId, name, slug, category, description, dockerImage, installCmd, sourceUrl, version, installed: 1, enabled: 1, createdAt: now, updatedAt: now }).run();
11
+ return { id, name, slug };
12
+ }
13
+
14
+ export function getInstalledTools() {
15
+ const db = getDb();
16
+ return db.select().from(tools).orderBy(tools.category).all();
17
+ }
18
+
19
+ export function getToolBySlug(slug) {
20
+ const db = getDb();
21
+ return db.select().from(tools).where(eq(tools.slug, slug)).get();
22
+ }
23
+
24
+ export function getToolById(id) {
25
+ const db = getDb();
26
+ return db.select().from(tools).where(eq(tools.id, id)).get();
27
+ }
28
+
29
+ export function updateTool(id, data) {
30
+ const db = getDb();
31
+ db.update(tools).set({ ...data, updatedAt: Date.now() }).where(eq(tools.id, id)).run();
32
+ }
33
+
34
+ export function uninstallTool(id) {
35
+ const db = getDb();
36
+ db.delete(tools).where(eq(tools.id, id)).run();
37
+ }
38
+
39
+ export function toggleTool(id) {
40
+ const db = getDb();
41
+ const tool = db.select().from(tools).where(eq(tools.id, id)).get();
42
+ if (!tool) return null;
43
+ const enabled = tool.enabled ? 0 : 1;
44
+ db.update(tools).set({ enabled, updatedAt: Date.now() }).where(eq(tools.id, id)).run();
45
+ return enabled;
46
+ }
47
+
48
+ // ── Docker Containers ──────────────────────────────────────────────────────
49
+
50
+ export function createContainer({ toolId, containerId, imageName, agentId, ports, env }) {
51
+ const db = getDb();
52
+ const now = Date.now();
53
+ const id = randomUUID();
54
+ db.insert(dockerContainers).values({ id, toolId, containerId, imageName, status: 'pending', agentId, ports: ports ? JSON.stringify(ports) : null, env: env ? JSON.stringify(env) : null, createdAt: now }).run();
55
+ return { id, containerId };
56
+ }
57
+
58
+ export function getContainers() {
59
+ const db = getDb();
60
+ return db.select().from(dockerContainers).orderBy(desc(dockerContainers.createdAt)).all();
61
+ }
62
+
63
+ export function updateContainer(id, data) {
64
+ const db = getDb();
65
+ db.update(dockerContainers).set(data).where(eq(dockerContainers.id, id)).run();
66
+ }
67
+
68
+ export function stopContainer(id) {
69
+ const db = getDb();
70
+ db.update(dockerContainers).set({ status: 'stopped', stoppedAt: Date.now() }).where(eq(dockerContainers.id, id)).run();
71
+ }
@@ -0,0 +1,99 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { z } from 'zod';
3
+ import { githubApi } from './github.js';
4
+ import { createModel } from '../ai/model.js';
5
+
6
+ /**
7
+ * Generate a short descriptive title for a job using the LLM.
8
+ * Uses structured output to avoid thinking-token leaks with extended-thinking models.
9
+ * @param {string} jobDescription - The full job description
10
+ * @returns {Promise<string>} ~10 word title
11
+ */
12
+ async function generateJobTitle(jobDescription) {
13
+ try {
14
+ const model = await createModel({ maxTokens: 100 });
15
+ const response = await model.withStructuredOutput(z.object({ title: z.string() })).invoke([
16
+ ['system', 'Generate a descriptive ~10 word title for this agent job. The title should clearly describe what the job will do.'],
17
+ ['human', jobDescription],
18
+ ]);
19
+ return response.title.trim() || jobDescription.slice(0, 80);
20
+ } catch {
21
+ // Fallback: first line, truncated
22
+ const firstLine = jobDescription.split('\n').find(l => l.trim()) || jobDescription;
23
+ return firstLine.replace(/^#+\s*/, '').trim().split(/\s+/).slice(0, 10).join(' ');
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Create a new job branch with job.config.json
29
+ * @param {string} jobDescription - The job description
30
+ * @param {Object} [options] - Optional overrides
31
+ * @param {string} [options.llmProvider] - LLM provider override (e.g. 'openai', 'anthropic')
32
+ * @param {string} [options.llmModel] - LLM model override (e.g. 'gpt-4o', 'claude-sonnet-4-5-20250929')
33
+ * @param {string} [options.agentBackend] - Agent backend override ('pi' or 'claude-code')
34
+ * @returns {Promise<{job_id: string, branch: string, title: string}>} - Job ID, branch name, and title
35
+ */
36
+ async function createJob(jobDescription, options = {}) {
37
+ if (!jobDescription?.trim()) throw new Error('Job description cannot be empty');
38
+ const { GH_OWNER, GH_REPO } = process.env;
39
+ if (!GH_OWNER || !GH_REPO) throw new Error('GH_OWNER and GH_REPO environment variables are required');
40
+ const jobId = randomUUID();
41
+ const branch = `job/${jobId}`;
42
+ const repo = `/repos/${GH_OWNER}/${GH_REPO}`;
43
+
44
+ // Generate a short descriptive title
45
+ const title = await generateJobTitle(jobDescription);
46
+
47
+ // 1. Get main branch SHA and its tree SHA
48
+ const mainRef = await githubApi(`${repo}/git/ref/heads/main`);
49
+ const mainSha = mainRef.object.sha;
50
+ const mainCommit = await githubApi(`${repo}/git/commits/${mainSha}`);
51
+ const baseTreeSha = mainCommit.tree.sha;
52
+
53
+ // 2. Build job.config.json — single source of truth for job metadata
54
+ const config = { title, job: jobDescription };
55
+ if (options.llmProvider) config.llm_provider = options.llmProvider;
56
+ if (options.llmModel) config.llm_model = options.llmModel;
57
+ if (options.agentBackend) config.agent_backend = options.agentBackend;
58
+
59
+ const treeEntries = [
60
+ {
61
+ path: `logs/${jobId}/job.config.json`,
62
+ mode: '100644',
63
+ type: 'blob',
64
+ content: JSON.stringify(config, null, 2),
65
+ },
66
+ ];
67
+
68
+ // 3. Create tree (base_tree preserves all existing files)
69
+ const tree = await githubApi(`${repo}/git/trees`, {
70
+ method: 'POST',
71
+ body: JSON.stringify({
72
+ base_tree: baseTreeSha,
73
+ tree: treeEntries,
74
+ }),
75
+ });
76
+
77
+ // 4. Create a single commit with job config
78
+ const commit = await githubApi(`${repo}/git/commits`, {
79
+ method: 'POST',
80
+ body: JSON.stringify({
81
+ message: `🤖 Agent Job: ${title}`,
82
+ tree: tree.sha,
83
+ parents: [mainSha],
84
+ }),
85
+ });
86
+
87
+ // 5. Create branch pointing to the commit (triggers run-job.yml)
88
+ await githubApi(`${repo}/git/refs`, {
89
+ method: 'POST',
90
+ body: JSON.stringify({
91
+ ref: `refs/heads/${branch}`,
92
+ sha: commit.sha,
93
+ }),
94
+ });
95
+
96
+ return { job_id: jobId, branch, title };
97
+ }
98
+
99
+ export { createJob };