@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,163 @@
|
|
|
1
|
+
import { randomUUID, randomBytes, createHash, timingSafeEqual } from 'crypto';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from './index.js';
|
|
4
|
+
import { settings } from './schema.js';
|
|
5
|
+
|
|
6
|
+
const KEY_PREFIX = 'tpb_';
|
|
7
|
+
|
|
8
|
+
// In-memory cache: { key_hash, id } or null
|
|
9
|
+
let _cache = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate a new API key: tpb_ + 64 hex chars (32 random bytes).
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function generateApiKey() {
|
|
16
|
+
return KEY_PREFIX + randomBytes(32).toString('hex');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hash an API key using SHA-256.
|
|
21
|
+
* @param {string} key - Raw API key
|
|
22
|
+
* @returns {string} Hex digest
|
|
23
|
+
*/
|
|
24
|
+
export function hashApiKey(key) {
|
|
25
|
+
return createHash('sha256').update(key).digest('hex');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lazy-load the API key hash into the in-memory cache.
|
|
30
|
+
*/
|
|
31
|
+
function _ensureCache() {
|
|
32
|
+
if (_cache !== null) return _cache;
|
|
33
|
+
|
|
34
|
+
const db = getDb();
|
|
35
|
+
const row = db
|
|
36
|
+
.select()
|
|
37
|
+
.from(settings)
|
|
38
|
+
.where(eq(settings.type, 'api_key'))
|
|
39
|
+
.get();
|
|
40
|
+
|
|
41
|
+
if (row) {
|
|
42
|
+
const parsed = JSON.parse(row.value);
|
|
43
|
+
_cache = { keyHash: parsed.key_hash, id: row.id };
|
|
44
|
+
} else {
|
|
45
|
+
_cache = false; // no key exists — distinguish from "not loaded yet"
|
|
46
|
+
}
|
|
47
|
+
return _cache;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Clear the in-memory cache (call after create/delete).
|
|
52
|
+
*/
|
|
53
|
+
export function invalidateApiKeyCache() {
|
|
54
|
+
_cache = null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create (or replace) the API key. Deletes any existing key first.
|
|
59
|
+
* @param {string} createdBy - User ID
|
|
60
|
+
* @returns {{ key: string, record: object }}
|
|
61
|
+
*/
|
|
62
|
+
export function createApiKeyRecord(createdBy) {
|
|
63
|
+
const db = getDb();
|
|
64
|
+
|
|
65
|
+
// Delete any existing API key
|
|
66
|
+
db.delete(settings).where(eq(settings.type, 'api_key')).run();
|
|
67
|
+
|
|
68
|
+
const key = generateApiKey();
|
|
69
|
+
const keyHash = hashApiKey(key);
|
|
70
|
+
const keyPrefix = key.slice(0, 8); // "tpb_" + first 4 hex chars
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
|
|
73
|
+
const record = {
|
|
74
|
+
id: randomUUID(),
|
|
75
|
+
type: 'api_key',
|
|
76
|
+
key: 'api_key',
|
|
77
|
+
value: JSON.stringify({ key_prefix: keyPrefix, key_hash: keyHash, last_used_at: null }),
|
|
78
|
+
createdBy,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
db.insert(settings).values(record).run();
|
|
84
|
+
invalidateApiKeyCache();
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
key,
|
|
88
|
+
record: {
|
|
89
|
+
id: record.id,
|
|
90
|
+
keyPrefix,
|
|
91
|
+
createdAt: now,
|
|
92
|
+
lastUsedAt: null,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the current API key metadata (no hash).
|
|
99
|
+
* @returns {object|null}
|
|
100
|
+
*/
|
|
101
|
+
export function getApiKey() {
|
|
102
|
+
const db = getDb();
|
|
103
|
+
const row = db
|
|
104
|
+
.select()
|
|
105
|
+
.from(settings)
|
|
106
|
+
.where(eq(settings.type, 'api_key'))
|
|
107
|
+
.get();
|
|
108
|
+
|
|
109
|
+
if (!row) return null;
|
|
110
|
+
|
|
111
|
+
const parsed = JSON.parse(row.value);
|
|
112
|
+
return {
|
|
113
|
+
id: row.id,
|
|
114
|
+
keyPrefix: parsed.key_prefix,
|
|
115
|
+
createdAt: row.createdAt,
|
|
116
|
+
lastUsedAt: parsed.last_used_at,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Delete the API key.
|
|
122
|
+
*/
|
|
123
|
+
export function deleteApiKey() {
|
|
124
|
+
const db = getDb();
|
|
125
|
+
db.delete(settings).where(eq(settings.type, 'api_key')).run();
|
|
126
|
+
invalidateApiKeyCache();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Verify a raw API key against the cached hash.
|
|
131
|
+
* @param {string} rawKey - Raw API key from request header
|
|
132
|
+
* @returns {object|null} Record if valid, null otherwise
|
|
133
|
+
*/
|
|
134
|
+
export function verifyApiKey(rawKey) {
|
|
135
|
+
if (!rawKey || !rawKey.startsWith(KEY_PREFIX)) return null;
|
|
136
|
+
|
|
137
|
+
const keyHash = hashApiKey(rawKey);
|
|
138
|
+
const cached = _ensureCache();
|
|
139
|
+
|
|
140
|
+
if (!cached) return null;
|
|
141
|
+
const a = Buffer.from(cached.keyHash, 'hex');
|
|
142
|
+
const b = Buffer.from(keyHash, 'hex');
|
|
143
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
|
|
144
|
+
|
|
145
|
+
// Update last_used_at in background (non-blocking)
|
|
146
|
+
try {
|
|
147
|
+
const db = getDb();
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
const row = db.select().from(settings).where(eq(settings.id, cached.id)).get();
|
|
150
|
+
if (row) {
|
|
151
|
+
const parsed = JSON.parse(row.value);
|
|
152
|
+
parsed.last_used_at = now;
|
|
153
|
+
db.update(settings)
|
|
154
|
+
.set({ value: JSON.stringify(parsed), updatedAt: now })
|
|
155
|
+
.where(eq(settings.id, cached.id))
|
|
156
|
+
.run();
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Non-fatal: last_used_at is informational
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return cached;
|
|
163
|
+
}
|
package/lib/db/chats.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, desc, asc } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from './index.js';
|
|
4
|
+
import { chats, messages } from './schema.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a new chat.
|
|
8
|
+
* @param {string} userId
|
|
9
|
+
* @param {string} [title='New Chat']
|
|
10
|
+
* @param {string} [id] - Optional chat ID (UUID). Generated if not provided.
|
|
11
|
+
* @returns {object} The created chat
|
|
12
|
+
*/
|
|
13
|
+
export function createChat(userId, title = 'New Chat', id = null) {
|
|
14
|
+
const db = getDb();
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
const chat = {
|
|
17
|
+
id: id || randomUUID(),
|
|
18
|
+
userId,
|
|
19
|
+
title,
|
|
20
|
+
createdAt: now,
|
|
21
|
+
updatedAt: now,
|
|
22
|
+
};
|
|
23
|
+
db.insert(chats).values(chat).run();
|
|
24
|
+
return chat;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get all chats for a user, ordered by most recently updated.
|
|
29
|
+
* @param {string} userId
|
|
30
|
+
* @returns {object[]}
|
|
31
|
+
*/
|
|
32
|
+
export function getChatsByUser(userId) {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
return db
|
|
35
|
+
.select()
|
|
36
|
+
.from(chats)
|
|
37
|
+
.where(eq(chats.userId, userId))
|
|
38
|
+
.orderBy(desc(chats.updatedAt))
|
|
39
|
+
.all();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get a single chat by ID.
|
|
44
|
+
* @param {string} chatId
|
|
45
|
+
* @returns {object|undefined}
|
|
46
|
+
*/
|
|
47
|
+
export function getChatById(chatId) {
|
|
48
|
+
const db = getDb();
|
|
49
|
+
return db.select().from(chats).where(eq(chats.id, chatId)).get();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update a chat's title.
|
|
54
|
+
* @param {string} chatId
|
|
55
|
+
* @param {string} title
|
|
56
|
+
*/
|
|
57
|
+
export function updateChatTitle(chatId, title) {
|
|
58
|
+
const db = getDb();
|
|
59
|
+
db.update(chats)
|
|
60
|
+
.set({ title, updatedAt: Date.now() })
|
|
61
|
+
.where(eq(chats.id, chatId))
|
|
62
|
+
.run();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Toggle a chat's starred status.
|
|
67
|
+
* @param {string} chatId
|
|
68
|
+
* @returns {number} The new starred value (0 or 1)
|
|
69
|
+
*/
|
|
70
|
+
export function toggleChatStarred(chatId) {
|
|
71
|
+
const db = getDb();
|
|
72
|
+
const chat = db.select({ starred: chats.starred }).from(chats).where(eq(chats.id, chatId)).get();
|
|
73
|
+
const newValue = chat?.starred ? 0 : 1;
|
|
74
|
+
db.update(chats)
|
|
75
|
+
.set({ starred: newValue })
|
|
76
|
+
.where(eq(chats.id, chatId))
|
|
77
|
+
.run();
|
|
78
|
+
return newValue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Delete a chat and all its messages.
|
|
83
|
+
* @param {string} chatId
|
|
84
|
+
*/
|
|
85
|
+
export function deleteChat(chatId) {
|
|
86
|
+
const db = getDb();
|
|
87
|
+
db.delete(messages).where(eq(messages.chatId, chatId)).run();
|
|
88
|
+
db.delete(chats).where(eq(chats.id, chatId)).run();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Delete all chats and messages for a user.
|
|
93
|
+
* @param {string} userId
|
|
94
|
+
*/
|
|
95
|
+
export function deleteAllChatsByUser(userId) {
|
|
96
|
+
const db = getDb();
|
|
97
|
+
const userChats = db
|
|
98
|
+
.select({ id: chats.id })
|
|
99
|
+
.from(chats)
|
|
100
|
+
.where(eq(chats.userId, userId))
|
|
101
|
+
.all();
|
|
102
|
+
|
|
103
|
+
for (const chat of userChats) {
|
|
104
|
+
db.delete(messages).where(eq(messages.chatId, chat.id)).run();
|
|
105
|
+
}
|
|
106
|
+
db.delete(chats).where(eq(chats.userId, userId)).run();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get all messages for a chat, ordered by creation time.
|
|
111
|
+
* @param {string} chatId
|
|
112
|
+
* @returns {object[]}
|
|
113
|
+
*/
|
|
114
|
+
export function getMessagesByChatId(chatId) {
|
|
115
|
+
const db = getDb();
|
|
116
|
+
return db
|
|
117
|
+
.select()
|
|
118
|
+
.from(messages)
|
|
119
|
+
.where(eq(messages.chatId, chatId))
|
|
120
|
+
.orderBy(asc(messages.createdAt))
|
|
121
|
+
.all();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Save a message to a chat. Also updates the chat's updatedAt timestamp.
|
|
126
|
+
* @param {string} chatId
|
|
127
|
+
* @param {string} role - 'user' or 'assistant'
|
|
128
|
+
* @param {string} content
|
|
129
|
+
* @param {string} [id] - Optional message ID
|
|
130
|
+
* @returns {object} The created message
|
|
131
|
+
*/
|
|
132
|
+
export function saveMessage(chatId, role, content, id = null) {
|
|
133
|
+
const db = getDb();
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const message = {
|
|
136
|
+
id: id || randomUUID(),
|
|
137
|
+
chatId,
|
|
138
|
+
role,
|
|
139
|
+
content,
|
|
140
|
+
createdAt: now,
|
|
141
|
+
};
|
|
142
|
+
db.insert(messages).values(message).run();
|
|
143
|
+
db.update(chats).set({ updatedAt: now }).where(eq(chats.id, chatId)).run();
|
|
144
|
+
return message;
|
|
145
|
+
}
|
package/lib/db/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
5
|
+
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
|
6
|
+
import { thepopebotDb, dataDir, PROJECT_ROOT } from '../paths.js';
|
|
7
|
+
import * as schema from './schema.js';
|
|
8
|
+
|
|
9
|
+
let _db = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get or create the Drizzle database instance (lazy singleton).
|
|
13
|
+
* @returns {import('drizzle-orm/better-sqlite3').BetterSQLite3Database}
|
|
14
|
+
*/
|
|
15
|
+
export function getDb() {
|
|
16
|
+
if (!_db) {
|
|
17
|
+
// Ensure data directory exists
|
|
18
|
+
if (!fs.existsSync(dataDir)) {
|
|
19
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
const sqlite = new Database(thepopebotDb);
|
|
22
|
+
sqlite.pragma('journal_mode = WAL');
|
|
23
|
+
_db = drizzle(sqlite, { schema });
|
|
24
|
+
}
|
|
25
|
+
return _db;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the database — apply pending migrations.
|
|
30
|
+
* Called from instrumentation.js at server startup.
|
|
31
|
+
* Uses Drizzle Kit migrations from the package's drizzle/ folder.
|
|
32
|
+
*/
|
|
33
|
+
export function initDatabase() {
|
|
34
|
+
if (!fs.existsSync(dataDir)) {
|
|
35
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sqlite = new Database(thepopebotDb);
|
|
39
|
+
sqlite.pragma('journal_mode = WAL');
|
|
40
|
+
const db = drizzle(sqlite, { schema });
|
|
41
|
+
|
|
42
|
+
// Resolve migrations folder from the installed package.
|
|
43
|
+
// import.meta.url doesn't survive webpack bundling, so resolve from PROJECT_ROOT.
|
|
44
|
+
const migrationsFolder = path.join(PROJECT_ROOT, 'node_modules', 'thepopebot', 'drizzle');
|
|
45
|
+
|
|
46
|
+
migrate(db, { migrationsFolder });
|
|
47
|
+
|
|
48
|
+
sqlite.close();
|
|
49
|
+
|
|
50
|
+
// Force re-creation of drizzle instance on next getDb() call
|
|
51
|
+
_db = null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, desc, sql } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from './index.js';
|
|
4
|
+
import { notifications, subscriptions } from './schema.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a notification, then distribute to all subscribers.
|
|
8
|
+
* @param {string} notificationText - Human-readable notification text
|
|
9
|
+
* @param {object} payload - Raw webhook payload
|
|
10
|
+
* @returns {object} The created notification
|
|
11
|
+
*/
|
|
12
|
+
export async function createNotification(notificationText, payload) {
|
|
13
|
+
const db = getDb();
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
const row = {
|
|
16
|
+
id: randomUUID(),
|
|
17
|
+
notification: notificationText,
|
|
18
|
+
payload: JSON.stringify(payload),
|
|
19
|
+
read: 0,
|
|
20
|
+
createdAt: now,
|
|
21
|
+
};
|
|
22
|
+
db.insert(notifications).values(row).run();
|
|
23
|
+
|
|
24
|
+
// Distribute to subscribers (fire-and-forget)
|
|
25
|
+
distributeNotification(notificationText).catch((err) => {
|
|
26
|
+
console.error('Failed to distribute notification:', err);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return row;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get all notifications, newest first.
|
|
34
|
+
* @returns {object[]}
|
|
35
|
+
*/
|
|
36
|
+
export function getNotifications() {
|
|
37
|
+
const db = getDb();
|
|
38
|
+
return db
|
|
39
|
+
.select()
|
|
40
|
+
.from(notifications)
|
|
41
|
+
.orderBy(desc(notifications.createdAt))
|
|
42
|
+
.all();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get count of unread notifications.
|
|
47
|
+
* @returns {number}
|
|
48
|
+
*/
|
|
49
|
+
export function getUnreadCount() {
|
|
50
|
+
const db = getDb();
|
|
51
|
+
const result = db
|
|
52
|
+
.select({ count: sql`count(*)` })
|
|
53
|
+
.from(notifications)
|
|
54
|
+
.where(eq(notifications.read, 0))
|
|
55
|
+
.get();
|
|
56
|
+
return result?.count ?? 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mark all notifications as read.
|
|
61
|
+
*/
|
|
62
|
+
export function markAllRead() {
|
|
63
|
+
const db = getDb();
|
|
64
|
+
db.update(notifications)
|
|
65
|
+
.set({ read: 1 })
|
|
66
|
+
.where(eq(notifications.read, 0))
|
|
67
|
+
.run();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get all subscriptions.
|
|
72
|
+
* @returns {object[]}
|
|
73
|
+
*/
|
|
74
|
+
export function getSubscriptions() {
|
|
75
|
+
const db = getDb();
|
|
76
|
+
return db.select().from(subscriptions).all();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Distribute a notification to all subscribers.
|
|
81
|
+
* @param {string} notificationText - The notification message
|
|
82
|
+
*/
|
|
83
|
+
async function distributeNotification(notificationText) {
|
|
84
|
+
const subs = getSubscriptions();
|
|
85
|
+
if (!subs.length) return;
|
|
86
|
+
|
|
87
|
+
for (const sub of subs) {
|
|
88
|
+
try {
|
|
89
|
+
if (sub.platform === 'telegram') {
|
|
90
|
+
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
91
|
+
if (!botToken) continue;
|
|
92
|
+
const { sendMessage } = await import('../tools/telegram.js');
|
|
93
|
+
await sendMessage(botToken, sub.channelId, notificationText);
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`Failed to send to ${sub.platform}/${sub.channelId}:`, err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
package/lib/db/schema.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const users = sqliteTable('users', {
|
|
4
|
+
id: text('id').primaryKey(),
|
|
5
|
+
email: text('email').notNull().unique(),
|
|
6
|
+
passwordHash: text('password_hash').notNull(),
|
|
7
|
+
role: text('role').notNull().default('admin'),
|
|
8
|
+
createdAt: integer('created_at').notNull(),
|
|
9
|
+
updatedAt: integer('updated_at').notNull(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const chats = sqliteTable('chats', {
|
|
13
|
+
id: text('id').primaryKey(),
|
|
14
|
+
userId: text('user_id').notNull(),
|
|
15
|
+
title: text('title').notNull().default('New Chat'),
|
|
16
|
+
starred: integer('starred').notNull().default(0),
|
|
17
|
+
createdAt: integer('created_at').notNull(),
|
|
18
|
+
updatedAt: integer('updated_at').notNull(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const messages = sqliteTable('messages', {
|
|
22
|
+
id: text('id').primaryKey(),
|
|
23
|
+
chatId: text('chat_id').notNull(),
|
|
24
|
+
role: text('role').notNull(),
|
|
25
|
+
content: text('content').notNull(),
|
|
26
|
+
createdAt: integer('created_at').notNull(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const notifications = sqliteTable('notifications', {
|
|
30
|
+
id: text('id').primaryKey(),
|
|
31
|
+
notification: text('notification').notNull(),
|
|
32
|
+
payload: text('payload').notNull(),
|
|
33
|
+
read: integer('read').notNull().default(0),
|
|
34
|
+
createdAt: integer('created_at').notNull(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const subscriptions = sqliteTable('subscriptions', {
|
|
38
|
+
id: text('id').primaryKey(),
|
|
39
|
+
platform: text('platform').notNull(),
|
|
40
|
+
channelId: text('channel_id').notNull(),
|
|
41
|
+
createdAt: integer('created_at').notNull(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const settings = sqliteTable('settings', {
|
|
45
|
+
id: text('id').primaryKey(),
|
|
46
|
+
type: text('type').notNull(),
|
|
47
|
+
key: text('key').notNull(),
|
|
48
|
+
value: text('value').notNull(),
|
|
49
|
+
createdBy: text('created_by'),
|
|
50
|
+
createdAt: integer('created_at').notNull(),
|
|
51
|
+
updatedAt: integer('updated_at').notNull(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
// Bug Bounty
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export const programs = sqliteTable('programs', {
|
|
59
|
+
id: text('id').primaryKey(),
|
|
60
|
+
name: text('name').notNull(),
|
|
61
|
+
platform: text('platform').notNull().default('custom'), // hackerone, bugcrowd, intigriti, yeswehack, federacy, custom
|
|
62
|
+
url: text('url'),
|
|
63
|
+
scopeUrl: text('scope_url'),
|
|
64
|
+
minBounty: integer('min_bounty'),
|
|
65
|
+
maxBounty: integer('max_bounty'),
|
|
66
|
+
status: text('status').notNull().default('active'), // active, paused, retired
|
|
67
|
+
notes: text('notes'),
|
|
68
|
+
syncHandle: text('sync_handle'), // platform-specific handle for synced programs
|
|
69
|
+
lastSyncedAt: integer('last_synced_at'),
|
|
70
|
+
createdAt: integer('created_at').notNull(),
|
|
71
|
+
updatedAt: integer('updated_at').notNull(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const targets = sqliteTable('targets', {
|
|
75
|
+
id: text('id').primaryKey(),
|
|
76
|
+
programId: text('program_id'),
|
|
77
|
+
type: text('type').notNull().default('domain'), // domain, ip, url, wildcard, api, mobile, cidr
|
|
78
|
+
value: text('value').notNull(),
|
|
79
|
+
status: text('status').notNull().default('in_scope'), // in_scope, out_of_scope, testing, completed
|
|
80
|
+
technologies: text('technologies'), // JSON array of detected tech
|
|
81
|
+
notes: text('notes'),
|
|
82
|
+
lastScannedAt: integer('last_scanned_at'),
|
|
83
|
+
syncSource: text('sync_source'), // hackerone, bugcrowd, intigriti, yeswehack, federacy
|
|
84
|
+
syncProgramHandle: text('sync_program_handle'), // handle from synced platform
|
|
85
|
+
createdAt: integer('created_at').notNull(),
|
|
86
|
+
updatedAt: integer('updated_at').notNull(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const findings = sqliteTable('findings', {
|
|
90
|
+
id: text('id').primaryKey(),
|
|
91
|
+
targetId: text('target_id'),
|
|
92
|
+
title: text('title').notNull(),
|
|
93
|
+
severity: text('severity').notNull().default('info'), // critical, high, medium, low, info
|
|
94
|
+
type: text('type').notNull(), // xss, sqli, ssrf, idor, rce, lfi, open_redirect, subdomain_takeover, info_disclosure, misconfig, etc.
|
|
95
|
+
status: text('status').notNull().default('new'), // new, triaging, confirmed, reported, duplicate, resolved, bounty_paid
|
|
96
|
+
description: text('description'),
|
|
97
|
+
stepsToReproduce: text('steps_to_reproduce'),
|
|
98
|
+
impact: text('impact'),
|
|
99
|
+
evidence: text('evidence'), // JSON array of screenshot URLs or file paths
|
|
100
|
+
bountyAmount: integer('bounty_amount'),
|
|
101
|
+
reportUrl: text('report_url'),
|
|
102
|
+
agentId: text('agent_id'), // which agent discovered this
|
|
103
|
+
toolId: text('tool_id'), // which tool found it
|
|
104
|
+
rawOutput: text('raw_output'), // raw tool output
|
|
105
|
+
reportedAt: integer('reported_at'),
|
|
106
|
+
createdAt: integer('created_at').notNull(),
|
|
107
|
+
updatedAt: integer('updated_at').notNull(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
111
|
+
// Tool Registry
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export const tools = sqliteTable('tools', {
|
|
115
|
+
id: text('id').primaryKey(),
|
|
116
|
+
catalogId: text('catalog_id'), // links to built-in catalog entry
|
|
117
|
+
name: text('name').notNull(),
|
|
118
|
+
slug: text('slug').notNull().unique(),
|
|
119
|
+
category: text('category').notNull(),
|
|
120
|
+
description: text('description'),
|
|
121
|
+
dockerImage: text('docker_image'),
|
|
122
|
+
installCmd: text('install_cmd'),
|
|
123
|
+
sourceUrl: text('source_url'), // GitHub URL
|
|
124
|
+
version: text('version'),
|
|
125
|
+
installed: integer('installed').notNull().default(0),
|
|
126
|
+
enabled: integer('enabled').notNull().default(1),
|
|
127
|
+
config: text('config'), // JSON tool-specific config
|
|
128
|
+
mcpServerId: text('mcp_server_id'), // if exposed via MCP
|
|
129
|
+
createdAt: integer('created_at').notNull(),
|
|
130
|
+
updatedAt: integer('updated_at').notNull(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export const dockerContainers = sqliteTable('docker_containers', {
|
|
134
|
+
id: text('id').primaryKey(),
|
|
135
|
+
toolId: text('tool_id'),
|
|
136
|
+
containerId: text('container_id'), // Docker container ID
|
|
137
|
+
imageName: text('image_name').notNull(),
|
|
138
|
+
status: text('status').notNull().default('created'), // created, running, stopped, error
|
|
139
|
+
agentId: text('agent_id'),
|
|
140
|
+
ports: text('ports'), // JSON port mapping
|
|
141
|
+
env: text('env'), // JSON env vars (secrets redacted)
|
|
142
|
+
logs: text('logs'),
|
|
143
|
+
createdAt: integer('created_at').notNull(),
|
|
144
|
+
stoppedAt: integer('stopped_at'),
|
|
145
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { eq, and } from 'drizzle-orm';
|
|
3
|
+
import { getDb } from './index.js';
|
|
4
|
+
import { settings } from './schema.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the stored available version from the DB.
|
|
8
|
+
* @returns {string|null}
|
|
9
|
+
*/
|
|
10
|
+
export function getAvailableVersion() {
|
|
11
|
+
const db = getDb();
|
|
12
|
+
const row = db
|
|
13
|
+
.select()
|
|
14
|
+
.from(settings)
|
|
15
|
+
.where(and(eq(settings.type, 'update'), eq(settings.key, 'available_version')))
|
|
16
|
+
.get();
|
|
17
|
+
|
|
18
|
+
return row ? row.value : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set the available version in the DB (delete + insert upsert).
|
|
23
|
+
* @param {string} version
|
|
24
|
+
*/
|
|
25
|
+
export function setAvailableVersion(version) {
|
|
26
|
+
const db = getDb();
|
|
27
|
+
db.delete(settings)
|
|
28
|
+
.where(and(eq(settings.type, 'update'), eq(settings.key, 'available_version')))
|
|
29
|
+
.run();
|
|
30
|
+
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
db.insert(settings).values({
|
|
33
|
+
id: randomUUID(),
|
|
34
|
+
type: 'update',
|
|
35
|
+
key: 'available_version',
|
|
36
|
+
value: version,
|
|
37
|
+
createdAt: now,
|
|
38
|
+
updatedAt: now,
|
|
39
|
+
}).run();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Clear the available version from the DB.
|
|
44
|
+
*/
|
|
45
|
+
export function clearAvailableVersion() {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
db.delete(settings)
|
|
48
|
+
.where(and(eq(settings.type, 'update'), eq(settings.key, 'available_version')))
|
|
49
|
+
.run();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the stored release notes from the DB.
|
|
54
|
+
* @returns {string|null}
|
|
55
|
+
*/
|
|
56
|
+
export function getReleaseNotes() {
|
|
57
|
+
const db = getDb();
|
|
58
|
+
const row = db
|
|
59
|
+
.select()
|
|
60
|
+
.from(settings)
|
|
61
|
+
.where(and(eq(settings.type, 'update'), eq(settings.key, 'release_notes')))
|
|
62
|
+
.get();
|
|
63
|
+
|
|
64
|
+
return row ? row.value : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set the release notes in the DB (delete + insert upsert).
|
|
69
|
+
* @param {string} notes
|
|
70
|
+
*/
|
|
71
|
+
export function setReleaseNotes(notes) {
|
|
72
|
+
const db = getDb();
|
|
73
|
+
db.delete(settings)
|
|
74
|
+
.where(and(eq(settings.type, 'update'), eq(settings.key, 'release_notes')))
|
|
75
|
+
.run();
|
|
76
|
+
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
db.insert(settings).values({
|
|
79
|
+
id: randomUUID(),
|
|
80
|
+
type: 'update',
|
|
81
|
+
key: 'release_notes',
|
|
82
|
+
value: notes,
|
|
83
|
+
createdAt: now,
|
|
84
|
+
updatedAt: now,
|
|
85
|
+
}).run();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Clear the release notes from the DB.
|
|
90
|
+
*/
|
|
91
|
+
export function clearReleaseNotes() {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
db.delete(settings)
|
|
94
|
+
.where(and(eq(settings.type, 'update'), eq(settings.key, 'release_notes')))
|
|
95
|
+
.run();
|
|
96
|
+
}
|