@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.
- package/LICENSE +21 -0
- package/README.md +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
// ── Programs ─────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export async function getPrograms() {
|
|
14
|
+
await requireAuth();
|
|
15
|
+
const { getPrograms: dbGet } = await import('./programs.js');
|
|
16
|
+
return dbGet();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function createProgram(data) {
|
|
20
|
+
await requireAuth();
|
|
21
|
+
const { createProgram: dbCreate } = await import('./programs.js');
|
|
22
|
+
return dbCreate(data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function updateProgram(id, data) {
|
|
26
|
+
await requireAuth();
|
|
27
|
+
const { updateProgram: dbUpdate } = await import('./programs.js');
|
|
28
|
+
dbUpdate(id, data);
|
|
29
|
+
return { success: true };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function deleteProgram(id) {
|
|
33
|
+
await requireAuth();
|
|
34
|
+
const { deleteProgram: dbDelete } = await import('./programs.js');
|
|
35
|
+
dbDelete(id);
|
|
36
|
+
return { success: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Targets ──────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export async function getTargets(programId) {
|
|
42
|
+
await requireAuth();
|
|
43
|
+
const { getTargets: dbGet } = await import('./targets.js');
|
|
44
|
+
return dbGet(programId);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function createTarget(data) {
|
|
48
|
+
await requireAuth();
|
|
49
|
+
const { createTarget: dbCreate } = await import('./targets.js');
|
|
50
|
+
return dbCreate(data);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function updateTarget(id, data) {
|
|
54
|
+
await requireAuth();
|
|
55
|
+
const { updateTarget: dbUpdate } = await import('./targets.js');
|
|
56
|
+
dbUpdate(id, data);
|
|
57
|
+
return { success: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function deleteTarget(id) {
|
|
61
|
+
await requireAuth();
|
|
62
|
+
const { deleteTarget: dbDelete } = await import('./targets.js');
|
|
63
|
+
dbDelete(id);
|
|
64
|
+
return { success: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Findings ─────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export async function getFindings(filters) {
|
|
70
|
+
await requireAuth();
|
|
71
|
+
const { getFindings: dbGet } = await import('./findings.js');
|
|
72
|
+
return dbGet(filters);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function createFinding(data) {
|
|
76
|
+
await requireAuth();
|
|
77
|
+
const { createFinding: dbCreate } = await import('./findings.js');
|
|
78
|
+
return dbCreate(data);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function updateFinding(id, data) {
|
|
82
|
+
await requireAuth();
|
|
83
|
+
const { updateFinding: dbUpdate } = await import('./findings.js');
|
|
84
|
+
dbUpdate(id, data);
|
|
85
|
+
return { success: true };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function deleteFinding(id) {
|
|
89
|
+
await requireAuth();
|
|
90
|
+
const { deleteFinding: dbDelete } = await import('./findings.js');
|
|
91
|
+
dbDelete(id);
|
|
92
|
+
return { success: true };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function getFindingCounts() {
|
|
96
|
+
await requireAuth();
|
|
97
|
+
const { getFindingCounts: dbCounts } = await import('./findings.js');
|
|
98
|
+
return dbCounts();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Target Sync ─────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
export async function syncTargetsFromPlatform(platform, options = {}) {
|
|
104
|
+
await requireAuth();
|
|
105
|
+
const { syncPlatform } = await import('./sync-targets.js');
|
|
106
|
+
return syncPlatform(platform, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function syncAllTargets(options = {}) {
|
|
110
|
+
await requireAuth();
|
|
111
|
+
const { syncAllPlatforms } = await import('./sync-targets.js');
|
|
112
|
+
return syncAllPlatforms(options);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function getSyncStatus() {
|
|
116
|
+
await requireAuth();
|
|
117
|
+
const { getSyncStatus: getStatus } = await import('./sync-targets.js');
|
|
118
|
+
return getStatus();
|
|
119
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, and, desc, sql } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from '../db/index.js';
|
|
4
|
+
import { findings } from '../db/schema.js';
|
|
5
|
+
|
|
6
|
+
export function createFinding({ targetId, title, severity = 'info', type, description, stepsToReproduce, impact, agentId, toolId, rawOutput }) {
|
|
7
|
+
const db = getDb();
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const id = randomUUID();
|
|
10
|
+
db.insert(findings).values({ id, targetId, title, severity, type, status: 'new', description, stepsToReproduce, impact, agentId, toolId, rawOutput, createdAt: now, updatedAt: now }).run();
|
|
11
|
+
return { id, title, severity };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getFindings(filters = {}) {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
const conditions = [];
|
|
17
|
+
if (filters.targetId) conditions.push(eq(findings.targetId, filters.targetId));
|
|
18
|
+
if (filters.severity) conditions.push(eq(findings.severity, filters.severity));
|
|
19
|
+
if (filters.status) conditions.push(eq(findings.status, filters.status));
|
|
20
|
+
let query = db.select().from(findings);
|
|
21
|
+
if (conditions.length > 0) query = query.where(and(...conditions));
|
|
22
|
+
return query.orderBy(desc(findings.createdAt)).all();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getFindingById(id) {
|
|
26
|
+
const db = getDb();
|
|
27
|
+
return db.select().from(findings).where(eq(findings.id, id)).get();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function updateFinding(id, data) {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
db.update(findings).set({ ...data, updatedAt: Date.now() }).where(eq(findings.id, id)).run();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function deleteFinding(id) {
|
|
36
|
+
const db = getDb();
|
|
37
|
+
db.delete(findings).where(eq(findings.id, id)).run();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getFindingCounts() {
|
|
41
|
+
const db = getDb();
|
|
42
|
+
const [row] = db.select({
|
|
43
|
+
total: sql`count(*)`,
|
|
44
|
+
critical: sql`sum(case when severity='critical' then 1 else 0 end)`,
|
|
45
|
+
high: sql`sum(case when severity='high' then 1 else 0 end)`,
|
|
46
|
+
medium: sql`sum(case when severity='medium' then 1 else 0 end)`,
|
|
47
|
+
low: sql`sum(case when severity='low' then 1 else 0 end)`,
|
|
48
|
+
info: sql`sum(case when severity='info' then 1 else 0 end)`,
|
|
49
|
+
confirmed: sql`sum(case when status='confirmed' then 1 else 0 end)`,
|
|
50
|
+
reported: sql`sum(case when status='reported' then 1 else 0 end)`,
|
|
51
|
+
totalBounty: sql`coalesce(sum(bounty_amount), 0)`,
|
|
52
|
+
}).from(findings).all();
|
|
53
|
+
return {
|
|
54
|
+
total: Number(row.total) || 0,
|
|
55
|
+
critical: Number(row.critical) || 0,
|
|
56
|
+
high: Number(row.high) || 0,
|
|
57
|
+
medium: Number(row.medium) || 0,
|
|
58
|
+
low: Number(row.low) || 0,
|
|
59
|
+
info: Number(row.info) || 0,
|
|
60
|
+
confirmed: Number(row.confirmed) || 0,
|
|
61
|
+
reported: Number(row.reported) || 0,
|
|
62
|
+
totalBounty: Number(row.totalBounty) || 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, desc } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from '../db/index.js';
|
|
4
|
+
import { programs, targets } from '../db/schema.js';
|
|
5
|
+
|
|
6
|
+
export function createProgram({ name, platform = 'custom', url, scopeUrl, minBounty, maxBounty, notes }) {
|
|
7
|
+
const db = getDb();
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const id = randomUUID();
|
|
10
|
+
db.insert(programs).values({ id, name, platform, url, scopeUrl, minBounty, maxBounty, notes, status: 'active', createdAt: now, updatedAt: now }).run();
|
|
11
|
+
return { id, name, platform };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getPrograms() {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
return db.select().from(programs).orderBy(desc(programs.updatedAt)).all();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getProgramById(id) {
|
|
20
|
+
const db = getDb();
|
|
21
|
+
return db.select().from(programs).where(eq(programs.id, id)).get();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function updateProgram(id, data) {
|
|
25
|
+
const db = getDb();
|
|
26
|
+
db.update(programs).set({ ...data, updatedAt: Date.now() }).where(eq(programs.id, id)).run();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function deleteProgram(id) {
|
|
30
|
+
const db = getDb();
|
|
31
|
+
// Cascade: delete targets belonging to this program first
|
|
32
|
+
db.delete(targets).where(eq(targets.programId, id)).run();
|
|
33
|
+
db.delete(programs).where(eq(programs.id, id)).run();
|
|
34
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, and, isNotNull, sql, inArray } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from '../db/index.js';
|
|
4
|
+
import { programs, targets } from '../db/schema.js';
|
|
5
|
+
|
|
6
|
+
const BASE_URL = 'https://raw.githubusercontent.com/arkadiyt/bounty-targets-data/master/data';
|
|
7
|
+
|
|
8
|
+
const PLATFORM_CONFIGS = {
|
|
9
|
+
hackerone: {
|
|
10
|
+
url: `${BASE_URL}/hackerone_data.json`,
|
|
11
|
+
parseProgramName: (p) => p.name,
|
|
12
|
+
parseProgramHandle: (p) => p.handle,
|
|
13
|
+
parseProgramUrl: (p) => `https://hackerone.com/${p.handle}`,
|
|
14
|
+
parseMaxBounty: (p) => {
|
|
15
|
+
const amounts = (p.targets?.in_scope || []).map(t => t.max_severity_bounty || 0);
|
|
16
|
+
return amounts.length > 0 ? Math.max(...amounts) : null;
|
|
17
|
+
},
|
|
18
|
+
parseTargets: (p) => (p.targets?.in_scope || []).map(t => ({
|
|
19
|
+
type: mapAssetType(t.asset_type),
|
|
20
|
+
value: t.asset_identifier,
|
|
21
|
+
})),
|
|
22
|
+
},
|
|
23
|
+
bugcrowd: {
|
|
24
|
+
url: `${BASE_URL}/bugcrowd_data.json`,
|
|
25
|
+
parseProgramName: (p) => p.name,
|
|
26
|
+
parseProgramHandle: (p) => p.code || slugify(p.name),
|
|
27
|
+
parseProgramUrl: (p) => p.url || `https://bugcrowd.com/${p.code}`,
|
|
28
|
+
parseMaxBounty: (p) => p.max_payout || null,
|
|
29
|
+
parseTargets: (p) => (p.targets?.in_scope || p.target_groups || []).flatMap(g => {
|
|
30
|
+
if (g.targets) return g.targets.map(t => ({ type: mapAssetType(t.type || t.category), value: t.name || t.uri }));
|
|
31
|
+
return [{ type: mapAssetType(g.type || g.category), value: g.name || g.uri }];
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
intigriti: {
|
|
35
|
+
url: `${BASE_URL}/intigriti_data.json`,
|
|
36
|
+
parseProgramName: (p) => p.name,
|
|
37
|
+
parseProgramHandle: (p) => p.company_handle || p.handle || slugify(p.name),
|
|
38
|
+
parseProgramUrl: (p) => p.url || `https://app.intigriti.com/programs/${p.company_handle}`,
|
|
39
|
+
parseMaxBounty: (p) => p.max_bounty || null,
|
|
40
|
+
parseTargets: (p) => (p.targets?.in_scope || []).map(t => ({
|
|
41
|
+
type: mapAssetType(t.type),
|
|
42
|
+
value: t.endpoint || t.description,
|
|
43
|
+
})),
|
|
44
|
+
},
|
|
45
|
+
yeswehack: {
|
|
46
|
+
url: `${BASE_URL}/yeswehack_data.json`,
|
|
47
|
+
parseProgramName: (p) => p.title || p.name,
|
|
48
|
+
parseProgramHandle: (p) => p.slug || slugify(p.title || p.name || ''),
|
|
49
|
+
parseProgramUrl: (p) => p.url || `https://yeswehack.com/programs/${p.slug}`,
|
|
50
|
+
parseMaxBounty: (p) => p.max_bounty || null,
|
|
51
|
+
parseTargets: (p) => (p.targets || p.scopes || []).map(t => ({
|
|
52
|
+
type: mapAssetType(t.type || t.scope_type),
|
|
53
|
+
value: t.target || t.scope,
|
|
54
|
+
})),
|
|
55
|
+
},
|
|
56
|
+
federacy: {
|
|
57
|
+
url: `${BASE_URL}/federacy_data.json`,
|
|
58
|
+
parseProgramName: (p) => p.name,
|
|
59
|
+
parseProgramHandle: (p) => p.slug || slugify(p.name),
|
|
60
|
+
parseProgramUrl: (p) => p.url || `https://federacy.com/programs/${p.slug}`,
|
|
61
|
+
parseMaxBounty: (p) => p.max_bounty || null,
|
|
62
|
+
parseTargets: (p) => (p.targets || p.scope || []).map(t => ({
|
|
63
|
+
type: mapAssetType(typeof t === 'string' ? 'url' : t.type),
|
|
64
|
+
value: typeof t === 'string' ? t : t.target || t.url,
|
|
65
|
+
})),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function slugify(str) {
|
|
70
|
+
return (str || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-|-$/g, '');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Map platform asset type strings to our target types.
|
|
74
|
+
// Uses exact matches for short strings to avoid false positives (e.g. 'ip' matching 'script').
|
|
75
|
+
const ASSET_TYPE_MAP = {
|
|
76
|
+
'url': 'domain', 'website': 'domain', 'domain': 'domain', 'web': 'domain',
|
|
77
|
+
'wildcard': 'wildcard',
|
|
78
|
+
'ip_address': 'ip', 'ip': 'ip',
|
|
79
|
+
'cidr': 'cidr',
|
|
80
|
+
'api': 'api', 'endpoint': 'api',
|
|
81
|
+
'mobile': 'mobile', 'android': 'mobile', 'ios': 'mobile',
|
|
82
|
+
'other': 'domain',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function mapAssetType(raw) {
|
|
86
|
+
if (!raw) return 'domain';
|
|
87
|
+
const lower = ('' + raw).toLowerCase().trim();
|
|
88
|
+
// Exact match first
|
|
89
|
+
if (ASSET_TYPE_MAP[lower]) return ASSET_TYPE_MAP[lower];
|
|
90
|
+
// Substring matches for compound types like "Android App"
|
|
91
|
+
if (lower.includes('wildcard') || lower.includes('*.')) return 'wildcard';
|
|
92
|
+
if (lower.includes('android') || lower.includes('ios') || lower.includes('mobile')) return 'mobile';
|
|
93
|
+
if (lower.includes('cidr')) return 'cidr';
|
|
94
|
+
if (lower.includes('api')) return 'api';
|
|
95
|
+
return 'domain';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function fetchPlatformData(platform) {
|
|
99
|
+
const config = PLATFORM_CONFIGS[platform];
|
|
100
|
+
if (!config) throw new Error(`Unknown platform: ${platform}`);
|
|
101
|
+
|
|
102
|
+
const response = await fetch(config.url);
|
|
103
|
+
if (!response.ok) throw new Error(`Failed to fetch ${platform} data: ${response.status}`);
|
|
104
|
+
return response.json();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Sync targets from a specific platform's bounty-targets-data.
|
|
109
|
+
* Pre-fetches existing programs/targets to avoid N+1 queries.
|
|
110
|
+
*/
|
|
111
|
+
export async function syncPlatform(platform, options = {}) {
|
|
112
|
+
const config = PLATFORM_CONFIGS[platform];
|
|
113
|
+
if (!config) throw new Error(`Unknown platform: ${platform}`);
|
|
114
|
+
|
|
115
|
+
const db = getDb();
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const stats = { programsAdded: 0, programsUpdated: 0, targetsAdded: 0, targetsSkipped: 0, errors: [] };
|
|
118
|
+
const maxPrograms = options.maxPrograms || 0;
|
|
119
|
+
|
|
120
|
+
let data;
|
|
121
|
+
try {
|
|
122
|
+
data = await fetchPlatformData(platform);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
stats.errors.push(`Fetch failed: ${err.message}`);
|
|
125
|
+
return stats;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!Array.isArray(data)) {
|
|
129
|
+
stats.errors.push(`Expected array, got ${typeof data}`);
|
|
130
|
+
return stats;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const programsToSync = maxPrograms > 0 ? data.slice(0, maxPrograms) : data;
|
|
134
|
+
|
|
135
|
+
// Bulk-prefetch existing programs for this platform (avoids N+1)
|
|
136
|
+
const existingPrograms = db.select().from(programs)
|
|
137
|
+
.where(eq(programs.platform, platform)).all();
|
|
138
|
+
const programByHandle = new Map(existingPrograms.map(p => [p.syncHandle, p]));
|
|
139
|
+
|
|
140
|
+
// Bulk-prefetch existing target keys for all programs on this platform
|
|
141
|
+
const programIds = existingPrograms.map(p => p.id);
|
|
142
|
+
const existingTargetRows = programIds.length > 0
|
|
143
|
+
? db.select({ programId: targets.programId, type: targets.type, value: targets.value })
|
|
144
|
+
.from(targets)
|
|
145
|
+
.where(inArray(targets.programId, programIds))
|
|
146
|
+
.all()
|
|
147
|
+
: [];
|
|
148
|
+
const existingTargetKeys = new Set(existingTargetRows.map(t => `${t.programId}:${t.type}:${t.value}`));
|
|
149
|
+
|
|
150
|
+
for (const entry of programsToSync) {
|
|
151
|
+
try {
|
|
152
|
+
const handle = config.parseProgramHandle(entry);
|
|
153
|
+
const name = config.parseProgramName(entry);
|
|
154
|
+
if (!handle || !name) continue;
|
|
155
|
+
|
|
156
|
+
let program = programByHandle.get(handle);
|
|
157
|
+
|
|
158
|
+
if (program) {
|
|
159
|
+
db.update(programs).set({
|
|
160
|
+
name,
|
|
161
|
+
url: config.parseProgramUrl(entry),
|
|
162
|
+
maxBounty: config.parseMaxBounty(entry),
|
|
163
|
+
lastSyncedAt: now,
|
|
164
|
+
updatedAt: now,
|
|
165
|
+
}).where(eq(programs.id, program.id)).run();
|
|
166
|
+
stats.programsUpdated++;
|
|
167
|
+
} else {
|
|
168
|
+
const programId = randomUUID();
|
|
169
|
+
db.insert(programs).values({
|
|
170
|
+
id: programId,
|
|
171
|
+
name,
|
|
172
|
+
platform,
|
|
173
|
+
url: config.parseProgramUrl(entry),
|
|
174
|
+
maxBounty: config.parseMaxBounty(entry),
|
|
175
|
+
syncHandle: handle,
|
|
176
|
+
lastSyncedAt: now,
|
|
177
|
+
status: 'active',
|
|
178
|
+
createdAt: now,
|
|
179
|
+
updatedAt: now,
|
|
180
|
+
}).run();
|
|
181
|
+
program = { id: programId };
|
|
182
|
+
programByHandle.set(handle, program);
|
|
183
|
+
stats.programsAdded++;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Sync targets
|
|
187
|
+
const parsedTargets = config.parseTargets(entry);
|
|
188
|
+
for (const t of parsedTargets) {
|
|
189
|
+
if (!t.value) continue;
|
|
190
|
+
const key = `${program.id}:${t.type}:${t.value}`;
|
|
191
|
+
if (existingTargetKeys.has(key)) {
|
|
192
|
+
stats.targetsSkipped++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
db.insert(targets).values({
|
|
196
|
+
id: randomUUID(),
|
|
197
|
+
programId: program.id,
|
|
198
|
+
type: t.type,
|
|
199
|
+
value: t.value,
|
|
200
|
+
status: 'in_scope',
|
|
201
|
+
syncSource: platform,
|
|
202
|
+
syncProgramHandle: handle,
|
|
203
|
+
createdAt: now,
|
|
204
|
+
updatedAt: now,
|
|
205
|
+
}).run();
|
|
206
|
+
existingTargetKeys.add(key); // prevent duplicates within this sync
|
|
207
|
+
stats.targetsAdded++;
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
stats.errors.push(`${config.parseProgramName(entry) || 'unknown'}: ${err.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return stats;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Sync all platforms in parallel. Returns combined stats per platform.
|
|
219
|
+
*/
|
|
220
|
+
export async function syncAllPlatforms(options = {}) {
|
|
221
|
+
const platforms = options.platforms || Object.keys(PLATFORM_CONFIGS);
|
|
222
|
+
const entries = await Promise.allSettled(
|
|
223
|
+
platforms.map(p => syncPlatform(p, options).then(stats => [p, stats]))
|
|
224
|
+
);
|
|
225
|
+
const results = {};
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
if (entry.status === 'fulfilled') {
|
|
228
|
+
const [p, stats] = entry.value;
|
|
229
|
+
results[p] = stats;
|
|
230
|
+
} else {
|
|
231
|
+
// Extract platform name from error if possible
|
|
232
|
+
results[entry.reason?.message || 'unknown'] = { programsAdded: 0, programsUpdated: 0, targetsAdded: 0, targetsSkipped: 0, errors: [entry.reason?.message || 'Unknown error'] };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get sync status summary using SQL aggregation.
|
|
240
|
+
*/
|
|
241
|
+
export function getSyncStatus() {
|
|
242
|
+
const db = getDb();
|
|
243
|
+
const rows = db.select({
|
|
244
|
+
platform: programs.platform,
|
|
245
|
+
count: sql`count(*)`,
|
|
246
|
+
lastSync: sql`max(last_synced_at)`,
|
|
247
|
+
}).from(programs)
|
|
248
|
+
.where(isNotNull(programs.syncHandle))
|
|
249
|
+
.groupBy(programs.platform)
|
|
250
|
+
.all();
|
|
251
|
+
|
|
252
|
+
const platformCounts = {};
|
|
253
|
+
let totalSyncedPrograms = 0;
|
|
254
|
+
let lastSyncedAt = 0;
|
|
255
|
+
for (const r of rows) {
|
|
256
|
+
platformCounts[r.platform] = Number(r.count);
|
|
257
|
+
totalSyncedPrograms += Number(r.count);
|
|
258
|
+
lastSyncedAt = Math.max(lastSyncedAt, Number(r.lastSync) || 0);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
totalSyncedPrograms,
|
|
263
|
+
platformCounts,
|
|
264
|
+
lastSyncedAt: lastSyncedAt || null,
|
|
265
|
+
availablePlatforms: Object.keys(PLATFORM_CONFIGS),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, desc } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from '../db/index.js';
|
|
4
|
+
import { targets } from '../db/schema.js';
|
|
5
|
+
|
|
6
|
+
export function createTarget({ programId, type = 'domain', value, status = 'in_scope', notes }) {
|
|
7
|
+
const db = getDb();
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const id = randomUUID();
|
|
10
|
+
db.insert(targets).values({ id, programId, type, value, status, notes, createdAt: now, updatedAt: now }).run();
|
|
11
|
+
return { id, value, type };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getTargets(programId) {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
if (programId) return db.select().from(targets).where(eq(targets.programId, programId)).orderBy(desc(targets.updatedAt)).all();
|
|
17
|
+
return db.select().from(targets).orderBy(desc(targets.updatedAt)).all();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTargetById(id) {
|
|
21
|
+
const db = getDb();
|
|
22
|
+
return db.select().from(targets).where(eq(targets.id, id)).get();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function updateTarget(id, data) {
|
|
26
|
+
const db = getDb();
|
|
27
|
+
db.update(targets).set({ ...data, updatedAt: Date.now() }).where(eq(targets.id, id)).run();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function deleteTarget(id) {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
db.delete(targets).where(eq(targets.id, id)).run();
|
|
33
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base channel adapter interface.
|
|
3
|
+
* Every chat channel (Telegram, Slack, web, etc.) implements this contract.
|
|
4
|
+
*/
|
|
5
|
+
class ChannelAdapter {
|
|
6
|
+
/**
|
|
7
|
+
* Handle an incoming webhook request from this channel.
|
|
8
|
+
* Returns normalized message data or null if no action needed.
|
|
9
|
+
*
|
|
10
|
+
* @param {Request} request - Incoming HTTP request
|
|
11
|
+
* @returns {Promise<{ threadId: string, text: string, attachments: Array, metadata: object } | null>}
|
|
12
|
+
*
|
|
13
|
+
* Attachments array (may be empty) — only non-text content that the LLM needs to see:
|
|
14
|
+
* { category: "image", mimeType: "image/png", data: Buffer } — send to LLM as vision
|
|
15
|
+
* { category: "document", mimeType: "application/pdf", data: Buffer } — future: extract/attach
|
|
16
|
+
*
|
|
17
|
+
* The adapter downloads authenticated files and normalizes them.
|
|
18
|
+
* Voice/audio messages are fully resolved by the adapter — transcribed to text
|
|
19
|
+
* and included in the `text` field. They are NOT passed as attachments.
|
|
20
|
+
*/
|
|
21
|
+
async receive(request) {
|
|
22
|
+
throw new Error('Not implemented');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Called when message is received — adapter shows acknowledgment.
|
|
27
|
+
* Telegram: thumbs up reaction. Slack: emoji reaction. Web: no-op.
|
|
28
|
+
*/
|
|
29
|
+
async acknowledge(metadata) {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Called while AI is processing — adapter shows activity.
|
|
33
|
+
* Telegram: typing indicator. Slack: typing indicator. Web: no-op (streaming handles this).
|
|
34
|
+
* Returns a stop function.
|
|
35
|
+
*/
|
|
36
|
+
startProcessingIndicator(metadata) {
|
|
37
|
+
return () => {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Send a complete (non-streaming) response back to the channel.
|
|
42
|
+
*/
|
|
43
|
+
async sendResponse(threadId, text, metadata) {
|
|
44
|
+
throw new Error('Not implemented');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether this channel supports real streaming (e.g., web chat via Vercel AI SDK).
|
|
49
|
+
* If true, the AI layer provides a stream instead of a complete response.
|
|
50
|
+
*/
|
|
51
|
+
get supportsStreaming() {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { ChannelAdapter };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TelegramAdapter } from './telegram.js';
|
|
2
|
+
|
|
3
|
+
let _telegramAdapter = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the Telegram channel adapter (lazy singleton).
|
|
7
|
+
* @param {string} botToken - Telegram bot token
|
|
8
|
+
* @returns {TelegramAdapter}
|
|
9
|
+
*/
|
|
10
|
+
export function getTelegramAdapter(botToken) {
|
|
11
|
+
if (!_telegramAdapter || _telegramAdapter.botToken !== botToken) {
|
|
12
|
+
_telegramAdapter = new TelegramAdapter(botToken);
|
|
13
|
+
}
|
|
14
|
+
return _telegramAdapter;
|
|
15
|
+
}
|