@delt/claude-alarm 0.6.26 → 0.7.1
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/dist/channel/server.js +10 -0
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +66 -4
- package/dist/cli.js.map +1 -1
- package/dist/hub/server.js +56 -4
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +56 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/hub/server.js
CHANGED
|
@@ -582,12 +582,21 @@ ${label}`);
|
|
|
582
582
|
${bodyLines.join("\n")}`;
|
|
583
583
|
}
|
|
584
584
|
);
|
|
585
|
+
const tokens = [];
|
|
586
|
+
const placeholder = (i) => `\0CODE${i}\0`;
|
|
587
|
+
text = text.replace(/```(?:\w*)\n?([\s\S]*?)```/g, (_m, body) => {
|
|
588
|
+
const i = tokens.push(`<pre>${this.escHtml(body)}</pre>`) - 1;
|
|
589
|
+
return placeholder(i);
|
|
590
|
+
});
|
|
591
|
+
text = text.replace(/`([^`\n]+)`/g, (_m, body) => {
|
|
592
|
+
const i = tokens.push(`<code>${this.escHtml(body)}</code>`) - 1;
|
|
593
|
+
return placeholder(i);
|
|
594
|
+
});
|
|
585
595
|
let html = this.escHtml(text);
|
|
586
|
-
html = html.replace(/```(?:\w*)\n?([\s\S]*?)```/g, "<pre>$1</pre>");
|
|
587
|
-
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
588
596
|
html = html.replace(/^#{1,3}\s+(.+)$/gm, "<b>$1</b>");
|
|
589
597
|
html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
590
|
-
html = html.replace(/\*(
|
|
598
|
+
html = html.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
|
599
|
+
html = html.replace(/\u0000CODE(\d+)\u0000/g, (_m, n) => tokens[Number(n)] ?? "");
|
|
591
600
|
return html;
|
|
592
601
|
}
|
|
593
602
|
/** Update config (e.g., from dashboard settings) */
|
|
@@ -660,6 +669,9 @@ var HubServer = class {
|
|
|
660
669
|
// All connected dashboard WebSockets
|
|
661
670
|
dashboardSockets = /* @__PURE__ */ new Set();
|
|
662
671
|
telegramBot;
|
|
672
|
+
heartbeatInterval;
|
|
673
|
+
channelAlive = /* @__PURE__ */ new Map();
|
|
674
|
+
// sessionId -> alive flag
|
|
663
675
|
host;
|
|
664
676
|
port;
|
|
665
677
|
token;
|
|
@@ -711,6 +723,7 @@ var HubServer = class {
|
|
|
711
723
|
}
|
|
712
724
|
async start() {
|
|
713
725
|
this.cleanupUploads();
|
|
726
|
+
this.startHeartbeat();
|
|
714
727
|
return new Promise((resolve, reject) => {
|
|
715
728
|
this.httpServer.on("error", reject);
|
|
716
729
|
this.httpServer.listen(this.port, this.host, () => {
|
|
@@ -722,6 +735,7 @@ var HubServer = class {
|
|
|
722
735
|
}
|
|
723
736
|
stop() {
|
|
724
737
|
return new Promise((resolve) => {
|
|
738
|
+
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
725
739
|
if (this.telegramBot) this.telegramBot.stopPolling();
|
|
726
740
|
for (const ws of this.channelSockets.values()) ws.terminate();
|
|
727
741
|
for (const ws of this.dashboardSockets) ws.terminate();
|
|
@@ -746,7 +760,7 @@ var HubServer = class {
|
|
|
746
760
|
if (origin && (origin.includes("127.0.0.1") || origin.includes("localhost"))) {
|
|
747
761
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
748
762
|
}
|
|
749
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
763
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
750
764
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
751
765
|
if (req.method === "OPTIONS") {
|
|
752
766
|
res.writeHead(204);
|
|
@@ -775,6 +789,22 @@ var HubServer = class {
|
|
|
775
789
|
sessions: this.sessions.count(),
|
|
776
790
|
uptime: Date.now() - this.startTime
|
|
777
791
|
});
|
|
792
|
+
} else if (url.pathname.startsWith("/api/sessions/") && req.method === "DELETE") {
|
|
793
|
+
const sessionId = url.pathname.slice("/api/sessions/".length);
|
|
794
|
+
const ws = this.channelSockets.get(sessionId);
|
|
795
|
+
if (ws) {
|
|
796
|
+
ws.terminate();
|
|
797
|
+
}
|
|
798
|
+
const session = this.sessions.unregister(sessionId);
|
|
799
|
+
this.channelSockets.delete(sessionId);
|
|
800
|
+
this.localChannels.delete(sessionId);
|
|
801
|
+
this.channelAlive.delete(sessionId);
|
|
802
|
+
if (session) {
|
|
803
|
+
this.broadcastToDashboards({ type: "session_disconnected", sessionId });
|
|
804
|
+
this.jsonResponse(res, 200, { ok: true });
|
|
805
|
+
} else {
|
|
806
|
+
this.jsonResponse(res, 404, { error: "Session not found" });
|
|
807
|
+
}
|
|
778
808
|
} else if (url.pathname === "/api/send" && req.method === "POST") {
|
|
779
809
|
this.handleApiSend(req, res);
|
|
780
810
|
} else if (url.pathname === "/api/notify" && req.method === "POST") {
|
|
@@ -865,6 +895,14 @@ var HubServer = class {
|
|
|
865
895
|
handleChannelConnection(ws, req) {
|
|
866
896
|
const isLocal = this.isLocalRequest(req);
|
|
867
897
|
logger.info(`Channel server connected (local: ${isLocal})`);
|
|
898
|
+
ws.on("pong", () => {
|
|
899
|
+
for (const [sessionId, sock] of this.channelSockets) {
|
|
900
|
+
if (sock === ws) {
|
|
901
|
+
this.channelAlive.set(sessionId, true);
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
});
|
|
868
906
|
ws.on("message", (data) => {
|
|
869
907
|
try {
|
|
870
908
|
const msg = JSON.parse(data.toString());
|
|
@@ -879,6 +917,7 @@ var HubServer = class {
|
|
|
879
917
|
const session = this.sessions.unregister(sessionId);
|
|
880
918
|
this.channelSockets.delete(sessionId);
|
|
881
919
|
this.localChannels.delete(sessionId);
|
|
920
|
+
this.channelAlive.delete(sessionId);
|
|
882
921
|
logger.info(`Channel disconnected: ${sessionId}`);
|
|
883
922
|
this.broadcastToDashboards({
|
|
884
923
|
type: "session_disconnected",
|
|
@@ -1193,6 +1232,19 @@ var HubServer = class {
|
|
|
1193
1232
|
this.jsonResponse(res, 500, { error: err.message });
|
|
1194
1233
|
}
|
|
1195
1234
|
}
|
|
1235
|
+
startHeartbeat() {
|
|
1236
|
+
this.heartbeatInterval = setInterval(() => {
|
|
1237
|
+
for (const [sessionId, ws] of this.channelSockets) {
|
|
1238
|
+
if (this.channelAlive.get(sessionId) === false) {
|
|
1239
|
+
logger.info(`Heartbeat timeout, terminating session: ${sessionId}`);
|
|
1240
|
+
ws.terminate();
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
this.channelAlive.set(sessionId, false);
|
|
1244
|
+
ws.ping();
|
|
1245
|
+
}
|
|
1246
|
+
}, 3e4);
|
|
1247
|
+
}
|
|
1196
1248
|
cleanupUploads() {
|
|
1197
1249
|
try {
|
|
1198
1250
|
if (!fs3.existsSync(UPLOADS_DIR)) return;
|
package/dist/hub/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hub/server.ts","../../src/shared/logger.ts","../../src/shared/constants.ts","../../src/hub/session-manager.ts","../../src/hub/notifier.ts","../../src/hub/telegram.ts","../../src/shared/config.ts"],"sourcesContent":["import http from 'node:http';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { WebSocketServer, WebSocket } from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { randomUUID } from 'node:crypto';\r\nimport {\r\n DEFAULT_HUB_HOST,\r\n DEFAULT_HUB_PORT,\r\n WS_PATH_CHANNEL,\r\n WS_PATH_DASHBOARD,\r\n UPLOADS_DIR,\r\n} from '../shared/constants.js';\r\nimport { SessionManager } from './session-manager.js';\r\nimport { Notifier } from './notifier.js';\r\nimport { TelegramBot } from './telegram.js';\r\nimport { loadConfig, saveConfig } from '../shared/config.js';\r\nimport type { ChannelMessage, AppConfig, SessionInfo, WebhookConfig, TelegramConfig } from '../shared/types.js';\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nexport class HubServer {\r\n private httpServer: http.Server;\r\n private wssChannel: WebSocketServer;\r\n private wssDashboard: WebSocketServer;\r\n private sessions = new SessionManager();\r\n private notifier = new Notifier();\r\n private startTime = Date.now();\r\n\r\n // Map sessionId -> channel WebSocket\r\n private channelSockets = new Map<string, WebSocket>();\r\n // Track which channel connections are local\r\n private localChannels = new Set<string>();\r\n // All connected dashboard WebSockets\r\n private dashboardSockets = new Set<WebSocket>();\r\n\r\n private telegramBot?: TelegramBot;\r\n\r\n private host: string;\r\n private port: number;\r\n private token?: string;\r\n\r\n constructor(config?: Partial<AppConfig>) {\r\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\r\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\r\n this.token = config?.hub?.token;\r\n\r\n if (config?.notifications) {\r\n this.notifier.configure({\r\n desktop: config.notifications.desktop,\r\n });\r\n }\r\n if (config?.webhooks) {\r\n this.notifier.configure({ webhooks: config.webhooks });\r\n }\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\r\n\r\n // Initialize Telegram bot if configured\r\n const fullConfig = loadConfig();\r\n if (fullConfig.telegram?.enabled && fullConfig.telegram.botToken && fullConfig.telegram.chatId) {\r\n this.initTelegram(fullConfig.telegram);\r\n }\r\n\r\n // HTTP Server\r\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\r\n\r\n // WebSocket for channel servers\r\n this.wssChannel = new WebSocketServer({ noServer: true });\r\n this.wssChannel.on('connection', (ws: WebSocket, req: http.IncomingMessage) => this.handleChannelConnection(ws, req));\r\n\r\n // WebSocket for dashboard\r\n this.wssDashboard = new WebSocketServer({ noServer: true });\r\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\r\n\r\n // Route WebSocket upgrade requests\r\n this.httpServer.on('upgrade', (req, socket, head) => {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n const pathname = url.pathname;\r\n\r\n // Token auth for WebSocket connections (skip for local requests)\r\n if (this.token && !this.isLocalRequest(req)) {\r\n const wsToken = url.searchParams.get('token');\r\n if (wsToken !== this.token) {\r\n socket.destroy();\r\n return;\r\n }\r\n }\r\n\r\n if (pathname === WS_PATH_CHANNEL) {\r\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssChannel.emit('connection', ws, req);\r\n });\r\n } else if (pathname === WS_PATH_DASHBOARD) {\r\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssDashboard.emit('connection', ws, req);\r\n });\r\n } else {\r\n socket.destroy();\r\n }\r\n });\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.cleanupUploads();\r\n return new Promise((resolve, reject) => {\r\n this.httpServer.on('error', reject);\r\n this.httpServer.listen(this.port, this.host, () => {\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n stop(): Promise<void> {\r\n return new Promise((resolve) => {\r\n // Stop telegram bot\r\n if (this.telegramBot) this.telegramBot.stopPolling();\r\n\r\n // Force-close all WebSocket connections\r\n for (const ws of this.channelSockets.values()) ws.terminate();\r\n for (const ws of this.dashboardSockets) ws.terminate();\r\n this.channelSockets.clear();\r\n this.dashboardSockets.clear();\r\n\r\n this.wssChannel.close();\r\n this.wssDashboard.close();\r\n this.httpServer.close(() => {\r\n logger.info('Hub server stopped');\r\n resolve();\r\n });\r\n\r\n // Force resolve after 3 seconds if server won't close\r\n setTimeout(() => {\r\n logger.warn('Force shutting down');\r\n resolve();\r\n }, 3000);\r\n });\r\n }\r\n\r\n // --- HTTP Handler ---\r\n\r\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n\r\n // CORS headers - restrict to same origin\r\n const origin = req.headers.origin;\r\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\r\n res.setHeader('Access-Control-Allow-Origin', origin);\r\n }\r\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\r\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\r\n\r\n if (req.method === 'OPTIONS') {\r\n res.writeHead(204);\r\n res.end();\r\n return;\r\n }\r\n\r\n // Token auth for API endpoints (skip dashboard HTML serving)\r\n if (url.pathname !== '/' && this.token) {\r\n if (!this.isLocalRequest(req)) {\r\n const authHeader = req.headers['authorization'];\r\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\r\n if (bearerToken !== this.token) {\r\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Route\r\n if (url.pathname === '/' && req.method === 'GET') {\r\n this.serveDashboard(res);\r\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\r\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, {\r\n running: true,\r\n pid: process.pid,\r\n port: this.port,\r\n sessions: this.sessions.count(),\r\n uptime: Date.now() - this.startTime,\r\n });\r\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\r\n this.handleApiSend(req, res);\r\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\r\n this.handleApiNotify(req, res);\r\n } else if (url.pathname === '/api/webhooks' && req.method === 'GET') {\r\n const config = loadConfig();\r\n this.jsonResponse(res, 200, { webhooks: config.webhooks || [] });\r\n } else if (url.pathname === '/api/webhooks' && req.method === 'POST') {\r\n this.handleWebhookSave(req, res);\r\n } else if (url.pathname === '/api/telegram' && req.method === 'GET') {\r\n const cfg = loadConfig();\r\n const tg = cfg.telegram ?? { botToken: '', chatId: '', enabled: false };\r\n // Mask bot token for security\r\n this.jsonResponse(res, 200, {\r\n telegram: { ...tg, botToken: tg.botToken ? `${tg.botToken.slice(0, 8)}...` : '' },\r\n });\r\n } else if (url.pathname === '/api/telegram' && req.method === 'POST') {\r\n this.handleTelegramSave(req, res);\r\n } else if (url.pathname === '/api/telegram/test' && req.method === 'POST') {\r\n this.handleTelegramTest(req, res);\r\n } else if (url.pathname === '/api/telegram/detect' && req.method === 'POST') {\r\n this.handleTelegramDetect(req, res);\r\n } else {\r\n this.jsonResponse(res, 404, { error: 'Not found' });\r\n }\r\n }\r\n\r\n private serveDashboard(res: http.ServerResponse): void {\r\n // Look for dashboard HTML relative to this file (dist) or source\r\n const candidates = [\r\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\r\n path.join(__dirname, 'dashboard', 'index.html'), // from dist/ (bundled index.js)\r\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\r\n path.join(__dirname, '..', 'src', 'dashboard', 'index.html'), // from dist/ -> src/\r\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\r\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\r\n ];\r\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\r\n\r\n for (const candidate of candidates) {\r\n if (fs.existsSync(candidate)) {\r\n const html = fs.readFileSync(candidate, 'utf-8');\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end(html);\r\n return;\r\n }\r\n }\r\n\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\r\n }\r\n\r\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { sessionId, content } = body as { sessionId?: string; content?: string };\r\n if (!sessionId || !content) {\r\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\r\n return;\r\n }\r\n\r\n const ws = this.channelSockets.get(sessionId);\r\n if (!ws || ws.readyState !== WebSocket.OPEN) {\r\n this.jsonResponse(res, 404, { error: 'Session not connected' });\r\n return;\r\n }\r\n\r\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\r\n ws.send(JSON.stringify(msg));\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\r\n if (!title || !message) {\r\n this.jsonResponse(res, 400, { error: 'title and message are required' });\r\n return;\r\n }\r\n\r\n await this.notifier.notify(title, message, (level as any) ?? 'info');\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n // --- Channel WebSocket ---\r\n\r\n private handleChannelConnection(ws: WebSocket, req: http.IncomingMessage): void {\r\n const isLocal = this.isLocalRequest(req);\r\n logger.info(`Channel server connected (local: ${isLocal})`);\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n this.handleChannelMessage(ws, isLocal, msg);\r\n } catch {\r\n logger.warn('Invalid message from channel');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n // Find and remove the session for this socket\r\n for (const [sessionId, sock] of this.channelSockets) {\r\n if (sock === ws) {\r\n const session = this.sessions.unregister(sessionId);\r\n this.channelSockets.delete(sessionId);\r\n this.localChannels.delete(sessionId);\r\n logger.info(`Channel disconnected: ${sessionId}`);\r\n this.broadcastToDashboards({\r\n type: 'session_disconnected',\r\n sessionId,\r\n });\r\n break;\r\n }\r\n }\r\n });\r\n }\r\n\r\n private handleChannelMessage(ws: WebSocket, isLocal: boolean, msg: ChannelMessage): void {\r\n switch (msg.type) {\r\n case 'register': {\r\n const session = msg.session;\r\n session.isLocal = isLocal;\r\n const isReregister = !!this.sessions.get(session.id);\r\n this.sessions.register(session);\r\n this.channelSockets.set(session.id, ws);\r\n if (isLocal) this.localChannels.add(session.id);\r\n logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);\r\n this.broadcastToDashboards({\r\n type: isReregister ? 'session_updated' : 'session_connected',\r\n session,\r\n });\r\n break;\r\n }\r\n\r\n case 'status': {\r\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\r\n if (updated) {\r\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\r\n }\r\n break;\r\n }\r\n\r\n case 'notify': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const notifySession = this.sessions.get(msg.sessionId);\r\n const notifyLabel = this.getSessionLabel(notifySession);\r\n this.notifier.notifyWithSession(msg.sessionId, notifyLabel, `[${notifyLabel}] ${msg.title}`, msg.message, msg.level ?? 'info');\r\n this.broadcastToDashboards({\r\n type: 'notification',\r\n sessionId: msg.sessionId,\r\n title: msg.title,\r\n message: msg.message,\r\n level: msg.level,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n\r\n case 'reply': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const replySession = this.sessions.get(msg.sessionId);\r\n const replyLabel = this.getSessionLabel(replySession);\r\n this.notifier.notifyWithSession(msg.sessionId, replyLabel, `[${replyLabel}] Reply`, msg.content.slice(0, 3000), 'info');\r\n this.broadcastToDashboards({\r\n type: 'reply_from_session',\r\n sessionId: msg.sessionId,\r\n content: msg.content,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n\r\n case 'permission_request': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n logger.info(`Permission request [${msg.requestId}] from ${msg.sessionId}: ${msg.toolName}`);\r\n this.broadcastToDashboards({\r\n type: 'permission_request',\r\n sessionId: msg.sessionId,\r\n requestId: msg.requestId,\r\n toolName: msg.toolName,\r\n description: msg.description,\r\n inputPreview: msg.inputPreview,\r\n timestamp: msg.timestamp,\r\n });\r\n // Forward to Telegram\r\n if (this.telegramBot) {\r\n const session = this.sessions.get(msg.sessionId);\r\n const label = this.getSessionLabel(session);\r\n this.telegramBot.sendPermissionRequest(msg.sessionId, label, msg.requestId, msg.toolName, msg.description, msg.inputPreview);\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // --- Dashboard WebSocket ---\r\n\r\n private handleDashboardConnection(ws: WebSocket): void {\r\n this.dashboardSockets.add(ws);\r\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\r\n\r\n // Send current session list\r\n const sessionsMsg: ChannelMessage = {\r\n type: 'sessions_list',\r\n sessions: this.sessions.getAll(),\r\n };\r\n ws.send(JSON.stringify(sessionsMsg));\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n if (msg.type === 'message_to_session') {\r\n const channelWs = this.channelSockets.get(msg.sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n channelWs.send(JSON.stringify(msg));\r\n }\r\n } else if (msg.type === 'image_upload') {\r\n this.handleImageUpload(msg);\r\n } else if (msg.type === 'permission_response') {\r\n const channelWs = this.channelSockets.get(msg.sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Permission verdict [${msg.requestId}]: ${msg.behavior} -> session ${msg.sessionId}`);\r\n }\r\n }\r\n } catch {\r\n logger.warn('Invalid message from dashboard');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n this.dashboardSockets.delete(ws);\r\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\r\n });\r\n }\r\n\r\n // --- Helpers ---\r\n\r\n private broadcastToDashboards(msg: ChannelMessage): void {\r\n const payload = JSON.stringify(msg);\r\n for (const ws of this.dashboardSockets) {\r\n if (ws.readyState === WebSocket.OPEN) {\r\n ws.send(payload);\r\n }\r\n }\r\n }\r\n\r\n private handleImageUpload(msg: ChannelMessage & { type: 'image_upload' }): void {\r\n const { sessionId, imageData, mimeType, originalName, content } = msg;\r\n\r\n // Only allow for local sessions\r\n if (!this.localChannels.has(sessionId)) {\r\n logger.warn(`Image upload rejected: session ${sessionId} is not local`);\r\n return;\r\n }\r\n\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (!channelWs || channelWs.readyState !== WebSocket.OPEN) return;\r\n\r\n // Validate mime type\r\n const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];\r\n if (!allowedTypes.includes(mimeType)) return;\r\n\r\n // Extract base64 data (remove data URL prefix if present)\r\n const base64Data = imageData.replace(/^data:image\\/\\w+;base64,/, '');\r\n const buffer = Buffer.from(base64Data, 'base64');\r\n\r\n // Validate size (10MB max)\r\n if (buffer.length > 10 * 1024 * 1024) {\r\n logger.warn('Image upload rejected: exceeds 10MB');\r\n return;\r\n }\r\n\r\n // Save to uploads dir\r\n fs.mkdirSync(UPLOADS_DIR, { recursive: true });\r\n const ext = mimeType.split('/')[1] === 'jpeg' ? 'jpg' : mimeType.split('/')[1];\r\n const filename = `${randomUUID()}.${ext}`;\r\n const filePath = path.join(UPLOADS_DIR, filename);\r\n fs.writeFileSync(filePath, buffer);\r\n\r\n // Forward file path to channel\r\n const forwardMsg: ChannelMessage = {\r\n type: 'image_to_session',\r\n sessionId,\r\n imagePath: filePath,\r\n mimeType,\r\n originalName,\r\n content,\r\n };\r\n channelWs.send(JSON.stringify(forwardMsg));\r\n logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);\r\n\r\n // Cleanup after 5 minutes\r\n setTimeout(() => {\r\n try { fs.unlinkSync(filePath); } catch {}\r\n }, 5 * 60 * 1000);\r\n }\r\n\r\n private async handleWebhookSave(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { webhooks } = body as { webhooks?: WebhookConfig[] };\r\n if (!Array.isArray(webhooks)) { this.jsonResponse(res, 400, { error: 'webhooks must be an array' }); return; }\r\n const config = loadConfig();\r\n config.webhooks = webhooks;\r\n saveConfig(config);\r\n this.notifier.configure({ webhooks });\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private initTelegram(config: TelegramConfig): void {\r\n this.telegramBot = new TelegramBot(config);\r\n this.telegramBot.getSessions = () => this.sessions.getAll();\r\n this.telegramBot.onMessageToSession = (sessionId, content) => {\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Telegram message forwarded to session: ${sessionId}`);\r\n }\r\n };\r\n this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n const msg: ChannelMessage = { type: 'image_to_session', sessionId, imagePath, mimeType, content: caption };\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Telegram photo forwarded to session: ${sessionId}`);\r\n }\r\n };\r\n this.telegramBot.onPermissionVerdict = (sessionId, requestId, behavior) => {\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n const msg: ChannelMessage = { type: 'permission_response', sessionId, requestId, behavior };\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Telegram permission verdict [${requestId}]: ${behavior} -> session ${sessionId}`);\r\n }\r\n // Also notify dashboards so they can dismiss the permission bar\r\n this.broadcastToDashboards({ type: 'permission_response', sessionId, requestId, behavior });\r\n };\r\n this.notifier.configure({ telegramBot: this.telegramBot });\r\n this.telegramBot.startPolling();\r\n logger.info('Telegram bot initialized');\r\n }\r\n\r\n private async handleTelegramSave(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { telegram } = body as { telegram?: TelegramConfig };\r\n if (!telegram) { this.jsonResponse(res, 400, { error: 'telegram config required' }); return; }\r\n\r\n const config = loadConfig();\r\n // If botToken is masked (contains '...'), keep the existing token\r\n if (telegram.botToken.includes('...') && config.telegram?.botToken) {\r\n telegram.botToken = config.telegram.botToken;\r\n }\r\n config.telegram = telegram;\r\n saveConfig(config);\r\n\r\n // Stop existing bot if running\r\n if (this.telegramBot) {\r\n this.telegramBot.stopPolling();\r\n this.telegramBot = undefined;\r\n this.notifier.configure({ telegramBot: undefined as any });\r\n }\r\n\r\n // Start new bot if enabled\r\n if (telegram.enabled && telegram.botToken && telegram.chatId) {\r\n this.initTelegram(telegram);\r\n }\r\n\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private async handleTelegramTest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { botToken, chatId } = body as { botToken?: string; chatId?: string };\r\n if (!botToken || !chatId) {\r\n this.jsonResponse(res, 400, { error: 'botToken and chatId required' });\r\n return;\r\n }\r\n\r\n try {\r\n const testRes = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n chat_id: chatId,\r\n text: 'Claude Alarm test message! Connection successful.',\r\n }),\r\n });\r\n\r\n if (testRes.ok) {\r\n this.jsonResponse(res, 200, { ok: true });\r\n } else {\r\n const err = await testRes.json() as { description?: string };\r\n this.jsonResponse(res, 400, { error: (err as any).description || 'Telegram API error' });\r\n }\r\n } catch (err) {\r\n this.jsonResponse(res, 500, { error: (err as Error).message });\r\n }\r\n }\r\n\r\n private async handleTelegramDetect(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { botToken } = body as { botToken?: string };\r\n if (!botToken) {\r\n this.jsonResponse(res, 400, { error: 'botToken required' });\r\n return;\r\n }\r\n\r\n try {\r\n const detectRes = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates?timeout=0&limit=10`, {\r\n signal: AbortSignal.timeout(10000),\r\n });\r\n\r\n if (!detectRes.ok) {\r\n const err = await detectRes.json() as { description?: string };\r\n this.jsonResponse(res, 400, { error: (err as any).description || 'Invalid bot token' });\r\n return;\r\n }\r\n\r\n const data = await detectRes.json() as { ok: boolean; result: Array<{ message?: { chat: { id: number; first_name?: string; title?: string; type: string } } }> };\r\n if (!data.ok || !data.result.length) {\r\n this.jsonResponse(res, 200, { ok: false, chats: [] });\r\n return;\r\n }\r\n\r\n // Extract unique chats\r\n const chatMap = new Map<string, { id: string; name: string; type: string }>();\r\n for (const update of data.result) {\r\n if (update.message?.chat) {\r\n const chat = update.message.chat;\r\n const id = String(chat.id);\r\n if (!chatMap.has(id)) {\r\n chatMap.set(id, {\r\n id,\r\n name: chat.title || chat.first_name || id,\r\n type: chat.type,\r\n });\r\n }\r\n }\r\n }\r\n\r\n this.jsonResponse(res, 200, { ok: true, chats: [...chatMap.values()] });\r\n } catch (err) {\r\n this.jsonResponse(res, 500, { error: (err as Error).message });\r\n }\r\n }\r\n\r\n private cleanupUploads(): void {\r\n try {\r\n if (!fs.existsSync(UPLOADS_DIR)) return;\r\n const files = fs.readdirSync(UPLOADS_DIR);\r\n for (const file of files) {\r\n try { fs.unlinkSync(path.join(UPLOADS_DIR, file)); } catch {}\r\n }\r\n if (files.length > 0) logger.info(`Cleaned up ${files.length} leftover upload(s)`);\r\n } catch {}\r\n }\r\n\r\n private getSessionLabel(session?: SessionInfo): string {\r\n if (!session) return 'unknown';\r\n return session.displayName || session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n }\r\n\r\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' });\r\n res.end(JSON.stringify(body));\r\n }\r\n\r\n private isLocalRequest(req: http.IncomingMessage): boolean {\r\n const addr = req.socket.remoteAddress;\r\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\r\n }\r\n\r\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\r\n return new Promise((resolve) => {\r\n let data = '';\r\n let size = 0;\r\n req.on('data', (chunk) => {\r\n size += chunk.length;\r\n if (size > maxSize) {\r\n req.destroy();\r\n resolve(null);\r\n return;\r\n }\r\n data += chunk;\r\n });\r\n req.on('end', () => {\r\n try {\r\n resolve(JSON.parse(data));\r\n } catch {\r\n resolve(null);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n\r\n// Direct execution support\r\nif (process.argv[1] && (\r\n process.argv[1].endsWith('hub/server.js') ||\r\n process.argv[1].endsWith('hub/server.ts')\r\n)) {\r\n const config = loadConfig();\r\n const hub = new HubServer(config);\r\n hub.start().catch((err) => {\r\n logger.error('Failed to start hub:', err);\r\n process.exit(1);\r\n });\r\n\r\n const shutdown = () => {\r\n hub.stop().then(() => process.exit(0));\r\n };\r\n process.on('SIGINT', shutdown);\r\n process.on('SIGTERM', shutdown);\r\n}\r\n","/**\r\n * Logger that writes to stderr only.\r\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\r\n * Any console.log() would corrupt the protocol. Always use this logger.\r\n */\r\nexport const logger = {\r\n info(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm] ${msg}`, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\r\n },\r\n debug(msg: string, ...args: unknown[]) {\r\n if (process.env.CLAUDE_ALARM_DEBUG) {\r\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n};\r\n","import path from 'node:path';\r\nimport os from 'node:os';\r\n\r\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\r\nexport const DEFAULT_HUB_PORT = 7900;\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\r\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\r\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\r\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\r\nexport const UPLOADS_DIR = path.join(CONFIG_DIR, 'uploads');\r\n\r\nexport const WS_PATH_CHANNEL = '/ws/channel';\r\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\r\n\r\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\r\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\r\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\r\n\r\nexport class SessionManager {\r\n private sessions = new Map<string, SessionInfo>();\r\n\r\n register(session: SessionInfo): void {\r\n // Auto-number duplicate names\r\n const baseName = session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n const existing = Array.from(this.sessions.values()).filter(\r\n s => s.id !== session.id && (s.cwd?.replace(/^.*[/\\\\]/, '') || s.name) === baseName,\r\n );\r\n if (existing.length > 0) {\r\n // Number this one\r\n const usedNums = existing.map(s => {\r\n const m = s.displayName?.match(/\\((\\d+)\\)$/);\r\n return m ? parseInt(m[1], 10) : 1;\r\n });\r\n // Ensure first existing session has (1) if it doesn't yet\r\n for (const s of existing) {\r\n if (!s.displayName?.match(/\\(\\d+\\)$/)) {\r\n s.displayName = `${baseName} (1)`;\r\n }\r\n }\r\n const nextNum = Math.max(...usedNums, 1) + 1;\r\n session.displayName = `${baseName} (${nextNum})`;\r\n } else {\r\n session.displayName = baseName;\r\n }\r\n this.sessions.set(session.id, { ...session });\r\n }\r\n\r\n unregister(sessionId: string): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n this.sessions.delete(sessionId);\r\n return session;\r\n }\r\n\r\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.status = status;\r\n session.lastActivity = Date.now();\r\n }\r\n return session;\r\n }\r\n\r\n updateActivity(sessionId: string): void {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.lastActivity = Date.now();\r\n }\r\n }\r\n\r\n get(sessionId: string): SessionInfo | undefined {\r\n return this.sessions.get(sessionId);\r\n }\r\n\r\n getAll(): SessionInfo[] {\r\n return Array.from(this.sessions.values());\r\n }\r\n\r\n count(): number {\r\n return this.sessions.size;\r\n }\r\n}\r\n","import notifier from 'node-notifier';\r\nimport { execFile } from 'node:child_process';\r\nimport { logger } from '../shared/logger.js';\r\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\r\nimport type { TelegramBot } from './telegram.js';\r\n\r\nexport class Notifier {\r\n private webhooks: WebhookConfig[] = [];\r\n private desktopEnabled = true;\r\n private notificationSettingsOpened = false;\r\n private dashboardUrl?: string;\r\n private telegramBot?: TelegramBot;\r\n\r\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string; telegramBot?: TelegramBot }): void {\r\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\r\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\r\n if (options.webhooks) this.webhooks = options.webhooks;\r\n if (options.telegramBot) this.telegramBot = options.telegramBot;\r\n }\r\n\r\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\r\n await this.notifyWithSession(undefined, undefined, title, message, level);\r\n }\r\n\r\n async notifyWithSession(sessionId: string | undefined, sessionLabel: string | undefined, title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\r\n const promises: Promise<void>[] = [];\r\n\r\n if (this.desktopEnabled) {\r\n promises.push(this.sendDesktop(title, message, level));\r\n }\r\n\r\n for (const webhook of this.webhooks) {\r\n promises.push(this.sendWebhook(webhook, title, message, level));\r\n }\r\n\r\n if (this.telegramBot && sessionId && sessionLabel) {\r\n promises.push(this.telegramBot.sendNotification(sessionId, sessionLabel, title, message));\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n }\r\n\r\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\r\n if (process.platform === 'win32') {\r\n // Check if notifications are enabled by running snoretoast directly\r\n const enabled = await this.checkWindowsNotifications();\r\n if (!enabled) {\r\n this.openNotificationSettings();\r\n return;\r\n }\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const notification = (notifier as any).notify(\r\n {\r\n title: `Claude Alarm: ${title}`,\r\n message,\r\n sound: true,\r\n wait: true,\r\n },\r\n (err: Error | null) => {\r\n if (err) {\r\n logger.warn(`Desktop notification failed: ${err.message}`);\r\n }\r\n resolve();\r\n },\r\n );\r\n\r\n // No click handler - dashboard is already open and notifications\r\n // can be clicked there to focus the relevant session\r\n });\r\n }\r\n\r\n private checkWindowsNotifications(): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n execFile(\r\n 'powershell',\r\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\r\n (err, stdout) => {\r\n if (err) { resolve(true); return; } // assume enabled on error\r\n const value = stdout.trim();\r\n resolve(value !== '0');\r\n },\r\n );\r\n });\r\n }\r\n\r\n private openNotificationSettings(): void {\r\n if (this.notificationSettingsOpened) return;\r\n this.notificationSettingsOpened = true;\r\n\r\n logger.warn('Windows notifications are disabled. Opening notification settings...');\r\n logger.warn('Please enable notifications for this app, then try again.');\r\n\r\n if (process.platform === 'win32') {\r\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\r\n }\r\n\r\n // Allow re-opening after 5 minutes\r\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\r\n }\r\n\r\n private async sendWebhook(\r\n webhook: WebhookConfig,\r\n title: string,\r\n message: string,\r\n level: NotifyLevel,\r\n ): Promise<void> {\r\n try {\r\n const response = await fetch(webhook.url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...webhook.headers,\r\n },\r\n body: JSON.stringify({\r\n title,\r\n message,\r\n level,\r\n timestamp: Date.now(),\r\n source: 'claude-alarm',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\r\n }\r\n } catch (err) {\r\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\r\n }\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { logger } from '../shared/logger.js';\r\nimport { UPLOADS_DIR } from '../shared/constants.js';\r\nimport type { TelegramConfig, SessionInfo } from '../shared/types.js';\r\n\r\nconst TELEGRAM_API = 'https://api.telegram.org/bot';\r\n\r\ninterface TelegramPhotoSize {\r\n file_id: string;\r\n file_unique_id: string;\r\n width: number;\r\n height: number;\r\n file_size?: number;\r\n}\r\n\r\ninterface TelegramMessage {\r\n message_id: number;\r\n chat: { id: number };\r\n text?: string;\r\n caption?: string;\r\n photo?: TelegramPhotoSize[];\r\n reply_to_message?: { message_id: number };\r\n}\r\n\r\ninterface TelegramCallbackQuery {\r\n id: string;\r\n from: { id: number };\r\n message?: TelegramMessage;\r\n data?: string;\r\n}\r\n\r\ninterface TelegramUpdate {\r\n update_id: number;\r\n message?: TelegramMessage;\r\n callback_query?: TelegramCallbackQuery;\r\n}\r\n\r\nexport class TelegramBot {\r\n private config: TelegramConfig;\r\n private offset = 0;\r\n private polling = false;\r\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\r\n\r\n // message_id -> sessionId mapping for reply-based routing\r\n private messageSessionMap = new Map<number, string>();\r\n\r\n // Callback: when a text message arrives from Telegram for a session\r\n public onMessageToSession?: (sessionId: string, content: string) => void;\r\n // Callback: when an image arrives from Telegram for a session\r\n public onImageToSession?: (sessionId: string, imagePath: string, mimeType: string, caption?: string) => void;\r\n // Callback: when a permission verdict arrives from Telegram\r\n public onPermissionVerdict?: (sessionId: string, requestId: string, behavior: 'allow' | 'deny') => void;\r\n // Callback: get current sessions list\r\n public getSessions?: () => SessionInfo[];\r\n // Pending messages for session selection\r\n private pendingMessages = new Map<number, { text?: string; photoFileId?: string; caption?: string }>(); // chatId -> pending\r\n\r\n constructor(config: TelegramConfig) {\r\n this.config = config;\r\n }\r\n\r\n private get apiUrl(): string {\r\n return `${TELEGRAM_API}${this.config.botToken}`;\r\n }\r\n\r\n /** Send a notification message to Telegram */\r\n async sendNotification(sessionId: string, _sessionLabel: string, title: string, message: string): Promise<void> {\r\n const text = `<b>${this.escHtml(title)}</b>\\n${this.mdToHtml(message)}`;\r\n const result = await this.sendMessage(text);\r\n if (result?.message_id) {\r\n this.messageSessionMap.set(result.message_id, sessionId);\r\n // Cleanup old mappings (keep last 200)\r\n if (this.messageSessionMap.size > 200) {\r\n const keys = [...this.messageSessionMap.keys()];\r\n for (let i = 0; i < keys.length - 200; i++) {\r\n this.messageSessionMap.delete(keys[i]);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** Send a text message to the configured chat */\r\n private async sendMessage(text: string, replyToMessageId?: number, replyMarkup?: unknown): Promise<{ message_id: number } | null> {\r\n try {\r\n const body: Record<string, unknown> = {\r\n chat_id: this.config.chatId,\r\n text,\r\n parse_mode: 'HTML',\r\n };\r\n if (replyToMessageId) body.reply_to_message_id = replyToMessageId;\r\n if (replyMarkup) body.reply_markup = replyMarkup;\r\n\r\n const res = await fetch(`${this.apiUrl}/sendMessage`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const err = await res.text();\r\n logger.warn(`Telegram sendMessage failed: ${res.status} ${err}`);\r\n return null;\r\n }\r\n\r\n const data = await res.json() as { ok: boolean; result: { message_id: number } };\r\n return data.ok ? data.result : null;\r\n } catch (err) {\r\n logger.warn(`Telegram sendMessage error: ${(err as Error).message}`);\r\n return null;\r\n }\r\n }\r\n\r\n /** Start long polling for incoming messages */\r\n startPolling(): void {\r\n if (this.polling) return;\r\n this.polling = true;\r\n logger.info('Telegram bot polling started');\r\n this.poll();\r\n }\r\n\r\n /** Stop polling */\r\n stopPolling(): void {\r\n this.polling = false;\r\n if (this.pollTimer) {\r\n clearTimeout(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n logger.info('Telegram bot polling stopped');\r\n }\r\n\r\n private async poll(): Promise<void> {\r\n if (!this.polling) return;\r\n\r\n try {\r\n const res = await fetch(`${this.apiUrl}/getUpdates?offset=${this.offset}&timeout=30`, {\r\n signal: AbortSignal.timeout(35000),\r\n });\r\n\r\n if (!res.ok) {\r\n logger.warn(`Telegram getUpdates failed: ${res.status}`);\r\n this.scheduleNextPoll(5000);\r\n return;\r\n }\r\n\r\n const data = await res.json() as { ok: boolean; result: TelegramUpdate[] };\r\n if (data.ok && data.result.length > 0) {\r\n for (const update of data.result) {\r\n this.offset = update.update_id + 1;\r\n if (update.callback_query) {\r\n this.handleCallbackQuery(update.callback_query);\r\n } else if (update.message) {\r\n this.handleIncomingMessage(update.message);\r\n }\r\n }\r\n }\r\n } catch (err) {\r\n if ((err as Error).name !== 'AbortError') {\r\n logger.warn(`Telegram poll error: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n this.scheduleNextPoll(1000);\r\n }\r\n\r\n private scheduleNextPoll(delay: number): void {\r\n if (!this.polling) return;\r\n this.pollTimer = setTimeout(() => this.poll(), delay);\r\n }\r\n\r\n private async handleIncomingMessage(msg: TelegramMessage): Promise<void> {\r\n // Only process messages from the configured chat\r\n if (String(msg.chat.id) !== String(this.config.chatId)) return;\r\n\r\n const hasPhoto = msg.photo && msg.photo.length > 0;\r\n const text = (msg.text || msg.caption || '').trim();\r\n\r\n if (!text && !hasPhoto) return;\r\n\r\n // Check if it's a reply to a known message\r\n if (msg.reply_to_message) {\r\n const sessionId = this.messageSessionMap.get(msg.reply_to_message.message_id);\r\n if (sessionId) {\r\n if (hasPhoto) {\r\n await this.deliverPhotoToSession(sessionId, msg.photo!, text);\r\n } else {\r\n this.deliverToSession(sessionId, text);\r\n }\r\n return;\r\n }\r\n }\r\n\r\n // Check if it's a session selection command: /s_<index>\r\n if (text) {\r\n const selectMatch = text.match(/^\\/s_(\\d+)$/);\r\n if (selectMatch) {\r\n const pending = this.pendingMessages.get(msg.chat.id);\r\n if (pending) {\r\n this.pendingMessages.delete(msg.chat.id);\r\n const sessions = this.getSessions?.() ?? [];\r\n const idx = parseInt(selectMatch[1], 10) - 1;\r\n if (idx >= 0 && idx < sessions.length) {\r\n if (pending.photoFileId) {\r\n await this.deliverPhotoToSessionByFileId(sessions[idx].id, pending.photoFileId, pending.caption);\r\n } else if (pending.text) {\r\n this.deliverToSession(sessions[idx].id, pending.text);\r\n }\r\n this.sendMessage(`Sent to [${this.getLabel(sessions[idx])}]`);\r\n } else {\r\n this.sendMessage('Invalid session number.');\r\n }\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // No reply context — check sessions count\r\n const sessions = this.getSessions?.() ?? [];\r\n\r\n if (sessions.length === 0) {\r\n this.sendMessage('No active sessions connected.');\r\n return;\r\n }\r\n\r\n if (sessions.length === 1) {\r\n if (hasPhoto) {\r\n await this.deliverPhotoToSession(sessions[0].id, msg.photo!, text);\r\n } else {\r\n this.deliverToSession(sessions[0].id, text);\r\n }\r\n return;\r\n }\r\n\r\n // Multiple sessions — ask user to pick with inline buttons\r\n if (hasPhoto) {\r\n const largest = msg.photo![msg.photo!.length - 1];\r\n this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });\r\n } else {\r\n this.pendingMessages.set(msg.chat.id, { text });\r\n }\r\n const buttons = sessions.map((s, i) => ({\r\n text: this.getLabel(s),\r\n callback_data: `sess:${i}:${msg.chat.id}`,\r\n }));\r\n // Arrange buttons in rows of 2\r\n const rows: Array<typeof buttons> = [];\r\n for (let i = 0; i < buttons.length; i += 2) {\r\n rows.push(buttons.slice(i, i + 2));\r\n }\r\n this.sendMessage('Multiple sessions active. Select one:', undefined, { inline_keyboard: rows });\r\n }\r\n\r\n private deliverToSession(sessionId: string, content: string): void {\r\n if (this.onMessageToSession) {\r\n this.onMessageToSession(sessionId, content);\r\n }\r\n }\r\n\r\n private async deliverPhotoToSession(sessionId: string, photos: TelegramPhotoSize[], caption?: string): Promise<void> {\r\n // Get the largest photo (last in array)\r\n const largest = photos[photos.length - 1];\r\n await this.deliverPhotoToSessionByFileId(sessionId, largest.file_id, caption);\r\n }\r\n\r\n private async deliverPhotoToSessionByFileId(sessionId: string, fileId: string, caption?: string): Promise<void> {\r\n try {\r\n // Get file path from Telegram\r\n const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);\r\n if (!fileRes.ok) { logger.warn('Failed to get Telegram file info'); return; }\r\n const fileData = await fileRes.json() as { ok: boolean; result: { file_path: string } };\r\n if (!fileData.ok) return;\r\n\r\n // Download the file\r\n const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;\r\n const imgRes = await fetch(downloadUrl);\r\n if (!imgRes.ok) { logger.warn('Failed to download Telegram photo'); return; }\r\n const buffer = Buffer.from(await imgRes.arrayBuffer());\r\n\r\n // Determine extension\r\n const ext = fileData.result.file_path.split('.').pop() || 'jpg';\r\n const mimeType = ext === 'png' ? 'image/png' : ext === 'gif' ? 'image/gif' : ext === 'webp' ? 'image/webp' : 'image/jpeg';\r\n\r\n // Save to uploads dir\r\n fs.mkdirSync(UPLOADS_DIR, { recursive: true });\r\n const filename = `${randomUUID()}.${ext}`;\r\n const filePath = path.join(UPLOADS_DIR, filename);\r\n fs.writeFileSync(filePath, buffer);\r\n logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);\r\n\r\n // Deliver to session\r\n if (this.onImageToSession) {\r\n this.onImageToSession(sessionId, filePath, mimeType, caption);\r\n }\r\n\r\n // Cleanup after 5 minutes\r\n setTimeout(() => { try { fs.unlinkSync(filePath); } catch {} }, 5 * 60 * 1000);\r\n } catch (err) {\r\n logger.warn(`Telegram photo download failed: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private getLabel(session: SessionInfo): string {\r\n return session.displayName || session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n }\r\n\r\n /** Send a permission request with inline buttons */\r\n async sendPermissionRequest(sessionId: string, sessionLabel: string, requestId: string, toolName: string, description: string, inputPreview: string): Promise<void> {\r\n // Parse inputPreview for readable display\r\n let preview = inputPreview;\r\n let truncated = false;\r\n try {\r\n const p = JSON.parse(inputPreview);\r\n if (p.command) preview = `$ ${p.command}`;\r\n else if (p.title && p.message) preview = p.message;\r\n else if (p.file_path) {\r\n preview = p.file_path;\r\n if (p.content) { preview += '\\n' + p.content.slice(0, 3000); if (p.content.length > 500) truncated = true; }\r\n } else if (p.content && typeof p.content === 'string') {\r\n preview = p.content.slice(0, 3000);\r\n if (p.content.length > 500) truncated = true;\r\n }\r\n } catch {\r\n truncated = true;\r\n const cmdMatch = inputPreview.match(/\"command\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n const contentMatch = inputPreview.match(/\"content\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n if (cmdMatch) preview = `$ ${cmdMatch[1]}`;\r\n else if (contentMatch) preview = contentMatch[1].slice(0, 3000);\r\n }\r\n\r\n const truncNote = truncated ? '\\n\\n<i>...truncated</i>' : '';\r\n const previewSlice = preview.slice(0, 3000);\r\n // Use <code> for short single-line (commands), plain text for longer content\r\n const isShort = !previewSlice.includes('\\n') && previewSlice.length < 100;\r\n const previewHtml = isShort\r\n ? `<code>${this.escHtml(previewSlice)}</code>`\r\n : this.escHtml(previewSlice);\r\n // Friendly tool name for Telegram — use title for notify tools\r\n let displayTool = toolName;\r\n if ((toolName.endsWith('__notify') || toolName === 'notify') && inputPreview) {\r\n try { const pp = JSON.parse(inputPreview); if (pp.title) displayTool = pp.title; } catch {\r\n const tm = inputPreview.match(/\"title\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n if (tm) displayTool = tm[1];\r\n }\r\n } else {\r\n const mcpMatch = toolName.match(/__([^_]+)$/);\r\n if (mcpMatch) displayTool = mcpMatch[1].charAt(0).toUpperCase() + mcpMatch[1].slice(1);\r\n }\r\n\r\n const text = `⚠️ <b>Permission Request</b> — ${this.escHtml(sessionLabel)}\\n\\n` +\r\n `🔧 <b>${this.escHtml(displayTool)}</b>\\n` +\r\n `${previewHtml}${truncNote}`;\r\n\r\n const replyMarkup = {\r\n inline_keyboard: [[\r\n { text: '✅ Allow', callback_data: `perm:allow:${sessionId}:${requestId}` },\r\n { text: '❌ Deny', callback_data: `perm:deny:${sessionId}:${requestId}` },\r\n ]],\r\n };\r\n\r\n await this.sendMessage(text, undefined, replyMarkup);\r\n }\r\n\r\n private async handleCallbackQuery(query: TelegramCallbackQuery): Promise<void> {\r\n if (!query.data) return;\r\n\r\n if (query.data.startsWith('sess:')) {\r\n await this.handleSessionSelectCallback(query);\r\n return;\r\n }\r\n\r\n if (!query.data.startsWith('perm:')) return;\r\n\r\n const parts = query.data.split(':');\r\n if (parts.length < 4) return;\r\n const [, action, sessionId, requestId] = parts;\r\n const behavior = action === 'allow' ? 'allow' : 'deny';\r\n\r\n // Send verdict\r\n if (this.onPermissionVerdict) {\r\n this.onPermissionVerdict(sessionId, requestId, behavior as 'allow' | 'deny');\r\n }\r\n\r\n // Answer callback to remove loading state\r\n await this.answerCallbackQuery(query.id, behavior === 'allow' ? '✅ Allowed' : '❌ Denied');\r\n\r\n // Update message to show result (use escaped original text since it's plain)\r\n if (query.message) {\r\n const label = behavior === 'allow' ? '✅ <b>Allowed</b>' : '❌ <b>Denied</b>';\r\n const original = this.escHtml(query.message.text || '');\r\n await this.editMessageText(query.message.chat.id, query.message.message_id, original + `\\n\\n${label}`);\r\n }\r\n }\r\n\r\n private async handleSessionSelectCallback(query: TelegramCallbackQuery): Promise<void> {\r\n const parts = query.data!.split(':');\r\n if (parts.length < 3) return;\r\n const [, idxStr, chatIdStr] = parts;\r\n const idx = parseInt(idxStr, 10);\r\n const chatId = parseInt(chatIdStr, 10);\r\n\r\n const sessions = this.getSessions?.() ?? [];\r\n if (idx < 0 || idx >= sessions.length) {\r\n await this.answerCallbackQuery(query.id, 'Session not found');\r\n return;\r\n }\r\n\r\n const session = sessions[idx];\r\n const pending = this.pendingMessages.get(chatId);\r\n this.pendingMessages.delete(chatId);\r\n\r\n if (pending) {\r\n if (pending.photoFileId) {\r\n await this.deliverPhotoToSessionByFileId(session.id, pending.photoFileId, pending.caption);\r\n } else if (pending.text) {\r\n this.deliverToSession(session.id, pending.text);\r\n }\r\n }\r\n\r\n await this.answerCallbackQuery(query.id, `Sent to ${this.getLabel(session)}`);\r\n // Update message to show which session was selected\r\n if (query.message) {\r\n await this.editMessageText(query.message.chat.id, query.message.message_id, `✅ Sent to <b>${this.escHtml(this.getLabel(session))}</b>`);\r\n }\r\n }\r\n\r\n private async answerCallbackQuery(callbackQueryId: string, text: string): Promise<void> {\r\n try {\r\n await fetch(`${this.apiUrl}/answerCallbackQuery`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ callback_query_id: callbackQueryId, text }),\r\n });\r\n } catch (err) {\r\n logger.warn(`Telegram answerCallbackQuery error: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private async editMessageText(chatId: number, messageId: number, text: string): Promise<void> {\r\n try {\r\n await fetch(`${this.apiUrl}/editMessageText`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ chat_id: chatId, message_id: messageId, text, parse_mode: 'HTML' }),\r\n });\r\n } catch (err) {\r\n logger.warn(`Telegram editMessageText error: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private escHtml(s: string): string {\r\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\r\n }\r\n\r\n /** Convert markdown to Telegram HTML (escape first, then apply formatting) */\r\n private mdToHtml(s: string): string {\r\n // Handle tables before escaping (convert to clean text format)\r\n let text = s.replace(\r\n /^(\\|.+\\|)\\n\\|[-| :]+\\|\\n((?:\\|.+\\|\\n?)*)/gm,\r\n (_match, header: string, body: string) => {\r\n const headerCells = header.split('|').filter((c: string) => c.trim()).map((c: string) => c.trim());\r\n const headerLine = headerCells.join(' | ');\r\n const bodyLines = body.trim().split('\\n').map((row: string) => {\r\n return row.split('|').filter((c: string) => c.trim()).map((c: string) => c.trim()).join(' | ');\r\n });\r\n return `**${headerLine}**\\n${bodyLines.join('\\n')}`;\r\n },\r\n );\r\n let html = this.escHtml(text);\r\n // Code blocks: ```...```\r\n html = html.replace(/```(?:\\w*)\\n?([\\s\\S]*?)```/g, '<pre>$1</pre>');\r\n // Inline code: `...`\r\n html = html.replace(/`([^`]+)`/g, '<code>$1</code>');\r\n // Headings: # / ## / ### → bold (Telegram has no heading tags)\r\n html = html.replace(/^#{1,3}\\s+(.+)$/gm, '<b>$1</b>');\r\n // Bold: **...**\r\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>');\r\n // Italic: *...*\r\n html = html.replace(/\\*(.+?)\\*/g, '<i>$1</i>');\r\n return html;\r\n }\r\n\r\n /** Update config (e.g., from dashboard settings) */\r\n updateConfig(config: TelegramConfig): void {\r\n const wasPolling = this.polling;\r\n if (wasPolling) this.stopPolling();\r\n this.config = config;\r\n if (wasPolling && config.enabled) this.startPolling();\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\r\nimport type { AppConfig } from './types.js';\r\n\r\nconst DEFAULT_CONFIG: AppConfig = {\r\n hub: {\r\n host: DEFAULT_HUB_HOST,\r\n port: DEFAULT_HUB_PORT,\r\n },\r\n notifications: {\r\n desktop: true,\r\n sound: true,\r\n },\r\n webhooks: [],\r\n};\r\n\r\nexport function ensureConfigDir(): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nexport function loadConfig(): AppConfig {\r\n ensureConfigDir();\r\n let config: AppConfig;\r\n if (!fs.existsSync(CONFIG_FILE)) {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n } else {\r\n try {\r\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\r\n const parsed = JSON.parse(raw);\r\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...(parsed.telegram ? { telegram: parsed.telegram } : {}) };\r\n } catch {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n }\r\n }\r\n\r\n // Auto-generate token if missing\r\n if (!config.hub.token) {\r\n config.hub.token = randomUUID();\r\n saveConfig(config);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n/** Get the current token, generating one if needed */\r\nexport function getOrCreateToken(): string {\r\n const config = loadConfig();\r\n return config.hub.token!;\r\n}\r\n\r\nexport function saveConfig(config: AppConfig): void {\r\n ensureConfigDir();\r\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\r\n}\r\n\r\n/**\r\n * Add claude-alarm as an MCP channel server to .mcp.json\r\n */\r\nexport function setupMcpConfig(targetDir?: string): string {\r\n const dir = targetDir ?? process.cwd();\r\n const mcpPath = path.join(dir, '.mcp.json');\r\n\r\n let mcpConfig: Record<string, any> = {};\r\n if (fs.existsSync(mcpPath)) {\r\n try {\r\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\r\n } catch {\r\n mcpConfig = {};\r\n }\r\n }\r\n\r\n if (!mcpConfig.mcpServers) {\r\n mcpConfig.mcpServers = {};\r\n }\r\n\r\n mcpConfig.mcpServers['claude-alarm'] = {\r\n command: 'npx',\r\n args: ['-y', '@delt/claude-alarm', 'serve'],\r\n env: {\r\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\r\n },\r\n };\r\n\r\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\r\n return mcpPath;\r\n}\r\n"],"mappings":"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","names":["fs","path","randomUUID","path","sessions","path","fs","path","randomUUID","fs","randomUUID","fs","path","fs","randomUUID"]}
|
|
1
|
+
{"version":3,"sources":["../../src/hub/server.ts","../../src/shared/logger.ts","../../src/shared/constants.ts","../../src/hub/session-manager.ts","../../src/hub/notifier.ts","../../src/hub/telegram.ts","../../src/shared/config.ts"],"sourcesContent":["import http from 'node:http';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { WebSocketServer, WebSocket } from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { randomUUID } from 'node:crypto';\r\nimport {\r\n DEFAULT_HUB_HOST,\r\n DEFAULT_HUB_PORT,\r\n WS_PATH_CHANNEL,\r\n WS_PATH_DASHBOARD,\r\n UPLOADS_DIR,\r\n} from '../shared/constants.js';\r\nimport { SessionManager } from './session-manager.js';\r\nimport { Notifier } from './notifier.js';\r\nimport { TelegramBot } from './telegram.js';\r\nimport { loadConfig, saveConfig } from '../shared/config.js';\r\nimport type { ChannelMessage, AppConfig, SessionInfo, WebhookConfig, TelegramConfig } from '../shared/types.js';\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nexport class HubServer {\r\n private httpServer: http.Server;\r\n private wssChannel: WebSocketServer;\r\n private wssDashboard: WebSocketServer;\r\n private sessions = new SessionManager();\r\n private notifier = new Notifier();\r\n private startTime = Date.now();\r\n\r\n // Map sessionId -> channel WebSocket\r\n private channelSockets = new Map<string, WebSocket>();\r\n // Track which channel connections are local\r\n private localChannels = new Set<string>();\r\n // All connected dashboard WebSockets\r\n private dashboardSockets = new Set<WebSocket>();\r\n\r\n private telegramBot?: TelegramBot;\r\n private heartbeatInterval?: ReturnType<typeof setInterval>;\r\n private channelAlive = new Map<string, boolean>(); // sessionId -> alive flag\r\n\r\n private host: string;\r\n private port: number;\r\n private token?: string;\r\n\r\n constructor(config?: Partial<AppConfig>) {\r\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\r\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\r\n this.token = config?.hub?.token;\r\n\r\n if (config?.notifications) {\r\n this.notifier.configure({\r\n desktop: config.notifications.desktop,\r\n });\r\n }\r\n if (config?.webhooks) {\r\n this.notifier.configure({ webhooks: config.webhooks });\r\n }\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\r\n\r\n // Initialize Telegram bot if configured\r\n const fullConfig = loadConfig();\r\n if (fullConfig.telegram?.enabled && fullConfig.telegram.botToken && fullConfig.telegram.chatId) {\r\n this.initTelegram(fullConfig.telegram);\r\n }\r\n\r\n // HTTP Server\r\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\r\n\r\n // WebSocket for channel servers\r\n this.wssChannel = new WebSocketServer({ noServer: true });\r\n this.wssChannel.on('connection', (ws: WebSocket, req: http.IncomingMessage) => this.handleChannelConnection(ws, req));\r\n\r\n // WebSocket for dashboard\r\n this.wssDashboard = new WebSocketServer({ noServer: true });\r\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\r\n\r\n // Route WebSocket upgrade requests\r\n this.httpServer.on('upgrade', (req, socket, head) => {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n const pathname = url.pathname;\r\n\r\n // Token auth for WebSocket connections (skip for local requests)\r\n if (this.token && !this.isLocalRequest(req)) {\r\n const wsToken = url.searchParams.get('token');\r\n if (wsToken !== this.token) {\r\n socket.destroy();\r\n return;\r\n }\r\n }\r\n\r\n if (pathname === WS_PATH_CHANNEL) {\r\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssChannel.emit('connection', ws, req);\r\n });\r\n } else if (pathname === WS_PATH_DASHBOARD) {\r\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssDashboard.emit('connection', ws, req);\r\n });\r\n } else {\r\n socket.destroy();\r\n }\r\n });\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.cleanupUploads();\r\n this.startHeartbeat();\r\n return new Promise((resolve, reject) => {\r\n this.httpServer.on('error', reject);\r\n this.httpServer.listen(this.port, this.host, () => {\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n stop(): Promise<void> {\r\n return new Promise((resolve) => {\r\n // Stop heartbeat\r\n if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);\r\n\r\n // Stop telegram bot\r\n if (this.telegramBot) this.telegramBot.stopPolling();\r\n\r\n // Force-close all WebSocket connections\r\n for (const ws of this.channelSockets.values()) ws.terminate();\r\n for (const ws of this.dashboardSockets) ws.terminate();\r\n this.channelSockets.clear();\r\n this.dashboardSockets.clear();\r\n\r\n this.wssChannel.close();\r\n this.wssDashboard.close();\r\n this.httpServer.close(() => {\r\n logger.info('Hub server stopped');\r\n resolve();\r\n });\r\n\r\n // Force resolve after 3 seconds if server won't close\r\n setTimeout(() => {\r\n logger.warn('Force shutting down');\r\n resolve();\r\n }, 3000);\r\n });\r\n }\r\n\r\n // --- HTTP Handler ---\r\n\r\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n\r\n // CORS headers - restrict to same origin\r\n const origin = req.headers.origin;\r\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\r\n res.setHeader('Access-Control-Allow-Origin', origin);\r\n }\r\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');\r\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\r\n\r\n if (req.method === 'OPTIONS') {\r\n res.writeHead(204);\r\n res.end();\r\n return;\r\n }\r\n\r\n // Token auth for API endpoints (skip dashboard HTML serving)\r\n if (url.pathname !== '/' && this.token) {\r\n if (!this.isLocalRequest(req)) {\r\n const authHeader = req.headers['authorization'];\r\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\r\n if (bearerToken !== this.token) {\r\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Route\r\n if (url.pathname === '/' && req.method === 'GET') {\r\n this.serveDashboard(res);\r\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\r\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, {\r\n running: true,\r\n pid: process.pid,\r\n port: this.port,\r\n sessions: this.sessions.count(),\r\n uptime: Date.now() - this.startTime,\r\n });\r\n } else if (url.pathname.startsWith('/api/sessions/') && req.method === 'DELETE') {\r\n const sessionId = url.pathname.slice('/api/sessions/'.length);\r\n const ws = this.channelSockets.get(sessionId);\r\n if (ws) { ws.terminate(); }\r\n const session = this.sessions.unregister(sessionId);\r\n this.channelSockets.delete(sessionId);\r\n this.localChannels.delete(sessionId);\r\n this.channelAlive.delete(sessionId);\r\n if (session) {\r\n this.broadcastToDashboards({ type: 'session_disconnected', sessionId });\r\n this.jsonResponse(res, 200, { ok: true });\r\n } else {\r\n this.jsonResponse(res, 404, { error: 'Session not found' });\r\n }\r\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\r\n this.handleApiSend(req, res);\r\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\r\n this.handleApiNotify(req, res);\r\n } else if (url.pathname === '/api/webhooks' && req.method === 'GET') {\r\n const config = loadConfig();\r\n this.jsonResponse(res, 200, { webhooks: config.webhooks || [] });\r\n } else if (url.pathname === '/api/webhooks' && req.method === 'POST') {\r\n this.handleWebhookSave(req, res);\r\n } else if (url.pathname === '/api/telegram' && req.method === 'GET') {\r\n const cfg = loadConfig();\r\n const tg = cfg.telegram ?? { botToken: '', chatId: '', enabled: false };\r\n // Mask bot token for security\r\n this.jsonResponse(res, 200, {\r\n telegram: { ...tg, botToken: tg.botToken ? `${tg.botToken.slice(0, 8)}...` : '' },\r\n });\r\n } else if (url.pathname === '/api/telegram' && req.method === 'POST') {\r\n this.handleTelegramSave(req, res);\r\n } else if (url.pathname === '/api/telegram/test' && req.method === 'POST') {\r\n this.handleTelegramTest(req, res);\r\n } else if (url.pathname === '/api/telegram/detect' && req.method === 'POST') {\r\n this.handleTelegramDetect(req, res);\r\n } else {\r\n this.jsonResponse(res, 404, { error: 'Not found' });\r\n }\r\n }\r\n\r\n private serveDashboard(res: http.ServerResponse): void {\r\n // Look for dashboard HTML relative to this file (dist) or source\r\n const candidates = [\r\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\r\n path.join(__dirname, 'dashboard', 'index.html'), // from dist/ (bundled index.js)\r\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\r\n path.join(__dirname, '..', 'src', 'dashboard', 'index.html'), // from dist/ -> src/\r\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\r\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\r\n ];\r\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\r\n\r\n for (const candidate of candidates) {\r\n if (fs.existsSync(candidate)) {\r\n const html = fs.readFileSync(candidate, 'utf-8');\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end(html);\r\n return;\r\n }\r\n }\r\n\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\r\n }\r\n\r\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { sessionId, content } = body as { sessionId?: string; content?: string };\r\n if (!sessionId || !content) {\r\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\r\n return;\r\n }\r\n\r\n const ws = this.channelSockets.get(sessionId);\r\n if (!ws || ws.readyState !== WebSocket.OPEN) {\r\n this.jsonResponse(res, 404, { error: 'Session not connected' });\r\n return;\r\n }\r\n\r\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\r\n ws.send(JSON.stringify(msg));\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\r\n if (!title || !message) {\r\n this.jsonResponse(res, 400, { error: 'title and message are required' });\r\n return;\r\n }\r\n\r\n await this.notifier.notify(title, message, (level as any) ?? 'info');\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n // --- Channel WebSocket ---\r\n\r\n private handleChannelConnection(ws: WebSocket, req: http.IncomingMessage): void {\r\n const isLocal = this.isLocalRequest(req);\r\n logger.info(`Channel server connected (local: ${isLocal})`);\r\n\r\n // Track pong responses for heartbeat\r\n ws.on('pong', () => {\r\n for (const [sessionId, sock] of this.channelSockets) {\r\n if (sock === ws) { this.channelAlive.set(sessionId, true); break; }\r\n }\r\n });\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n this.handleChannelMessage(ws, isLocal, msg);\r\n } catch {\r\n logger.warn('Invalid message from channel');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n // Find and remove the session for this socket\r\n for (const [sessionId, sock] of this.channelSockets) {\r\n if (sock === ws) {\r\n const session = this.sessions.unregister(sessionId);\r\n this.channelSockets.delete(sessionId);\r\n this.localChannels.delete(sessionId);\r\n this.channelAlive.delete(sessionId);\r\n logger.info(`Channel disconnected: ${sessionId}`);\r\n this.broadcastToDashboards({\r\n type: 'session_disconnected',\r\n sessionId,\r\n });\r\n break;\r\n }\r\n }\r\n });\r\n }\r\n\r\n private handleChannelMessage(ws: WebSocket, isLocal: boolean, msg: ChannelMessage): void {\r\n switch (msg.type) {\r\n case 'register': {\r\n const session = msg.session;\r\n session.isLocal = isLocal;\r\n const isReregister = !!this.sessions.get(session.id);\r\n this.sessions.register(session);\r\n this.channelSockets.set(session.id, ws);\r\n if (isLocal) this.localChannels.add(session.id);\r\n logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);\r\n this.broadcastToDashboards({\r\n type: isReregister ? 'session_updated' : 'session_connected',\r\n session,\r\n });\r\n break;\r\n }\r\n\r\n case 'status': {\r\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\r\n if (updated) {\r\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\r\n }\r\n break;\r\n }\r\n\r\n case 'notify': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const notifySession = this.sessions.get(msg.sessionId);\r\n const notifyLabel = this.getSessionLabel(notifySession);\r\n this.notifier.notifyWithSession(msg.sessionId, notifyLabel, `[${notifyLabel}] ${msg.title}`, msg.message, msg.level ?? 'info');\r\n this.broadcastToDashboards({\r\n type: 'notification',\r\n sessionId: msg.sessionId,\r\n title: msg.title,\r\n message: msg.message,\r\n level: msg.level,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n\r\n case 'reply': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const replySession = this.sessions.get(msg.sessionId);\r\n const replyLabel = this.getSessionLabel(replySession);\r\n this.notifier.notifyWithSession(msg.sessionId, replyLabel, `[${replyLabel}] Reply`, msg.content.slice(0, 3000), 'info');\r\n this.broadcastToDashboards({\r\n type: 'reply_from_session',\r\n sessionId: msg.sessionId,\r\n content: msg.content,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n\r\n case 'permission_request': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n logger.info(`Permission request [${msg.requestId}] from ${msg.sessionId}: ${msg.toolName}`);\r\n this.broadcastToDashboards({\r\n type: 'permission_request',\r\n sessionId: msg.sessionId,\r\n requestId: msg.requestId,\r\n toolName: msg.toolName,\r\n description: msg.description,\r\n inputPreview: msg.inputPreview,\r\n timestamp: msg.timestamp,\r\n });\r\n // Forward to Telegram\r\n if (this.telegramBot) {\r\n const session = this.sessions.get(msg.sessionId);\r\n const label = this.getSessionLabel(session);\r\n this.telegramBot.sendPermissionRequest(msg.sessionId, label, msg.requestId, msg.toolName, msg.description, msg.inputPreview);\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // --- Dashboard WebSocket ---\r\n\r\n private handleDashboardConnection(ws: WebSocket): void {\r\n this.dashboardSockets.add(ws);\r\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\r\n\r\n // Send current session list\r\n const sessionsMsg: ChannelMessage = {\r\n type: 'sessions_list',\r\n sessions: this.sessions.getAll(),\r\n };\r\n ws.send(JSON.stringify(sessionsMsg));\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n if (msg.type === 'message_to_session') {\r\n const channelWs = this.channelSockets.get(msg.sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n channelWs.send(JSON.stringify(msg));\r\n }\r\n } else if (msg.type === 'image_upload') {\r\n this.handleImageUpload(msg);\r\n } else if (msg.type === 'permission_response') {\r\n const channelWs = this.channelSockets.get(msg.sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Permission verdict [${msg.requestId}]: ${msg.behavior} -> session ${msg.sessionId}`);\r\n }\r\n }\r\n } catch {\r\n logger.warn('Invalid message from dashboard');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n this.dashboardSockets.delete(ws);\r\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\r\n });\r\n }\r\n\r\n // --- Helpers ---\r\n\r\n private broadcastToDashboards(msg: ChannelMessage): void {\r\n const payload = JSON.stringify(msg);\r\n for (const ws of this.dashboardSockets) {\r\n if (ws.readyState === WebSocket.OPEN) {\r\n ws.send(payload);\r\n }\r\n }\r\n }\r\n\r\n private handleImageUpload(msg: ChannelMessage & { type: 'image_upload' }): void {\r\n const { sessionId, imageData, mimeType, originalName, content } = msg;\r\n\r\n // Only allow for local sessions\r\n if (!this.localChannels.has(sessionId)) {\r\n logger.warn(`Image upload rejected: session ${sessionId} is not local`);\r\n return;\r\n }\r\n\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (!channelWs || channelWs.readyState !== WebSocket.OPEN) return;\r\n\r\n // Validate mime type\r\n const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];\r\n if (!allowedTypes.includes(mimeType)) return;\r\n\r\n // Extract base64 data (remove data URL prefix if present)\r\n const base64Data = imageData.replace(/^data:image\\/\\w+;base64,/, '');\r\n const buffer = Buffer.from(base64Data, 'base64');\r\n\r\n // Validate size (10MB max)\r\n if (buffer.length > 10 * 1024 * 1024) {\r\n logger.warn('Image upload rejected: exceeds 10MB');\r\n return;\r\n }\r\n\r\n // Save to uploads dir\r\n fs.mkdirSync(UPLOADS_DIR, { recursive: true });\r\n const ext = mimeType.split('/')[1] === 'jpeg' ? 'jpg' : mimeType.split('/')[1];\r\n const filename = `${randomUUID()}.${ext}`;\r\n const filePath = path.join(UPLOADS_DIR, filename);\r\n fs.writeFileSync(filePath, buffer);\r\n\r\n // Forward file path to channel\r\n const forwardMsg: ChannelMessage = {\r\n type: 'image_to_session',\r\n sessionId,\r\n imagePath: filePath,\r\n mimeType,\r\n originalName,\r\n content,\r\n };\r\n channelWs.send(JSON.stringify(forwardMsg));\r\n logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);\r\n\r\n // Cleanup after 5 minutes\r\n setTimeout(() => {\r\n try { fs.unlinkSync(filePath); } catch {}\r\n }, 5 * 60 * 1000);\r\n }\r\n\r\n private async handleWebhookSave(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { webhooks } = body as { webhooks?: WebhookConfig[] };\r\n if (!Array.isArray(webhooks)) { this.jsonResponse(res, 400, { error: 'webhooks must be an array' }); return; }\r\n const config = loadConfig();\r\n config.webhooks = webhooks;\r\n saveConfig(config);\r\n this.notifier.configure({ webhooks });\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private initTelegram(config: TelegramConfig): void {\r\n this.telegramBot = new TelegramBot(config);\r\n this.telegramBot.getSessions = () => this.sessions.getAll();\r\n this.telegramBot.onMessageToSession = (sessionId, content) => {\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Telegram message forwarded to session: ${sessionId}`);\r\n }\r\n };\r\n this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n const msg: ChannelMessage = { type: 'image_to_session', sessionId, imagePath, mimeType, content: caption };\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Telegram photo forwarded to session: ${sessionId}`);\r\n }\r\n };\r\n this.telegramBot.onPermissionVerdict = (sessionId, requestId, behavior) => {\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n const msg: ChannelMessage = { type: 'permission_response', sessionId, requestId, behavior };\r\n channelWs.send(JSON.stringify(msg));\r\n logger.info(`Telegram permission verdict [${requestId}]: ${behavior} -> session ${sessionId}`);\r\n }\r\n // Also notify dashboards so they can dismiss the permission bar\r\n this.broadcastToDashboards({ type: 'permission_response', sessionId, requestId, behavior });\r\n };\r\n this.notifier.configure({ telegramBot: this.telegramBot });\r\n this.telegramBot.startPolling();\r\n logger.info('Telegram bot initialized');\r\n }\r\n\r\n private async handleTelegramSave(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { telegram } = body as { telegram?: TelegramConfig };\r\n if (!telegram) { this.jsonResponse(res, 400, { error: 'telegram config required' }); return; }\r\n\r\n const config = loadConfig();\r\n // If botToken is masked (contains '...'), keep the existing token\r\n if (telegram.botToken.includes('...') && config.telegram?.botToken) {\r\n telegram.botToken = config.telegram.botToken;\r\n }\r\n config.telegram = telegram;\r\n saveConfig(config);\r\n\r\n // Stop existing bot if running\r\n if (this.telegramBot) {\r\n this.telegramBot.stopPolling();\r\n this.telegramBot = undefined;\r\n this.notifier.configure({ telegramBot: undefined as any });\r\n }\r\n\r\n // Start new bot if enabled\r\n if (telegram.enabled && telegram.botToken && telegram.chatId) {\r\n this.initTelegram(telegram);\r\n }\r\n\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private async handleTelegramTest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { botToken, chatId } = body as { botToken?: string; chatId?: string };\r\n if (!botToken || !chatId) {\r\n this.jsonResponse(res, 400, { error: 'botToken and chatId required' });\r\n return;\r\n }\r\n\r\n try {\r\n const testRes = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n chat_id: chatId,\r\n text: 'Claude Alarm test message! Connection successful.',\r\n }),\r\n });\r\n\r\n if (testRes.ok) {\r\n this.jsonResponse(res, 200, { ok: true });\r\n } else {\r\n const err = await testRes.json() as { description?: string };\r\n this.jsonResponse(res, 400, { error: (err as any).description || 'Telegram API error' });\r\n }\r\n } catch (err) {\r\n this.jsonResponse(res, 500, { error: (err as Error).message });\r\n }\r\n }\r\n\r\n private async handleTelegramDetect(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { botToken } = body as { botToken?: string };\r\n if (!botToken) {\r\n this.jsonResponse(res, 400, { error: 'botToken required' });\r\n return;\r\n }\r\n\r\n try {\r\n const detectRes = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates?timeout=0&limit=10`, {\r\n signal: AbortSignal.timeout(10000),\r\n });\r\n\r\n if (!detectRes.ok) {\r\n const err = await detectRes.json() as { description?: string };\r\n this.jsonResponse(res, 400, { error: (err as any).description || 'Invalid bot token' });\r\n return;\r\n }\r\n\r\n const data = await detectRes.json() as { ok: boolean; result: Array<{ message?: { chat: { id: number; first_name?: string; title?: string; type: string } } }> };\r\n if (!data.ok || !data.result.length) {\r\n this.jsonResponse(res, 200, { ok: false, chats: [] });\r\n return;\r\n }\r\n\r\n // Extract unique chats\r\n const chatMap = new Map<string, { id: string; name: string; type: string }>();\r\n for (const update of data.result) {\r\n if (update.message?.chat) {\r\n const chat = update.message.chat;\r\n const id = String(chat.id);\r\n if (!chatMap.has(id)) {\r\n chatMap.set(id, {\r\n id,\r\n name: chat.title || chat.first_name || id,\r\n type: chat.type,\r\n });\r\n }\r\n }\r\n }\r\n\r\n this.jsonResponse(res, 200, { ok: true, chats: [...chatMap.values()] });\r\n } catch (err) {\r\n this.jsonResponse(res, 500, { error: (err as Error).message });\r\n }\r\n }\r\n\r\n private startHeartbeat(): void {\r\n // Ping channel WebSockets every 30s, terminate unresponsive ones\r\n this.heartbeatInterval = setInterval(() => {\r\n for (const [sessionId, ws] of this.channelSockets) {\r\n if (this.channelAlive.get(sessionId) === false) {\r\n // No pong received since last ping — terminate\r\n logger.info(`Heartbeat timeout, terminating session: ${sessionId}`);\r\n ws.terminate();\r\n continue;\r\n }\r\n this.channelAlive.set(sessionId, false);\r\n ws.ping();\r\n }\r\n }, 30000);\r\n }\r\n\r\n private cleanupUploads(): void {\r\n try {\r\n if (!fs.existsSync(UPLOADS_DIR)) return;\r\n const files = fs.readdirSync(UPLOADS_DIR);\r\n for (const file of files) {\r\n try { fs.unlinkSync(path.join(UPLOADS_DIR, file)); } catch {}\r\n }\r\n if (files.length > 0) logger.info(`Cleaned up ${files.length} leftover upload(s)`);\r\n } catch {}\r\n }\r\n\r\n private getSessionLabel(session?: SessionInfo): string {\r\n if (!session) return 'unknown';\r\n return session.displayName || session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n }\r\n\r\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' });\r\n res.end(JSON.stringify(body));\r\n }\r\n\r\n private isLocalRequest(req: http.IncomingMessage): boolean {\r\n const addr = req.socket.remoteAddress;\r\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\r\n }\r\n\r\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\r\n return new Promise((resolve) => {\r\n let data = '';\r\n let size = 0;\r\n req.on('data', (chunk) => {\r\n size += chunk.length;\r\n if (size > maxSize) {\r\n req.destroy();\r\n resolve(null);\r\n return;\r\n }\r\n data += chunk;\r\n });\r\n req.on('end', () => {\r\n try {\r\n resolve(JSON.parse(data));\r\n } catch {\r\n resolve(null);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n\r\n// Direct execution support\r\nif (process.argv[1] && (\r\n process.argv[1].endsWith('hub/server.js') ||\r\n process.argv[1].endsWith('hub/server.ts')\r\n)) {\r\n const config = loadConfig();\r\n const hub = new HubServer(config);\r\n hub.start().catch((err) => {\r\n logger.error('Failed to start hub:', err);\r\n process.exit(1);\r\n });\r\n\r\n const shutdown = () => {\r\n hub.stop().then(() => process.exit(0));\r\n };\r\n process.on('SIGINT', shutdown);\r\n process.on('SIGTERM', shutdown);\r\n}\r\n","/**\r\n * Logger that writes to stderr only.\r\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\r\n * Any console.log() would corrupt the protocol. Always use this logger.\r\n */\r\nexport const logger = {\r\n info(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm] ${msg}`, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\r\n },\r\n debug(msg: string, ...args: unknown[]) {\r\n if (process.env.CLAUDE_ALARM_DEBUG) {\r\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n};\r\n","import path from 'node:path';\r\nimport os from 'node:os';\r\n\r\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\r\nexport const DEFAULT_HUB_PORT = 7900;\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\r\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\r\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\r\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\r\nexport const UPLOADS_DIR = path.join(CONFIG_DIR, 'uploads');\r\n\r\nexport const WS_PATH_CHANNEL = '/ws/channel';\r\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\r\n\r\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\r\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\r\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\r\n\r\nexport class SessionManager {\r\n private sessions = new Map<string, SessionInfo>();\r\n\r\n register(session: SessionInfo): void {\r\n // Auto-number duplicate names\r\n const baseName = session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n const existing = Array.from(this.sessions.values()).filter(\r\n s => s.id !== session.id && (s.cwd?.replace(/^.*[/\\\\]/, '') || s.name) === baseName,\r\n );\r\n if (existing.length > 0) {\r\n // Number this one\r\n const usedNums = existing.map(s => {\r\n const m = s.displayName?.match(/\\((\\d+)\\)$/);\r\n return m ? parseInt(m[1], 10) : 1;\r\n });\r\n // Ensure first existing session has (1) if it doesn't yet\r\n for (const s of existing) {\r\n if (!s.displayName?.match(/\\(\\d+\\)$/)) {\r\n s.displayName = `${baseName} (1)`;\r\n }\r\n }\r\n const nextNum = Math.max(...usedNums, 1) + 1;\r\n session.displayName = `${baseName} (${nextNum})`;\r\n } else {\r\n session.displayName = baseName;\r\n }\r\n this.sessions.set(session.id, { ...session });\r\n }\r\n\r\n unregister(sessionId: string): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n this.sessions.delete(sessionId);\r\n return session;\r\n }\r\n\r\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.status = status;\r\n session.lastActivity = Date.now();\r\n }\r\n return session;\r\n }\r\n\r\n updateActivity(sessionId: string): void {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.lastActivity = Date.now();\r\n }\r\n }\r\n\r\n get(sessionId: string): SessionInfo | undefined {\r\n return this.sessions.get(sessionId);\r\n }\r\n\r\n getAll(): SessionInfo[] {\r\n return Array.from(this.sessions.values());\r\n }\r\n\r\n count(): number {\r\n return this.sessions.size;\r\n }\r\n}\r\n","import notifier from 'node-notifier';\r\nimport { execFile } from 'node:child_process';\r\nimport { logger } from '../shared/logger.js';\r\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\r\nimport type { TelegramBot } from './telegram.js';\r\n\r\nexport class Notifier {\r\n private webhooks: WebhookConfig[] = [];\r\n private desktopEnabled = true;\r\n private notificationSettingsOpened = false;\r\n private dashboardUrl?: string;\r\n private telegramBot?: TelegramBot;\r\n\r\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string; telegramBot?: TelegramBot }): void {\r\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\r\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\r\n if (options.webhooks) this.webhooks = options.webhooks;\r\n if (options.telegramBot) this.telegramBot = options.telegramBot;\r\n }\r\n\r\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\r\n await this.notifyWithSession(undefined, undefined, title, message, level);\r\n }\r\n\r\n async notifyWithSession(sessionId: string | undefined, sessionLabel: string | undefined, title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\r\n const promises: Promise<void>[] = [];\r\n\r\n if (this.desktopEnabled) {\r\n promises.push(this.sendDesktop(title, message, level));\r\n }\r\n\r\n for (const webhook of this.webhooks) {\r\n promises.push(this.sendWebhook(webhook, title, message, level));\r\n }\r\n\r\n if (this.telegramBot && sessionId && sessionLabel) {\r\n promises.push(this.telegramBot.sendNotification(sessionId, sessionLabel, title, message));\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n }\r\n\r\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\r\n if (process.platform === 'win32') {\r\n // Check if notifications are enabled by running snoretoast directly\r\n const enabled = await this.checkWindowsNotifications();\r\n if (!enabled) {\r\n this.openNotificationSettings();\r\n return;\r\n }\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const notification = (notifier as any).notify(\r\n {\r\n title: `Claude Alarm: ${title}`,\r\n message,\r\n sound: true,\r\n wait: true,\r\n },\r\n (err: Error | null) => {\r\n if (err) {\r\n logger.warn(`Desktop notification failed: ${err.message}`);\r\n }\r\n resolve();\r\n },\r\n );\r\n\r\n // No click handler - dashboard is already open and notifications\r\n // can be clicked there to focus the relevant session\r\n });\r\n }\r\n\r\n private checkWindowsNotifications(): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n execFile(\r\n 'powershell',\r\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\r\n (err, stdout) => {\r\n if (err) { resolve(true); return; } // assume enabled on error\r\n const value = stdout.trim();\r\n resolve(value !== '0');\r\n },\r\n );\r\n });\r\n }\r\n\r\n private openNotificationSettings(): void {\r\n if (this.notificationSettingsOpened) return;\r\n this.notificationSettingsOpened = true;\r\n\r\n logger.warn('Windows notifications are disabled. Opening notification settings...');\r\n logger.warn('Please enable notifications for this app, then try again.');\r\n\r\n if (process.platform === 'win32') {\r\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\r\n }\r\n\r\n // Allow re-opening after 5 minutes\r\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\r\n }\r\n\r\n private async sendWebhook(\r\n webhook: WebhookConfig,\r\n title: string,\r\n message: string,\r\n level: NotifyLevel,\r\n ): Promise<void> {\r\n try {\r\n const response = await fetch(webhook.url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...webhook.headers,\r\n },\r\n body: JSON.stringify({\r\n title,\r\n message,\r\n level,\r\n timestamp: Date.now(),\r\n source: 'claude-alarm',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\r\n }\r\n } catch (err) {\r\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\r\n }\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { logger } from '../shared/logger.js';\r\nimport { UPLOADS_DIR } from '../shared/constants.js';\r\nimport type { TelegramConfig, SessionInfo } from '../shared/types.js';\r\n\r\nconst TELEGRAM_API = 'https://api.telegram.org/bot';\r\n\r\ninterface TelegramPhotoSize {\r\n file_id: string;\r\n file_unique_id: string;\r\n width: number;\r\n height: number;\r\n file_size?: number;\r\n}\r\n\r\ninterface TelegramMessage {\r\n message_id: number;\r\n chat: { id: number };\r\n text?: string;\r\n caption?: string;\r\n photo?: TelegramPhotoSize[];\r\n reply_to_message?: { message_id: number };\r\n}\r\n\r\ninterface TelegramCallbackQuery {\r\n id: string;\r\n from: { id: number };\r\n message?: TelegramMessage;\r\n data?: string;\r\n}\r\n\r\ninterface TelegramUpdate {\r\n update_id: number;\r\n message?: TelegramMessage;\r\n callback_query?: TelegramCallbackQuery;\r\n}\r\n\r\nexport class TelegramBot {\r\n private config: TelegramConfig;\r\n private offset = 0;\r\n private polling = false;\r\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\r\n\r\n // message_id -> sessionId mapping for reply-based routing\r\n private messageSessionMap = new Map<number, string>();\r\n\r\n // Callback: when a text message arrives from Telegram for a session\r\n public onMessageToSession?: (sessionId: string, content: string) => void;\r\n // Callback: when an image arrives from Telegram for a session\r\n public onImageToSession?: (sessionId: string, imagePath: string, mimeType: string, caption?: string) => void;\r\n // Callback: when a permission verdict arrives from Telegram\r\n public onPermissionVerdict?: (sessionId: string, requestId: string, behavior: 'allow' | 'deny') => void;\r\n // Callback: get current sessions list\r\n public getSessions?: () => SessionInfo[];\r\n // Pending messages for session selection\r\n private pendingMessages = new Map<number, { text?: string; photoFileId?: string; caption?: string }>(); // chatId -> pending\r\n\r\n constructor(config: TelegramConfig) {\r\n this.config = config;\r\n }\r\n\r\n private get apiUrl(): string {\r\n return `${TELEGRAM_API}${this.config.botToken}`;\r\n }\r\n\r\n /** Send a notification message to Telegram */\r\n async sendNotification(sessionId: string, _sessionLabel: string, title: string, message: string): Promise<void> {\r\n const text = `<b>${this.escHtml(title)}</b>\\n${this.mdToHtml(message)}`;\r\n const result = await this.sendMessage(text);\r\n if (result?.message_id) {\r\n this.messageSessionMap.set(result.message_id, sessionId);\r\n // Cleanup old mappings (keep last 200)\r\n if (this.messageSessionMap.size > 200) {\r\n const keys = [...this.messageSessionMap.keys()];\r\n for (let i = 0; i < keys.length - 200; i++) {\r\n this.messageSessionMap.delete(keys[i]);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** Send a text message to the configured chat */\r\n private async sendMessage(text: string, replyToMessageId?: number, replyMarkup?: unknown): Promise<{ message_id: number } | null> {\r\n try {\r\n const body: Record<string, unknown> = {\r\n chat_id: this.config.chatId,\r\n text,\r\n parse_mode: 'HTML',\r\n };\r\n if (replyToMessageId) body.reply_to_message_id = replyToMessageId;\r\n if (replyMarkup) body.reply_markup = replyMarkup;\r\n\r\n const res = await fetch(`${this.apiUrl}/sendMessage`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const err = await res.text();\r\n logger.warn(`Telegram sendMessage failed: ${res.status} ${err}`);\r\n return null;\r\n }\r\n\r\n const data = await res.json() as { ok: boolean; result: { message_id: number } };\r\n return data.ok ? data.result : null;\r\n } catch (err) {\r\n logger.warn(`Telegram sendMessage error: ${(err as Error).message}`);\r\n return null;\r\n }\r\n }\r\n\r\n /** Start long polling for incoming messages */\r\n startPolling(): void {\r\n if (this.polling) return;\r\n this.polling = true;\r\n logger.info('Telegram bot polling started');\r\n this.poll();\r\n }\r\n\r\n /** Stop polling */\r\n stopPolling(): void {\r\n this.polling = false;\r\n if (this.pollTimer) {\r\n clearTimeout(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n logger.info('Telegram bot polling stopped');\r\n }\r\n\r\n private async poll(): Promise<void> {\r\n if (!this.polling) return;\r\n\r\n try {\r\n const res = await fetch(`${this.apiUrl}/getUpdates?offset=${this.offset}&timeout=30`, {\r\n signal: AbortSignal.timeout(35000),\r\n });\r\n\r\n if (!res.ok) {\r\n logger.warn(`Telegram getUpdates failed: ${res.status}`);\r\n this.scheduleNextPoll(5000);\r\n return;\r\n }\r\n\r\n const data = await res.json() as { ok: boolean; result: TelegramUpdate[] };\r\n if (data.ok && data.result.length > 0) {\r\n for (const update of data.result) {\r\n this.offset = update.update_id + 1;\r\n if (update.callback_query) {\r\n this.handleCallbackQuery(update.callback_query);\r\n } else if (update.message) {\r\n this.handleIncomingMessage(update.message);\r\n }\r\n }\r\n }\r\n } catch (err) {\r\n if ((err as Error).name !== 'AbortError') {\r\n logger.warn(`Telegram poll error: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n this.scheduleNextPoll(1000);\r\n }\r\n\r\n private scheduleNextPoll(delay: number): void {\r\n if (!this.polling) return;\r\n this.pollTimer = setTimeout(() => this.poll(), delay);\r\n }\r\n\r\n private async handleIncomingMessage(msg: TelegramMessage): Promise<void> {\r\n // Only process messages from the configured chat\r\n if (String(msg.chat.id) !== String(this.config.chatId)) return;\r\n\r\n const hasPhoto = msg.photo && msg.photo.length > 0;\r\n const text = (msg.text || msg.caption || '').trim();\r\n\r\n if (!text && !hasPhoto) return;\r\n\r\n // Check if it's a reply to a known message\r\n if (msg.reply_to_message) {\r\n const sessionId = this.messageSessionMap.get(msg.reply_to_message.message_id);\r\n if (sessionId) {\r\n if (hasPhoto) {\r\n await this.deliverPhotoToSession(sessionId, msg.photo!, text);\r\n } else {\r\n this.deliverToSession(sessionId, text);\r\n }\r\n return;\r\n }\r\n }\r\n\r\n // Check if it's a session selection command: /s_<index>\r\n if (text) {\r\n const selectMatch = text.match(/^\\/s_(\\d+)$/);\r\n if (selectMatch) {\r\n const pending = this.pendingMessages.get(msg.chat.id);\r\n if (pending) {\r\n this.pendingMessages.delete(msg.chat.id);\r\n const sessions = this.getSessions?.() ?? [];\r\n const idx = parseInt(selectMatch[1], 10) - 1;\r\n if (idx >= 0 && idx < sessions.length) {\r\n if (pending.photoFileId) {\r\n await this.deliverPhotoToSessionByFileId(sessions[idx].id, pending.photoFileId, pending.caption);\r\n } else if (pending.text) {\r\n this.deliverToSession(sessions[idx].id, pending.text);\r\n }\r\n this.sendMessage(`Sent to [${this.getLabel(sessions[idx])}]`);\r\n } else {\r\n this.sendMessage('Invalid session number.');\r\n }\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // No reply context — check sessions count\r\n const sessions = this.getSessions?.() ?? [];\r\n\r\n if (sessions.length === 0) {\r\n this.sendMessage('No active sessions connected.');\r\n return;\r\n }\r\n\r\n if (sessions.length === 1) {\r\n if (hasPhoto) {\r\n await this.deliverPhotoToSession(sessions[0].id, msg.photo!, text);\r\n } else {\r\n this.deliverToSession(sessions[0].id, text);\r\n }\r\n return;\r\n }\r\n\r\n // Multiple sessions — ask user to pick with inline buttons\r\n if (hasPhoto) {\r\n const largest = msg.photo![msg.photo!.length - 1];\r\n this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });\r\n } else {\r\n this.pendingMessages.set(msg.chat.id, { text });\r\n }\r\n const buttons = sessions.map((s, i) => ({\r\n text: this.getLabel(s),\r\n callback_data: `sess:${i}:${msg.chat.id}`,\r\n }));\r\n // Arrange buttons in rows of 2\r\n const rows: Array<typeof buttons> = [];\r\n for (let i = 0; i < buttons.length; i += 2) {\r\n rows.push(buttons.slice(i, i + 2));\r\n }\r\n this.sendMessage('Multiple sessions active. Select one:', undefined, { inline_keyboard: rows });\r\n }\r\n\r\n private deliverToSession(sessionId: string, content: string): void {\r\n if (this.onMessageToSession) {\r\n this.onMessageToSession(sessionId, content);\r\n }\r\n }\r\n\r\n private async deliverPhotoToSession(sessionId: string, photos: TelegramPhotoSize[], caption?: string): Promise<void> {\r\n // Get the largest photo (last in array)\r\n const largest = photos[photos.length - 1];\r\n await this.deliverPhotoToSessionByFileId(sessionId, largest.file_id, caption);\r\n }\r\n\r\n private async deliverPhotoToSessionByFileId(sessionId: string, fileId: string, caption?: string): Promise<void> {\r\n try {\r\n // Get file path from Telegram\r\n const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);\r\n if (!fileRes.ok) { logger.warn('Failed to get Telegram file info'); return; }\r\n const fileData = await fileRes.json() as { ok: boolean; result: { file_path: string } };\r\n if (!fileData.ok) return;\r\n\r\n // Download the file\r\n const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;\r\n const imgRes = await fetch(downloadUrl);\r\n if (!imgRes.ok) { logger.warn('Failed to download Telegram photo'); return; }\r\n const buffer = Buffer.from(await imgRes.arrayBuffer());\r\n\r\n // Determine extension\r\n const ext = fileData.result.file_path.split('.').pop() || 'jpg';\r\n const mimeType = ext === 'png' ? 'image/png' : ext === 'gif' ? 'image/gif' : ext === 'webp' ? 'image/webp' : 'image/jpeg';\r\n\r\n // Save to uploads dir\r\n fs.mkdirSync(UPLOADS_DIR, { recursive: true });\r\n const filename = `${randomUUID()}.${ext}`;\r\n const filePath = path.join(UPLOADS_DIR, filename);\r\n fs.writeFileSync(filePath, buffer);\r\n logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);\r\n\r\n // Deliver to session\r\n if (this.onImageToSession) {\r\n this.onImageToSession(sessionId, filePath, mimeType, caption);\r\n }\r\n\r\n // Cleanup after 5 minutes\r\n setTimeout(() => { try { fs.unlinkSync(filePath); } catch {} }, 5 * 60 * 1000);\r\n } catch (err) {\r\n logger.warn(`Telegram photo download failed: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private getLabel(session: SessionInfo): string {\r\n return session.displayName || session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n }\r\n\r\n /** Send a permission request with inline buttons */\r\n async sendPermissionRequest(sessionId: string, sessionLabel: string, requestId: string, toolName: string, description: string, inputPreview: string): Promise<void> {\r\n // Parse inputPreview for readable display\r\n let preview = inputPreview;\r\n let truncated = false;\r\n try {\r\n const p = JSON.parse(inputPreview);\r\n if (p.command) preview = `$ ${p.command}`;\r\n else if (p.title && p.message) preview = p.message;\r\n else if (p.file_path) {\r\n preview = p.file_path;\r\n if (p.content) { preview += '\\n' + p.content.slice(0, 3000); if (p.content.length > 500) truncated = true; }\r\n } else if (p.content && typeof p.content === 'string') {\r\n preview = p.content.slice(0, 3000);\r\n if (p.content.length > 500) truncated = true;\r\n }\r\n } catch {\r\n truncated = true;\r\n const cmdMatch = inputPreview.match(/\"command\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n const contentMatch = inputPreview.match(/\"content\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n if (cmdMatch) preview = `$ ${cmdMatch[1]}`;\r\n else if (contentMatch) preview = contentMatch[1].slice(0, 3000);\r\n }\r\n\r\n const truncNote = truncated ? '\\n\\n<i>...truncated</i>' : '';\r\n const previewSlice = preview.slice(0, 3000);\r\n // Use <code> for short single-line (commands), plain text for longer content\r\n const isShort = !previewSlice.includes('\\n') && previewSlice.length < 100;\r\n const previewHtml = isShort\r\n ? `<code>${this.escHtml(previewSlice)}</code>`\r\n : this.escHtml(previewSlice);\r\n // Friendly tool name for Telegram — use title for notify tools\r\n let displayTool = toolName;\r\n if ((toolName.endsWith('__notify') || toolName === 'notify') && inputPreview) {\r\n try { const pp = JSON.parse(inputPreview); if (pp.title) displayTool = pp.title; } catch {\r\n const tm = inputPreview.match(/\"title\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n if (tm) displayTool = tm[1];\r\n }\r\n } else {\r\n const mcpMatch = toolName.match(/__([^_]+)$/);\r\n if (mcpMatch) displayTool = mcpMatch[1].charAt(0).toUpperCase() + mcpMatch[1].slice(1);\r\n }\r\n\r\n const text = `⚠️ <b>Permission Request</b> — ${this.escHtml(sessionLabel)}\\n\\n` +\r\n `🔧 <b>${this.escHtml(displayTool)}</b>\\n` +\r\n `${previewHtml}${truncNote}`;\r\n\r\n const replyMarkup = {\r\n inline_keyboard: [[\r\n { text: '✅ Allow', callback_data: `perm:allow:${sessionId}:${requestId}` },\r\n { text: '❌ Deny', callback_data: `perm:deny:${sessionId}:${requestId}` },\r\n ]],\r\n };\r\n\r\n await this.sendMessage(text, undefined, replyMarkup);\r\n }\r\n\r\n private async handleCallbackQuery(query: TelegramCallbackQuery): Promise<void> {\r\n if (!query.data) return;\r\n\r\n if (query.data.startsWith('sess:')) {\r\n await this.handleSessionSelectCallback(query);\r\n return;\r\n }\r\n\r\n if (!query.data.startsWith('perm:')) return;\r\n\r\n const parts = query.data.split(':');\r\n if (parts.length < 4) return;\r\n const [, action, sessionId, requestId] = parts;\r\n const behavior = action === 'allow' ? 'allow' : 'deny';\r\n\r\n // Send verdict\r\n if (this.onPermissionVerdict) {\r\n this.onPermissionVerdict(sessionId, requestId, behavior as 'allow' | 'deny');\r\n }\r\n\r\n // Answer callback to remove loading state\r\n await this.answerCallbackQuery(query.id, behavior === 'allow' ? '✅ Allowed' : '❌ Denied');\r\n\r\n // Update message to show result (use escaped original text since it's plain)\r\n if (query.message) {\r\n const label = behavior === 'allow' ? '✅ <b>Allowed</b>' : '❌ <b>Denied</b>';\r\n const original = this.escHtml(query.message.text || '');\r\n await this.editMessageText(query.message.chat.id, query.message.message_id, original + `\\n\\n${label}`);\r\n }\r\n }\r\n\r\n private async handleSessionSelectCallback(query: TelegramCallbackQuery): Promise<void> {\r\n const parts = query.data!.split(':');\r\n if (parts.length < 3) return;\r\n const [, idxStr, chatIdStr] = parts;\r\n const idx = parseInt(idxStr, 10);\r\n const chatId = parseInt(chatIdStr, 10);\r\n\r\n const sessions = this.getSessions?.() ?? [];\r\n if (idx < 0 || idx >= sessions.length) {\r\n await this.answerCallbackQuery(query.id, 'Session not found');\r\n return;\r\n }\r\n\r\n const session = sessions[idx];\r\n const pending = this.pendingMessages.get(chatId);\r\n this.pendingMessages.delete(chatId);\r\n\r\n if (pending) {\r\n if (pending.photoFileId) {\r\n await this.deliverPhotoToSessionByFileId(session.id, pending.photoFileId, pending.caption);\r\n } else if (pending.text) {\r\n this.deliverToSession(session.id, pending.text);\r\n }\r\n }\r\n\r\n await this.answerCallbackQuery(query.id, `Sent to ${this.getLabel(session)}`);\r\n // Update message to show which session was selected\r\n if (query.message) {\r\n await this.editMessageText(query.message.chat.id, query.message.message_id, `✅ Sent to <b>${this.escHtml(this.getLabel(session))}</b>`);\r\n }\r\n }\r\n\r\n private async answerCallbackQuery(callbackQueryId: string, text: string): Promise<void> {\r\n try {\r\n await fetch(`${this.apiUrl}/answerCallbackQuery`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ callback_query_id: callbackQueryId, text }),\r\n });\r\n } catch (err) {\r\n logger.warn(`Telegram answerCallbackQuery error: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private async editMessageText(chatId: number, messageId: number, text: string): Promise<void> {\r\n try {\r\n await fetch(`${this.apiUrl}/editMessageText`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ chat_id: chatId, message_id: messageId, text, parse_mode: 'HTML' }),\r\n });\r\n } catch (err) {\r\n logger.warn(`Telegram editMessageText error: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private escHtml(s: string): string {\r\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\r\n }\r\n\r\n /** Convert markdown to Telegram HTML (escape first, then apply formatting) */\r\n private mdToHtml(s: string): string {\r\n // Handle tables before escaping (convert to clean text format)\r\n let text = s.replace(\r\n /^(\\|.+\\|)\\n\\|[-| :]+\\|\\n((?:\\|.+\\|\\n?)*)/gm,\r\n (_match, header: string, body: string) => {\r\n const headerCells = header.split('|').filter((c: string) => c.trim()).map((c: string) => c.trim());\r\n const headerLine = headerCells.join(' | ');\r\n const bodyLines = body.trim().split('\\n').map((row: string) => {\r\n return row.split('|').filter((c: string) => c.trim()).map((c: string) => c.trim()).join(' | ');\r\n });\r\n return `**${headerLine}**\\n${bodyLines.join('\\n')}`;\r\n },\r\n );\r\n // Extract code spans/blocks BEFORE escaping/formatting so that bold/italic\r\n // regexes can't cross into them and produce overlapping tags (Telegram rejects).\r\n const tokens: string[] = [];\r\n const placeholder = (i: number) => `\\u0000CODE${i}\\u0000`;\r\n text = text.replace(/```(?:\\w*)\\n?([\\s\\S]*?)```/g, (_m, body: string) => {\r\n const i = tokens.push(`<pre>${this.escHtml(body)}</pre>`) - 1;\r\n return placeholder(i);\r\n });\r\n text = text.replace(/`([^`\\n]+)`/g, (_m, body: string) => {\r\n const i = tokens.push(`<code>${this.escHtml(body)}</code>`) - 1;\r\n return placeholder(i);\r\n });\r\n\r\n let html = this.escHtml(text);\r\n // Headings: # / ## / ### → bold (Telegram has no heading tags)\r\n html = html.replace(/^#{1,3}\\s+(.+)$/gm, '<b>$1</b>');\r\n // Bold: **...**\r\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>');\r\n // Italic: *...* (single line only, to avoid swallowing across blocks)\r\n html = html.replace(/\\*([^*\\n]+)\\*/g, '<i>$1</i>');\r\n\r\n // Restore code tokens\r\n html = html.replace(/\\u0000CODE(\\d+)\\u0000/g, (_m, n: string) => tokens[Number(n)] ?? '');\r\n return html;\r\n }\r\n\r\n /** Update config (e.g., from dashboard settings) */\r\n updateConfig(config: TelegramConfig): void {\r\n const wasPolling = this.polling;\r\n if (wasPolling) this.stopPolling();\r\n this.config = config;\r\n if (wasPolling && config.enabled) this.startPolling();\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\r\nimport type { AppConfig } from './types.js';\r\n\r\nconst DEFAULT_CONFIG: AppConfig = {\r\n hub: {\r\n host: DEFAULT_HUB_HOST,\r\n port: DEFAULT_HUB_PORT,\r\n },\r\n notifications: {\r\n desktop: true,\r\n sound: true,\r\n },\r\n webhooks: [],\r\n};\r\n\r\nexport function ensureConfigDir(): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nexport function loadConfig(): AppConfig {\r\n ensureConfigDir();\r\n let config: AppConfig;\r\n if (!fs.existsSync(CONFIG_FILE)) {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n } else {\r\n try {\r\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\r\n const parsed = JSON.parse(raw);\r\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...(parsed.telegram ? { telegram: parsed.telegram } : {}) };\r\n } catch {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n }\r\n }\r\n\r\n // Auto-generate token if missing\r\n if (!config.hub.token) {\r\n config.hub.token = randomUUID();\r\n saveConfig(config);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n/** Get the current token, generating one if needed */\r\nexport function getOrCreateToken(): string {\r\n const config = loadConfig();\r\n return config.hub.token!;\r\n}\r\n\r\nexport function saveConfig(config: AppConfig): void {\r\n ensureConfigDir();\r\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\r\n}\r\n\r\n/**\r\n * Add claude-alarm as an MCP channel server to .mcp.json\r\n */\r\nexport function setupMcpConfig(targetDir?: string): string {\r\n const dir = targetDir ?? process.cwd();\r\n const mcpPath = path.join(dir, '.mcp.json');\r\n\r\n let mcpConfig: Record<string, any> = {};\r\n if (fs.existsSync(mcpPath)) {\r\n try {\r\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\r\n } catch {\r\n mcpConfig = {};\r\n }\r\n }\r\n\r\n if (!mcpConfig.mcpServers) {\r\n mcpConfig.mcpServers = {};\r\n }\r\n\r\n mcpConfig.mcpServers['claude-alarm'] = {\r\n command: 'npx',\r\n args: ['-y', '@delt/claude-alarm', 'serve'],\r\n env: {\r\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\r\n },\r\n };\r\n\r\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\r\n return mcpPath;\r\n}\r\n"],"mappings":"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","names":["fs","path","randomUUID","path","sessions","path","fs","path","randomUUID","fs","randomUUID","fs","path","fs","randomUUID"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -136,6 +136,8 @@ declare class HubServer {
|
|
|
136
136
|
private localChannels;
|
|
137
137
|
private dashboardSockets;
|
|
138
138
|
private telegramBot?;
|
|
139
|
+
private heartbeatInterval?;
|
|
140
|
+
private channelAlive;
|
|
139
141
|
private host;
|
|
140
142
|
private port;
|
|
141
143
|
private token?;
|
|
@@ -156,6 +158,7 @@ declare class HubServer {
|
|
|
156
158
|
private handleTelegramSave;
|
|
157
159
|
private handleTelegramTest;
|
|
158
160
|
private handleTelegramDetect;
|
|
161
|
+
private startHeartbeat;
|
|
159
162
|
private cleanupUploads;
|
|
160
163
|
private getSessionLabel;
|
|
161
164
|
private jsonResponse;
|