@delt/claude-alarm 0.1.4 → 0.1.6
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/README.md +33 -18
- package/dist/channel/server.js +2 -1
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +16 -5
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +95 -1
- package/dist/hub/server.js +14 -4
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +95 -1
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"],"sourcesContent":["import http from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport { logger } from '../shared/logger.js';\nimport {\n DEFAULT_HUB_HOST,\n DEFAULT_HUB_PORT,\n WS_PATH_CHANNEL,\n WS_PATH_DASHBOARD,\n} from '../shared/constants.js';\nimport { SessionManager } from './session-manager.js';\nimport { Notifier } from './notifier.js';\nimport type { ChannelMessage, AppConfig } from '../shared/types.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport class HubServer {\n private httpServer: http.Server;\n private wssChannel: WebSocketServer;\n private wssDashboard: WebSocketServer;\n private sessions = new SessionManager();\n private notifier = new Notifier();\n private startTime = Date.now();\n\n // Map sessionId -> channel WebSocket\n private channelSockets = new Map<string, WebSocket>();\n // All connected dashboard WebSockets\n private dashboardSockets = new Set<WebSocket>();\n\n private host: string;\n private port: number;\n private token?: string;\n\n constructor(config?: Partial<AppConfig>) {\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\n this.token = config?.hub?.token;\n\n if (config?.notifications) {\n this.notifier.configure({\n desktop: config.notifications.desktop,\n });\n }\n if (config?.webhooks) {\n this.notifier.configure({ webhooks: config.webhooks });\n }\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\n\n // HTTP Server\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\n\n // WebSocket for channel servers\n this.wssChannel = new WebSocketServer({ noServer: true });\n this.wssChannel.on('connection', (ws) => this.handleChannelConnection(ws));\n\n // WebSocket for dashboard\n this.wssDashboard = new WebSocketServer({ noServer: true });\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\n\n // Route WebSocket upgrade requests\n this.httpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n // Token auth for WebSocket connections (skip for local requests)\n if (this.token && !this.isLocalRequest(req)) {\n const wsToken = url.searchParams.get('token');\n if (wsToken !== this.token) {\n socket.destroy();\n return;\n }\n }\n\n if (pathname === WS_PATH_CHANNEL) {\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\n this.wssChannel.emit('connection', ws, req);\n });\n } else if (pathname === WS_PATH_DASHBOARD) {\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\n this.wssDashboard.emit('connection', ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n }\n\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.httpServer.on('error', reject);\n this.httpServer.listen(this.port, this.host, () => {\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\n resolve();\n });\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n // Close all WebSocket connections\n for (const ws of this.channelSockets.values()) ws.close();\n for (const ws of this.dashboardSockets) ws.close();\n\n this.wssChannel.close();\n this.wssDashboard.close();\n this.httpServer.close(() => {\n logger.info('Hub server stopped');\n resolve();\n });\n });\n }\n\n // --- HTTP Handler ---\n\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n\n // CORS headers - restrict to same origin\n const origin = req.headers.origin;\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n }\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Token auth for API endpoints (skip dashboard HTML serving)\n if (url.pathname !== '/' && this.token) {\n if (!this.isLocalRequest(req)) {\n const authHeader = req.headers['authorization'];\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\n if (bearerToken !== this.token) {\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\n return;\n }\n }\n }\n\n // Route\n if (url.pathname === '/' && req.method === 'GET') {\n this.serveDashboard(res);\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\n this.jsonResponse(res, 200, {\n running: true,\n pid: process.pid,\n port: this.port,\n sessions: this.sessions.count(),\n uptime: Date.now() - this.startTime,\n });\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\n this.handleApiSend(req, res);\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\n this.handleApiNotify(req, res);\n } else {\n this.jsonResponse(res, 404, { error: 'Not found' });\n }\n }\n\n private serveDashboard(res: http.ServerResponse): void {\n // Look for dashboard HTML relative to this file (dist) or source\n const candidates = [\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\n ];\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n const html = fs.readFileSync(candidate, 'utf-8');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\n }\n\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { sessionId, content } = body as { sessionId?: string; content?: string };\n if (!sessionId || !content) {\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\n return;\n }\n\n const ws = this.channelSockets.get(sessionId);\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n this.jsonResponse(res, 404, { error: 'Session not connected' });\n return;\n }\n\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\n ws.send(JSON.stringify(msg));\n this.jsonResponse(res, 200, { ok: true });\n }\n\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\n if (!title || !message) {\n this.jsonResponse(res, 400, { error: 'title and message are required' });\n return;\n }\n\n await this.notifier.notify(title, message, (level as any) ?? 'info');\n this.jsonResponse(res, 200, { ok: true });\n }\n\n // --- Channel WebSocket ---\n\n private handleChannelConnection(ws: WebSocket): void {\n logger.info('Channel server connected');\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n this.handleChannelMessage(ws, msg);\n } catch {\n logger.warn('Invalid message from channel');\n }\n });\n\n ws.on('close', () => {\n // Find and remove the session for this socket\n for (const [sessionId, sock] of this.channelSockets) {\n if (sock === ws) {\n const session = this.sessions.unregister(sessionId);\n this.channelSockets.delete(sessionId);\n logger.info(`Channel disconnected: ${sessionId}`);\n this.broadcastToDashboards({\n type: 'session_disconnected',\n sessionId,\n });\n break;\n }\n }\n });\n }\n\n private handleChannelMessage(ws: WebSocket, msg: ChannelMessage): void {\n switch (msg.type) {\n case 'register': {\n const session = msg.session;\n this.sessions.register(session);\n this.channelSockets.set(session.id, ws);\n logger.info(`Session registered: ${session.id} (${session.name})`);\n this.broadcastToDashboards({ type: 'session_connected', session });\n break;\n }\n\n case 'status': {\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\n if (updated) {\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\n }\n break;\n }\n\n case 'notify': {\n this.sessions.updateActivity(msg.sessionId);\n this.notifier.notify(msg.title, msg.message, msg.level ?? 'info');\n this.broadcastToDashboards({\n type: 'notification',\n sessionId: msg.sessionId,\n title: msg.title,\n message: msg.message,\n level: msg.level,\n timestamp: Date.now(),\n });\n break;\n }\n\n case 'reply': {\n this.sessions.updateActivity(msg.sessionId);\n const session = this.sessions.get(msg.sessionId);\n const senderName = session?.name ?? msg.sessionId.slice(0, 8);\n this.notifier.notify(senderName, msg.content, 'info');\n this.broadcastToDashboards({\n type: 'reply_from_session',\n sessionId: msg.sessionId,\n content: msg.content,\n timestamp: Date.now(),\n });\n break;\n }\n }\n }\n\n // --- Dashboard WebSocket ---\n\n private handleDashboardConnection(ws: WebSocket): void {\n this.dashboardSockets.add(ws);\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\n\n // Send current session list\n const sessionsMsg: ChannelMessage = {\n type: 'sessions_list',\n sessions: this.sessions.getAll(),\n };\n ws.send(JSON.stringify(sessionsMsg));\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n if (msg.type === 'message_to_session') {\n const channelWs = this.channelSockets.get(msg.sessionId);\n if (channelWs?.readyState === WebSocket.OPEN) {\n channelWs.send(JSON.stringify(msg));\n }\n }\n } catch {\n logger.warn('Invalid message from dashboard');\n }\n });\n\n ws.on('close', () => {\n this.dashboardSockets.delete(ws);\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\n });\n }\n\n // --- Helpers ---\n\n private broadcastToDashboards(msg: ChannelMessage): void {\n const payload = JSON.stringify(msg);\n for (const ws of this.dashboardSockets) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(payload);\n }\n }\n }\n\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n }\n\n private isLocalRequest(req: http.IncomingMessage): boolean {\n const addr = req.socket.remoteAddress;\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\n }\n\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\n return new Promise((resolve) => {\n let data = '';\n let size = 0;\n req.on('data', (chunk) => {\n size += chunk.length;\n if (size > maxSize) {\n req.destroy();\n resolve(null);\n return;\n }\n data += chunk;\n });\n req.on('end', () => {\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(null);\n }\n });\n });\n }\n}\n\n// Direct execution support\nif (process.argv[1] && (\n process.argv[1].endsWith('hub/server.js') ||\n process.argv[1].endsWith('hub/server.ts')\n)) {\n const hub = new HubServer();\n hub.start().catch((err) => {\n logger.error('Failed to start hub:', err);\n process.exit(1);\n });\n\n const shutdown = () => {\n hub.stop().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n","/**\n * Logger that writes to stderr only.\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\n * Any console.log() would corrupt the protocol. Always use this logger.\n */\nexport const logger = {\n info(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm] ${msg}`, ...args);\n },\n warn(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\n },\n error(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\n },\n debug(msg: string, ...args: unknown[]) {\n if (process.env.CLAUDE_ALARM_DEBUG) {\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\n }\n },\n};\n","import path from 'node:path';\nimport os from 'node:os';\n\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\nexport const DEFAULT_HUB_PORT = 7890;\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\n\nexport const WS_PATH_CHANNEL = '/ws/channel';\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\n\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\n\nexport class SessionManager {\n private sessions = new Map<string, SessionInfo>();\n\n register(session: SessionInfo): void {\n this.sessions.set(session.id, { ...session });\n }\n\n unregister(sessionId: string): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n this.sessions.delete(sessionId);\n return session;\n }\n\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.status = status;\n session.lastActivity = Date.now();\n }\n return session;\n }\n\n updateActivity(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActivity = Date.now();\n }\n }\n\n get(sessionId: string): SessionInfo | undefined {\n return this.sessions.get(sessionId);\n }\n\n getAll(): SessionInfo[] {\n return Array.from(this.sessions.values());\n }\n\n count(): number {\n return this.sessions.size;\n }\n}\n","import notifier from 'node-notifier';\nimport { execFile } from 'node:child_process';\nimport { logger } from '../shared/logger.js';\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\n\nexport class Notifier {\n private webhooks: WebhookConfig[] = [];\n private desktopEnabled = true;\n private notificationSettingsOpened = false;\n private dashboardUrl?: string;\n\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string }): void {\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\n if (options.webhooks) this.webhooks = options.webhooks;\n }\n\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\n const promises: Promise<void>[] = [];\n\n if (this.desktopEnabled) {\n promises.push(this.sendDesktop(title, message, level));\n }\n\n for (const webhook of this.webhooks) {\n promises.push(this.sendWebhook(webhook, title, message, level));\n }\n\n await Promise.allSettled(promises);\n }\n\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\n if (process.platform === 'win32') {\n // Check if notifications are enabled by running snoretoast directly\n const enabled = await this.checkWindowsNotifications();\n if (!enabled) {\n this.openNotificationSettings();\n return;\n }\n }\n\n return new Promise((resolve) => {\n const notification = (notifier as any).notify(\n {\n title: `Claude Alarm: ${title}`,\n message,\n sound: true,\n wait: true,\n },\n (err: Error | null) => {\n if (err) {\n logger.warn(`Desktop notification failed: ${err.message}`);\n }\n resolve();\n },\n );\n\n if (this.dashboardUrl && notification) {\n const url = this.dashboardUrl;\n notification.on('click', () => {\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', `Start-Process \"${url}\"`]);\n } else if (process.platform === 'darwin') {\n execFile('open', [url]);\n } else {\n execFile('xdg-open', [url]);\n }\n });\n }\n });\n }\n\n private checkWindowsNotifications(): Promise<boolean> {\n return new Promise((resolve) => {\n execFile(\n 'powershell',\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\n (err, stdout) => {\n if (err) { resolve(true); return; } // assume enabled on error\n const value = stdout.trim();\n resolve(value !== '0');\n },\n );\n });\n }\n\n private openNotificationSettings(): void {\n if (this.notificationSettingsOpened) return;\n this.notificationSettingsOpened = true;\n\n logger.warn('Windows notifications are disabled. Opening notification settings...');\n logger.warn('Please enable notifications for this app, then try again.');\n\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\n }\n\n // Allow re-opening after 5 minutes\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\n }\n\n private async sendWebhook(\n webhook: WebhookConfig,\n title: string,\n message: string,\n level: NotifyLevel,\n ): Promise<void> {\n try {\n const response = await fetch(webhook.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...webhook.headers,\n },\n body: JSON.stringify({\n title,\n message,\n level,\n timestamp: Date.now(),\n source: 'claude-alarm',\n }),\n });\n\n if (!response.ok) {\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\n }\n } catch (err) {\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\n }\n }\n}\n"],"mappings":"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","names":["path","path"]}
|
|
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"],"sourcesContent":["import http from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport { logger } from '../shared/logger.js';\nimport {\n DEFAULT_HUB_HOST,\n DEFAULT_HUB_PORT,\n WS_PATH_CHANNEL,\n WS_PATH_DASHBOARD,\n} from '../shared/constants.js';\nimport { SessionManager } from './session-manager.js';\nimport { Notifier } from './notifier.js';\nimport type { ChannelMessage, AppConfig } from '../shared/types.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport class HubServer {\n private httpServer: http.Server;\n private wssChannel: WebSocketServer;\n private wssDashboard: WebSocketServer;\n private sessions = new SessionManager();\n private notifier = new Notifier();\n private startTime = Date.now();\n\n // Map sessionId -> channel WebSocket\n private channelSockets = new Map<string, WebSocket>();\n // All connected dashboard WebSockets\n private dashboardSockets = new Set<WebSocket>();\n\n private host: string;\n private port: number;\n private token?: string;\n\n constructor(config?: Partial<AppConfig>) {\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\n this.token = config?.hub?.token;\n\n if (config?.notifications) {\n this.notifier.configure({\n desktop: config.notifications.desktop,\n });\n }\n if (config?.webhooks) {\n this.notifier.configure({ webhooks: config.webhooks });\n }\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\n\n // HTTP Server\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\n\n // WebSocket for channel servers\n this.wssChannel = new WebSocketServer({ noServer: true });\n this.wssChannel.on('connection', (ws) => this.handleChannelConnection(ws));\n\n // WebSocket for dashboard\n this.wssDashboard = new WebSocketServer({ noServer: true });\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\n\n // Route WebSocket upgrade requests\n this.httpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n // Token auth for WebSocket connections (skip for local requests)\n if (this.token && !this.isLocalRequest(req)) {\n const wsToken = url.searchParams.get('token');\n if (wsToken !== this.token) {\n socket.destroy();\n return;\n }\n }\n\n if (pathname === WS_PATH_CHANNEL) {\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\n this.wssChannel.emit('connection', ws, req);\n });\n } else if (pathname === WS_PATH_DASHBOARD) {\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\n this.wssDashboard.emit('connection', ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n }\n\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.httpServer.on('error', reject);\n this.httpServer.listen(this.port, this.host, () => {\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\n resolve();\n });\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n // Force-close all WebSocket connections\n for (const ws of this.channelSockets.values()) ws.terminate();\n for (const ws of this.dashboardSockets) ws.terminate();\n this.channelSockets.clear();\n this.dashboardSockets.clear();\n\n this.wssChannel.close();\n this.wssDashboard.close();\n this.httpServer.close(() => {\n logger.info('Hub server stopped');\n resolve();\n });\n\n // Force resolve after 3 seconds if server won't close\n setTimeout(() => {\n logger.warn('Force shutting down');\n resolve();\n }, 3000);\n });\n }\n\n // --- HTTP Handler ---\n\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n\n // CORS headers - restrict to same origin\n const origin = req.headers.origin;\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n }\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Token auth for API endpoints (skip dashboard HTML serving)\n if (url.pathname !== '/' && this.token) {\n if (!this.isLocalRequest(req)) {\n const authHeader = req.headers['authorization'];\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\n if (bearerToken !== this.token) {\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\n return;\n }\n }\n }\n\n // Route\n if (url.pathname === '/' && req.method === 'GET') {\n this.serveDashboard(res);\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\n this.jsonResponse(res, 200, {\n running: true,\n pid: process.pid,\n port: this.port,\n sessions: this.sessions.count(),\n uptime: Date.now() - this.startTime,\n });\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\n this.handleApiSend(req, res);\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\n this.handleApiNotify(req, res);\n } else {\n this.jsonResponse(res, 404, { error: 'Not found' });\n }\n }\n\n private serveDashboard(res: http.ServerResponse): void {\n // Look for dashboard HTML relative to this file (dist) or source\n const candidates = [\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\n ];\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n const html = fs.readFileSync(candidate, 'utf-8');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\n }\n\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { sessionId, content } = body as { sessionId?: string; content?: string };\n if (!sessionId || !content) {\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\n return;\n }\n\n const ws = this.channelSockets.get(sessionId);\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n this.jsonResponse(res, 404, { error: 'Session not connected' });\n return;\n }\n\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\n ws.send(JSON.stringify(msg));\n this.jsonResponse(res, 200, { ok: true });\n }\n\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\n if (!title || !message) {\n this.jsonResponse(res, 400, { error: 'title and message are required' });\n return;\n }\n\n await this.notifier.notify(title, message, (level as any) ?? 'info');\n this.jsonResponse(res, 200, { ok: true });\n }\n\n // --- Channel WebSocket ---\n\n private handleChannelConnection(ws: WebSocket): void {\n logger.info('Channel server connected');\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n this.handleChannelMessage(ws, msg);\n } catch {\n logger.warn('Invalid message from channel');\n }\n });\n\n ws.on('close', () => {\n // Find and remove the session for this socket\n for (const [sessionId, sock] of this.channelSockets) {\n if (sock === ws) {\n const session = this.sessions.unregister(sessionId);\n this.channelSockets.delete(sessionId);\n logger.info(`Channel disconnected: ${sessionId}`);\n this.broadcastToDashboards({\n type: 'session_disconnected',\n sessionId,\n });\n break;\n }\n }\n });\n }\n\n private handleChannelMessage(ws: WebSocket, msg: ChannelMessage): void {\n switch (msg.type) {\n case 'register': {\n const session = msg.session;\n const isReregister = !!this.sessions.get(session.id);\n this.sessions.register(session);\n this.channelSockets.set(session.id, ws);\n logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);\n this.broadcastToDashboards({\n type: isReregister ? 'session_updated' : 'session_connected',\n session,\n });\n break;\n }\n\n case 'status': {\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\n if (updated) {\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\n }\n break;\n }\n\n case 'notify': {\n this.sessions.updateActivity(msg.sessionId);\n this.notifier.notify(msg.title, msg.message, msg.level ?? 'info');\n this.broadcastToDashboards({\n type: 'notification',\n sessionId: msg.sessionId,\n title: msg.title,\n message: msg.message,\n level: msg.level,\n timestamp: Date.now(),\n });\n break;\n }\n\n case 'reply': {\n this.sessions.updateActivity(msg.sessionId);\n const session = this.sessions.get(msg.sessionId);\n const senderName = session?.name ?? msg.sessionId.slice(0, 8);\n this.notifier.notify(senderName, msg.content, 'info');\n this.broadcastToDashboards({\n type: 'reply_from_session',\n sessionId: msg.sessionId,\n content: msg.content,\n timestamp: Date.now(),\n });\n break;\n }\n }\n }\n\n // --- Dashboard WebSocket ---\n\n private handleDashboardConnection(ws: WebSocket): void {\n this.dashboardSockets.add(ws);\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\n\n // Send current session list\n const sessionsMsg: ChannelMessage = {\n type: 'sessions_list',\n sessions: this.sessions.getAll(),\n };\n ws.send(JSON.stringify(sessionsMsg));\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n if (msg.type === 'message_to_session') {\n const channelWs = this.channelSockets.get(msg.sessionId);\n if (channelWs?.readyState === WebSocket.OPEN) {\n channelWs.send(JSON.stringify(msg));\n }\n }\n } catch {\n logger.warn('Invalid message from dashboard');\n }\n });\n\n ws.on('close', () => {\n this.dashboardSockets.delete(ws);\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\n });\n }\n\n // --- Helpers ---\n\n private broadcastToDashboards(msg: ChannelMessage): void {\n const payload = JSON.stringify(msg);\n for (const ws of this.dashboardSockets) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(payload);\n }\n }\n }\n\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n }\n\n private isLocalRequest(req: http.IncomingMessage): boolean {\n const addr = req.socket.remoteAddress;\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\n }\n\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\n return new Promise((resolve) => {\n let data = '';\n let size = 0;\n req.on('data', (chunk) => {\n size += chunk.length;\n if (size > maxSize) {\n req.destroy();\n resolve(null);\n return;\n }\n data += chunk;\n });\n req.on('end', () => {\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(null);\n }\n });\n });\n }\n}\n\n// Direct execution support\nif (process.argv[1] && (\n process.argv[1].endsWith('hub/server.js') ||\n process.argv[1].endsWith('hub/server.ts')\n)) {\n const hub = new HubServer();\n hub.start().catch((err) => {\n logger.error('Failed to start hub:', err);\n process.exit(1);\n });\n\n const shutdown = () => {\n hub.stop().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n","/**\n * Logger that writes to stderr only.\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\n * Any console.log() would corrupt the protocol. Always use this logger.\n */\nexport const logger = {\n info(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm] ${msg}`, ...args);\n },\n warn(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\n },\n error(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\n },\n debug(msg: string, ...args: unknown[]) {\n if (process.env.CLAUDE_ALARM_DEBUG) {\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\n }\n },\n};\n","import path from 'node:path';\nimport os from 'node:os';\n\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\nexport const DEFAULT_HUB_PORT = 7890;\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\n\nexport const WS_PATH_CHANNEL = '/ws/channel';\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\n\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\n\nexport class SessionManager {\n private sessions = new Map<string, SessionInfo>();\n\n register(session: SessionInfo): void {\n this.sessions.set(session.id, { ...session });\n }\n\n unregister(sessionId: string): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n this.sessions.delete(sessionId);\n return session;\n }\n\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.status = status;\n session.lastActivity = Date.now();\n }\n return session;\n }\n\n updateActivity(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActivity = Date.now();\n }\n }\n\n get(sessionId: string): SessionInfo | undefined {\n return this.sessions.get(sessionId);\n }\n\n getAll(): SessionInfo[] {\n return Array.from(this.sessions.values());\n }\n\n count(): number {\n return this.sessions.size;\n }\n}\n","import notifier from 'node-notifier';\nimport { execFile } from 'node:child_process';\nimport { logger } from '../shared/logger.js';\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\n\nexport class Notifier {\n private webhooks: WebhookConfig[] = [];\n private desktopEnabled = true;\n private notificationSettingsOpened = false;\n private dashboardUrl?: string;\n\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string }): void {\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\n if (options.webhooks) this.webhooks = options.webhooks;\n }\n\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\n const promises: Promise<void>[] = [];\n\n if (this.desktopEnabled) {\n promises.push(this.sendDesktop(title, message, level));\n }\n\n for (const webhook of this.webhooks) {\n promises.push(this.sendWebhook(webhook, title, message, level));\n }\n\n await Promise.allSettled(promises);\n }\n\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\n if (process.platform === 'win32') {\n // Check if notifications are enabled by running snoretoast directly\n const enabled = await this.checkWindowsNotifications();\n if (!enabled) {\n this.openNotificationSettings();\n return;\n }\n }\n\n return new Promise((resolve) => {\n const notification = (notifier as any).notify(\n {\n title: `Claude Alarm: ${title}`,\n message,\n sound: true,\n wait: true,\n },\n (err: Error | null) => {\n if (err) {\n logger.warn(`Desktop notification failed: ${err.message}`);\n }\n resolve();\n },\n );\n\n if (this.dashboardUrl && notification) {\n const url = this.dashboardUrl;\n notification.on('click', () => {\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', `Start-Process \"${url}\"`]);\n } else if (process.platform === 'darwin') {\n execFile('open', [url]);\n } else {\n execFile('xdg-open', [url]);\n }\n });\n }\n });\n }\n\n private checkWindowsNotifications(): Promise<boolean> {\n return new Promise((resolve) => {\n execFile(\n 'powershell',\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\n (err, stdout) => {\n if (err) { resolve(true); return; } // assume enabled on error\n const value = stdout.trim();\n resolve(value !== '0');\n },\n );\n });\n }\n\n private openNotificationSettings(): void {\n if (this.notificationSettingsOpened) return;\n this.notificationSettingsOpened = true;\n\n logger.warn('Windows notifications are disabled. Opening notification settings...');\n logger.warn('Please enable notifications for this app, then try again.');\n\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\n }\n\n // Allow re-opening after 5 minutes\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\n }\n\n private async sendWebhook(\n webhook: WebhookConfig,\n title: string,\n message: string,\n level: NotifyLevel,\n ): Promise<void> {\n try {\n const response = await fetch(webhook.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...webhook.headers,\n },\n body: JSON.stringify({\n title,\n message,\n level,\n timestamp: Date.now(),\n source: 'claude-alarm',\n }),\n });\n\n if (!response.ok) {\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\n }\n } catch (err) {\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\n }\n }\n}\n"],"mappings":"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","names":["path","path"]}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -256,14 +256,20 @@ var HubServer = class {
|
|
|
256
256
|
}
|
|
257
257
|
stop() {
|
|
258
258
|
return new Promise((resolve) => {
|
|
259
|
-
for (const ws of this.channelSockets.values()) ws.
|
|
260
|
-
for (const ws of this.dashboardSockets) ws.
|
|
259
|
+
for (const ws of this.channelSockets.values()) ws.terminate();
|
|
260
|
+
for (const ws of this.dashboardSockets) ws.terminate();
|
|
261
|
+
this.channelSockets.clear();
|
|
262
|
+
this.dashboardSockets.clear();
|
|
261
263
|
this.wssChannel.close();
|
|
262
264
|
this.wssDashboard.close();
|
|
263
265
|
this.httpServer.close(() => {
|
|
264
266
|
logger.info("Hub server stopped");
|
|
265
267
|
resolve();
|
|
266
268
|
});
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
logger.warn("Force shutting down");
|
|
271
|
+
resolve();
|
|
272
|
+
}, 3e3);
|
|
267
273
|
});
|
|
268
274
|
}
|
|
269
275
|
// --- HTTP Handler ---
|
|
@@ -397,10 +403,14 @@ var HubServer = class {
|
|
|
397
403
|
switch (msg.type) {
|
|
398
404
|
case "register": {
|
|
399
405
|
const session = msg.session;
|
|
406
|
+
const isReregister = !!this.sessions.get(session.id);
|
|
400
407
|
this.sessions.register(session);
|
|
401
408
|
this.channelSockets.set(session.id, ws);
|
|
402
|
-
logger.info(`Session registered: ${session.id} (${session.name})`);
|
|
403
|
-
this.broadcastToDashboards({
|
|
409
|
+
logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);
|
|
410
|
+
this.broadcastToDashboards({
|
|
411
|
+
type: isReregister ? "session_updated" : "session_connected",
|
|
412
|
+
session
|
|
413
|
+
});
|
|
404
414
|
break;
|
|
405
415
|
}
|
|
406
416
|
case "status": {
|
|
@@ -550,7 +560,8 @@ var HubClient = class {
|
|
|
550
560
|
status: "idle",
|
|
551
561
|
connectedAt: Date.now(),
|
|
552
562
|
lastActivity: Date.now(),
|
|
553
|
-
cwd: process.cwd()
|
|
563
|
+
cwd: process.cwd(),
|
|
564
|
+
channelEnabled: true
|
|
554
565
|
}
|
|
555
566
|
};
|
|
556
567
|
this.ws.send(JSON.stringify(registration));
|
package/dist/index.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/channel/hub-client.ts","../src/shared/config.ts"],"sourcesContent":["import http from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport { logger } from '../shared/logger.js';\nimport {\n DEFAULT_HUB_HOST,\n DEFAULT_HUB_PORT,\n WS_PATH_CHANNEL,\n WS_PATH_DASHBOARD,\n} from '../shared/constants.js';\nimport { SessionManager } from './session-manager.js';\nimport { Notifier } from './notifier.js';\nimport type { ChannelMessage, AppConfig } from '../shared/types.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport class HubServer {\n private httpServer: http.Server;\n private wssChannel: WebSocketServer;\n private wssDashboard: WebSocketServer;\n private sessions = new SessionManager();\n private notifier = new Notifier();\n private startTime = Date.now();\n\n // Map sessionId -> channel WebSocket\n private channelSockets = new Map<string, WebSocket>();\n // All connected dashboard WebSockets\n private dashboardSockets = new Set<WebSocket>();\n\n private host: string;\n private port: number;\n private token?: string;\n\n constructor(config?: Partial<AppConfig>) {\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\n this.token = config?.hub?.token;\n\n if (config?.notifications) {\n this.notifier.configure({\n desktop: config.notifications.desktop,\n });\n }\n if (config?.webhooks) {\n this.notifier.configure({ webhooks: config.webhooks });\n }\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\n\n // HTTP Server\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\n\n // WebSocket for channel servers\n this.wssChannel = new WebSocketServer({ noServer: true });\n this.wssChannel.on('connection', (ws) => this.handleChannelConnection(ws));\n\n // WebSocket for dashboard\n this.wssDashboard = new WebSocketServer({ noServer: true });\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\n\n // Route WebSocket upgrade requests\n this.httpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n // Token auth for WebSocket connections (skip for local requests)\n if (this.token && !this.isLocalRequest(req)) {\n const wsToken = url.searchParams.get('token');\n if (wsToken !== this.token) {\n socket.destroy();\n return;\n }\n }\n\n if (pathname === WS_PATH_CHANNEL) {\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\n this.wssChannel.emit('connection', ws, req);\n });\n } else if (pathname === WS_PATH_DASHBOARD) {\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\n this.wssDashboard.emit('connection', ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n }\n\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.httpServer.on('error', reject);\n this.httpServer.listen(this.port, this.host, () => {\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\n resolve();\n });\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n // Close all WebSocket connections\n for (const ws of this.channelSockets.values()) ws.close();\n for (const ws of this.dashboardSockets) ws.close();\n\n this.wssChannel.close();\n this.wssDashboard.close();\n this.httpServer.close(() => {\n logger.info('Hub server stopped');\n resolve();\n });\n });\n }\n\n // --- HTTP Handler ---\n\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n\n // CORS headers - restrict to same origin\n const origin = req.headers.origin;\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n }\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Token auth for API endpoints (skip dashboard HTML serving)\n if (url.pathname !== '/' && this.token) {\n if (!this.isLocalRequest(req)) {\n const authHeader = req.headers['authorization'];\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\n if (bearerToken !== this.token) {\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\n return;\n }\n }\n }\n\n // Route\n if (url.pathname === '/' && req.method === 'GET') {\n this.serveDashboard(res);\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\n this.jsonResponse(res, 200, {\n running: true,\n pid: process.pid,\n port: this.port,\n sessions: this.sessions.count(),\n uptime: Date.now() - this.startTime,\n });\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\n this.handleApiSend(req, res);\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\n this.handleApiNotify(req, res);\n } else {\n this.jsonResponse(res, 404, { error: 'Not found' });\n }\n }\n\n private serveDashboard(res: http.ServerResponse): void {\n // Look for dashboard HTML relative to this file (dist) or source\n const candidates = [\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\n ];\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n const html = fs.readFileSync(candidate, 'utf-8');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\n }\n\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { sessionId, content } = body as { sessionId?: string; content?: string };\n if (!sessionId || !content) {\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\n return;\n }\n\n const ws = this.channelSockets.get(sessionId);\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n this.jsonResponse(res, 404, { error: 'Session not connected' });\n return;\n }\n\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\n ws.send(JSON.stringify(msg));\n this.jsonResponse(res, 200, { ok: true });\n }\n\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\n if (!title || !message) {\n this.jsonResponse(res, 400, { error: 'title and message are required' });\n return;\n }\n\n await this.notifier.notify(title, message, (level as any) ?? 'info');\n this.jsonResponse(res, 200, { ok: true });\n }\n\n // --- Channel WebSocket ---\n\n private handleChannelConnection(ws: WebSocket): void {\n logger.info('Channel server connected');\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n this.handleChannelMessage(ws, msg);\n } catch {\n logger.warn('Invalid message from channel');\n }\n });\n\n ws.on('close', () => {\n // Find and remove the session for this socket\n for (const [sessionId, sock] of this.channelSockets) {\n if (sock === ws) {\n const session = this.sessions.unregister(sessionId);\n this.channelSockets.delete(sessionId);\n logger.info(`Channel disconnected: ${sessionId}`);\n this.broadcastToDashboards({\n type: 'session_disconnected',\n sessionId,\n });\n break;\n }\n }\n });\n }\n\n private handleChannelMessage(ws: WebSocket, msg: ChannelMessage): void {\n switch (msg.type) {\n case 'register': {\n const session = msg.session;\n this.sessions.register(session);\n this.channelSockets.set(session.id, ws);\n logger.info(`Session registered: ${session.id} (${session.name})`);\n this.broadcastToDashboards({ type: 'session_connected', session });\n break;\n }\n\n case 'status': {\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\n if (updated) {\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\n }\n break;\n }\n\n case 'notify': {\n this.sessions.updateActivity(msg.sessionId);\n this.notifier.notify(msg.title, msg.message, msg.level ?? 'info');\n this.broadcastToDashboards({\n type: 'notification',\n sessionId: msg.sessionId,\n title: msg.title,\n message: msg.message,\n level: msg.level,\n timestamp: Date.now(),\n });\n break;\n }\n\n case 'reply': {\n this.sessions.updateActivity(msg.sessionId);\n const session = this.sessions.get(msg.sessionId);\n const senderName = session?.name ?? msg.sessionId.slice(0, 8);\n this.notifier.notify(senderName, msg.content, 'info');\n this.broadcastToDashboards({\n type: 'reply_from_session',\n sessionId: msg.sessionId,\n content: msg.content,\n timestamp: Date.now(),\n });\n break;\n }\n }\n }\n\n // --- Dashboard WebSocket ---\n\n private handleDashboardConnection(ws: WebSocket): void {\n this.dashboardSockets.add(ws);\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\n\n // Send current session list\n const sessionsMsg: ChannelMessage = {\n type: 'sessions_list',\n sessions: this.sessions.getAll(),\n };\n ws.send(JSON.stringify(sessionsMsg));\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n if (msg.type === 'message_to_session') {\n const channelWs = this.channelSockets.get(msg.sessionId);\n if (channelWs?.readyState === WebSocket.OPEN) {\n channelWs.send(JSON.stringify(msg));\n }\n }\n } catch {\n logger.warn('Invalid message from dashboard');\n }\n });\n\n ws.on('close', () => {\n this.dashboardSockets.delete(ws);\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\n });\n }\n\n // --- Helpers ---\n\n private broadcastToDashboards(msg: ChannelMessage): void {\n const payload = JSON.stringify(msg);\n for (const ws of this.dashboardSockets) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(payload);\n }\n }\n }\n\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n }\n\n private isLocalRequest(req: http.IncomingMessage): boolean {\n const addr = req.socket.remoteAddress;\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\n }\n\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\n return new Promise((resolve) => {\n let data = '';\n let size = 0;\n req.on('data', (chunk) => {\n size += chunk.length;\n if (size > maxSize) {\n req.destroy();\n resolve(null);\n return;\n }\n data += chunk;\n });\n req.on('end', () => {\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(null);\n }\n });\n });\n }\n}\n\n// Direct execution support\nif (process.argv[1] && (\n process.argv[1].endsWith('hub/server.js') ||\n process.argv[1].endsWith('hub/server.ts')\n)) {\n const hub = new HubServer();\n hub.start().catch((err) => {\n logger.error('Failed to start hub:', err);\n process.exit(1);\n });\n\n const shutdown = () => {\n hub.stop().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n","/**\n * Logger that writes to stderr only.\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\n * Any console.log() would corrupt the protocol. Always use this logger.\n */\nexport const logger = {\n info(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm] ${msg}`, ...args);\n },\n warn(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\n },\n error(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\n },\n debug(msg: string, ...args: unknown[]) {\n if (process.env.CLAUDE_ALARM_DEBUG) {\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\n }\n },\n};\n","import path from 'node:path';\nimport os from 'node:os';\n\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\nexport const DEFAULT_HUB_PORT = 7890;\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\n\nexport const WS_PATH_CHANNEL = '/ws/channel';\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\n\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\n\nexport class SessionManager {\n private sessions = new Map<string, SessionInfo>();\n\n register(session: SessionInfo): void {\n this.sessions.set(session.id, { ...session });\n }\n\n unregister(sessionId: string): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n this.sessions.delete(sessionId);\n return session;\n }\n\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.status = status;\n session.lastActivity = Date.now();\n }\n return session;\n }\n\n updateActivity(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActivity = Date.now();\n }\n }\n\n get(sessionId: string): SessionInfo | undefined {\n return this.sessions.get(sessionId);\n }\n\n getAll(): SessionInfo[] {\n return Array.from(this.sessions.values());\n }\n\n count(): number {\n return this.sessions.size;\n }\n}\n","import notifier from 'node-notifier';\nimport { execFile } from 'node:child_process';\nimport { logger } from '../shared/logger.js';\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\n\nexport class Notifier {\n private webhooks: WebhookConfig[] = [];\n private desktopEnabled = true;\n private notificationSettingsOpened = false;\n private dashboardUrl?: string;\n\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string }): void {\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\n if (options.webhooks) this.webhooks = options.webhooks;\n }\n\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\n const promises: Promise<void>[] = [];\n\n if (this.desktopEnabled) {\n promises.push(this.sendDesktop(title, message, level));\n }\n\n for (const webhook of this.webhooks) {\n promises.push(this.sendWebhook(webhook, title, message, level));\n }\n\n await Promise.allSettled(promises);\n }\n\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\n if (process.platform === 'win32') {\n // Check if notifications are enabled by running snoretoast directly\n const enabled = await this.checkWindowsNotifications();\n if (!enabled) {\n this.openNotificationSettings();\n return;\n }\n }\n\n return new Promise((resolve) => {\n const notification = (notifier as any).notify(\n {\n title: `Claude Alarm: ${title}`,\n message,\n sound: true,\n wait: true,\n },\n (err: Error | null) => {\n if (err) {\n logger.warn(`Desktop notification failed: ${err.message}`);\n }\n resolve();\n },\n );\n\n if (this.dashboardUrl && notification) {\n const url = this.dashboardUrl;\n notification.on('click', () => {\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', `Start-Process \"${url}\"`]);\n } else if (process.platform === 'darwin') {\n execFile('open', [url]);\n } else {\n execFile('xdg-open', [url]);\n }\n });\n }\n });\n }\n\n private checkWindowsNotifications(): Promise<boolean> {\n return new Promise((resolve) => {\n execFile(\n 'powershell',\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\n (err, stdout) => {\n if (err) { resolve(true); return; } // assume enabled on error\n const value = stdout.trim();\n resolve(value !== '0');\n },\n );\n });\n }\n\n private openNotificationSettings(): void {\n if (this.notificationSettingsOpened) return;\n this.notificationSettingsOpened = true;\n\n logger.warn('Windows notifications are disabled. Opening notification settings...');\n logger.warn('Please enable notifications for this app, then try again.');\n\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\n }\n\n // Allow re-opening after 5 minutes\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\n }\n\n private async sendWebhook(\n webhook: WebhookConfig,\n title: string,\n message: string,\n level: NotifyLevel,\n ): Promise<void> {\n try {\n const response = await fetch(webhook.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...webhook.headers,\n },\n body: JSON.stringify({\n title,\n message,\n level,\n timestamp: Date.now(),\n source: 'claude-alarm',\n }),\n });\n\n if (!response.ok) {\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\n }\n } catch (err) {\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\n }\n }\n}\n","import WebSocket from 'ws';\nimport { logger } from '../shared/logger.js';\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\n\nexport class HubClient {\n private ws: WebSocket | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\n private queue: ChannelMessage[] = [];\n private connected = false;\n\n constructor(\n private sessionId: string,\n private sessionName: string,\n private hubHost = DEFAULT_HUB_HOST,\n private hubPort = DEFAULT_HUB_PORT,\n private token?: string,\n ) {}\n\n connect(): void {\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\n logger.debug(`Connecting to hub at ${url}`);\n\n try {\n this.ws = new WebSocket(url);\n\n this.ws.on('open', () => {\n logger.info('Connected to hub');\n this.connected = true;\n\n // Register this session\n const registration: ChannelMessage = {\n type: 'register',\n session: {\n id: this.sessionId,\n name: this.sessionName,\n status: 'idle',\n connectedAt: Date.now(),\n lastActivity: Date.now(),\n cwd: process.cwd(),\n },\n };\n this.ws!.send(JSON.stringify(registration));\n\n // Flush queued messages\n for (const msg of this.queue) {\n this.ws!.send(JSON.stringify(msg));\n }\n this.queue = [];\n });\n\n this.ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n for (const handler of this.messageHandlers) {\n handler(msg);\n }\n } catch (err) {\n logger.warn('Failed to parse hub message:', err);\n }\n });\n\n this.ws.on('close', () => {\n logger.info('Disconnected from hub');\n this.connected = false;\n this.scheduleReconnect();\n });\n\n this.ws.on('error', (err) => {\n logger.debug(`Hub connection error: ${err.message}`);\n this.connected = false;\n });\n } catch {\n logger.debug('Failed to connect to hub, will retry');\n this.scheduleReconnect();\n }\n }\n\n send(msg: ChannelMessage): void {\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n } else {\n if (this.queue.length < 100) {\n this.queue.push(msg);\n }\n logger.debug('Hub not connected, message queued');\n }\n }\n\n onMessage(handler: (msg: ChannelMessage) => void): void {\n this.messageHandlers.push(handler);\n }\n\n disconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.connected = false;\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, 5000);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\nimport type { AppConfig } from './types.js';\n\nconst DEFAULT_CONFIG: AppConfig = {\n hub: {\n host: DEFAULT_HUB_HOST,\n port: DEFAULT_HUB_PORT,\n },\n notifications: {\n desktop: true,\n sound: true,\n },\n webhooks: [],\n};\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): AppConfig {\n ensureConfigDir();\n let config: AppConfig;\n if (!fs.existsSync(CONFIG_FILE)) {\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\n } else {\n try {\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n const parsed = JSON.parse(raw);\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };\n } catch {\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\n }\n }\n\n // Auto-generate token if missing\n if (!config.hub.token) {\n config.hub.token = randomUUID();\n saveConfig(config);\n }\n\n return config;\n}\n\n/** Get the current token, generating one if needed */\nexport function getOrCreateToken(): string {\n const config = loadConfig();\n return config.hub.token!;\n}\n\nexport function saveConfig(config: AppConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\n}\n\n/**\n * Add claude-alarm as an MCP channel server to .mcp.json\n */\nexport function setupMcpConfig(targetDir?: string): string {\n const dir = targetDir ?? process.cwd();\n const mcpPath = path.join(dir, '.mcp.json');\n\n let mcpConfig: Record<string, any> = {};\n if (fs.existsSync(mcpPath)) {\n try {\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\n } catch {\n mcpConfig = {};\n }\n }\n\n if (!mcpConfig.mcpServers) {\n mcpConfig.mcpServers = {};\n }\n\n mcpConfig.mcpServers['claude-alarm'] = {\n command: 'npx',\n args: ['-y', '@delt/claude-alarm', 'serve'],\n env: {\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\n },\n };\n\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\n return mcpPath;\n}\n"],"mappings":"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","names":["path","path","WebSocket","WebSocket","fs","path","fs","fs","path"]}
|
|
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/channel/hub-client.ts","../src/shared/config.ts"],"sourcesContent":["import http from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport { logger } from '../shared/logger.js';\nimport {\n DEFAULT_HUB_HOST,\n DEFAULT_HUB_PORT,\n WS_PATH_CHANNEL,\n WS_PATH_DASHBOARD,\n} from '../shared/constants.js';\nimport { SessionManager } from './session-manager.js';\nimport { Notifier } from './notifier.js';\nimport type { ChannelMessage, AppConfig } from '../shared/types.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport class HubServer {\n private httpServer: http.Server;\n private wssChannel: WebSocketServer;\n private wssDashboard: WebSocketServer;\n private sessions = new SessionManager();\n private notifier = new Notifier();\n private startTime = Date.now();\n\n // Map sessionId -> channel WebSocket\n private channelSockets = new Map<string, WebSocket>();\n // All connected dashboard WebSockets\n private dashboardSockets = new Set<WebSocket>();\n\n private host: string;\n private port: number;\n private token?: string;\n\n constructor(config?: Partial<AppConfig>) {\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\n this.token = config?.hub?.token;\n\n if (config?.notifications) {\n this.notifier.configure({\n desktop: config.notifications.desktop,\n });\n }\n if (config?.webhooks) {\n this.notifier.configure({ webhooks: config.webhooks });\n }\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\n\n // HTTP Server\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\n\n // WebSocket for channel servers\n this.wssChannel = new WebSocketServer({ noServer: true });\n this.wssChannel.on('connection', (ws) => this.handleChannelConnection(ws));\n\n // WebSocket for dashboard\n this.wssDashboard = new WebSocketServer({ noServer: true });\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\n\n // Route WebSocket upgrade requests\n this.httpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n // Token auth for WebSocket connections (skip for local requests)\n if (this.token && !this.isLocalRequest(req)) {\n const wsToken = url.searchParams.get('token');\n if (wsToken !== this.token) {\n socket.destroy();\n return;\n }\n }\n\n if (pathname === WS_PATH_CHANNEL) {\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\n this.wssChannel.emit('connection', ws, req);\n });\n } else if (pathname === WS_PATH_DASHBOARD) {\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\n this.wssDashboard.emit('connection', ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n }\n\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.httpServer.on('error', reject);\n this.httpServer.listen(this.port, this.host, () => {\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\n resolve();\n });\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n // Force-close all WebSocket connections\n for (const ws of this.channelSockets.values()) ws.terminate();\n for (const ws of this.dashboardSockets) ws.terminate();\n this.channelSockets.clear();\n this.dashboardSockets.clear();\n\n this.wssChannel.close();\n this.wssDashboard.close();\n this.httpServer.close(() => {\n logger.info('Hub server stopped');\n resolve();\n });\n\n // Force resolve after 3 seconds if server won't close\n setTimeout(() => {\n logger.warn('Force shutting down');\n resolve();\n }, 3000);\n });\n }\n\n // --- HTTP Handler ---\n\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n\n // CORS headers - restrict to same origin\n const origin = req.headers.origin;\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n }\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Token auth for API endpoints (skip dashboard HTML serving)\n if (url.pathname !== '/' && this.token) {\n if (!this.isLocalRequest(req)) {\n const authHeader = req.headers['authorization'];\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\n if (bearerToken !== this.token) {\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\n return;\n }\n }\n }\n\n // Route\n if (url.pathname === '/' && req.method === 'GET') {\n this.serveDashboard(res);\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\n this.jsonResponse(res, 200, {\n running: true,\n pid: process.pid,\n port: this.port,\n sessions: this.sessions.count(),\n uptime: Date.now() - this.startTime,\n });\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\n this.handleApiSend(req, res);\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\n this.handleApiNotify(req, res);\n } else {\n this.jsonResponse(res, 404, { error: 'Not found' });\n }\n }\n\n private serveDashboard(res: http.ServerResponse): void {\n // Look for dashboard HTML relative to this file (dist) or source\n const candidates = [\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\n ];\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n const html = fs.readFileSync(candidate, 'utf-8');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\n }\n\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { sessionId, content } = body as { sessionId?: string; content?: string };\n if (!sessionId || !content) {\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\n return;\n }\n\n const ws = this.channelSockets.get(sessionId);\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n this.jsonResponse(res, 404, { error: 'Session not connected' });\n return;\n }\n\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\n ws.send(JSON.stringify(msg));\n this.jsonResponse(res, 200, { ok: true });\n }\n\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const body = await this.readBody(req);\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\n\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\n if (!title || !message) {\n this.jsonResponse(res, 400, { error: 'title and message are required' });\n return;\n }\n\n await this.notifier.notify(title, message, (level as any) ?? 'info');\n this.jsonResponse(res, 200, { ok: true });\n }\n\n // --- Channel WebSocket ---\n\n private handleChannelConnection(ws: WebSocket): void {\n logger.info('Channel server connected');\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n this.handleChannelMessage(ws, msg);\n } catch {\n logger.warn('Invalid message from channel');\n }\n });\n\n ws.on('close', () => {\n // Find and remove the session for this socket\n for (const [sessionId, sock] of this.channelSockets) {\n if (sock === ws) {\n const session = this.sessions.unregister(sessionId);\n this.channelSockets.delete(sessionId);\n logger.info(`Channel disconnected: ${sessionId}`);\n this.broadcastToDashboards({\n type: 'session_disconnected',\n sessionId,\n });\n break;\n }\n }\n });\n }\n\n private handleChannelMessage(ws: WebSocket, msg: ChannelMessage): void {\n switch (msg.type) {\n case 'register': {\n const session = msg.session;\n const isReregister = !!this.sessions.get(session.id);\n this.sessions.register(session);\n this.channelSockets.set(session.id, ws);\n logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);\n this.broadcastToDashboards({\n type: isReregister ? 'session_updated' : 'session_connected',\n session,\n });\n break;\n }\n\n case 'status': {\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\n if (updated) {\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\n }\n break;\n }\n\n case 'notify': {\n this.sessions.updateActivity(msg.sessionId);\n this.notifier.notify(msg.title, msg.message, msg.level ?? 'info');\n this.broadcastToDashboards({\n type: 'notification',\n sessionId: msg.sessionId,\n title: msg.title,\n message: msg.message,\n level: msg.level,\n timestamp: Date.now(),\n });\n break;\n }\n\n case 'reply': {\n this.sessions.updateActivity(msg.sessionId);\n const session = this.sessions.get(msg.sessionId);\n const senderName = session?.name ?? msg.sessionId.slice(0, 8);\n this.notifier.notify(senderName, msg.content, 'info');\n this.broadcastToDashboards({\n type: 'reply_from_session',\n sessionId: msg.sessionId,\n content: msg.content,\n timestamp: Date.now(),\n });\n break;\n }\n }\n }\n\n // --- Dashboard WebSocket ---\n\n private handleDashboardConnection(ws: WebSocket): void {\n this.dashboardSockets.add(ws);\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\n\n // Send current session list\n const sessionsMsg: ChannelMessage = {\n type: 'sessions_list',\n sessions: this.sessions.getAll(),\n };\n ws.send(JSON.stringify(sessionsMsg));\n\n ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n if (msg.type === 'message_to_session') {\n const channelWs = this.channelSockets.get(msg.sessionId);\n if (channelWs?.readyState === WebSocket.OPEN) {\n channelWs.send(JSON.stringify(msg));\n }\n }\n } catch {\n logger.warn('Invalid message from dashboard');\n }\n });\n\n ws.on('close', () => {\n this.dashboardSockets.delete(ws);\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\n });\n }\n\n // --- Helpers ---\n\n private broadcastToDashboards(msg: ChannelMessage): void {\n const payload = JSON.stringify(msg);\n for (const ws of this.dashboardSockets) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(payload);\n }\n }\n }\n\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n }\n\n private isLocalRequest(req: http.IncomingMessage): boolean {\n const addr = req.socket.remoteAddress;\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\n }\n\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\n return new Promise((resolve) => {\n let data = '';\n let size = 0;\n req.on('data', (chunk) => {\n size += chunk.length;\n if (size > maxSize) {\n req.destroy();\n resolve(null);\n return;\n }\n data += chunk;\n });\n req.on('end', () => {\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(null);\n }\n });\n });\n }\n}\n\n// Direct execution support\nif (process.argv[1] && (\n process.argv[1].endsWith('hub/server.js') ||\n process.argv[1].endsWith('hub/server.ts')\n)) {\n const hub = new HubServer();\n hub.start().catch((err) => {\n logger.error('Failed to start hub:', err);\n process.exit(1);\n });\n\n const shutdown = () => {\n hub.stop().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n","/**\n * Logger that writes to stderr only.\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\n * Any console.log() would corrupt the protocol. Always use this logger.\n */\nexport const logger = {\n info(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm] ${msg}`, ...args);\n },\n warn(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\n },\n error(msg: string, ...args: unknown[]) {\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\n },\n debug(msg: string, ...args: unknown[]) {\n if (process.env.CLAUDE_ALARM_DEBUG) {\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\n }\n },\n};\n","import path from 'node:path';\nimport os from 'node:os';\n\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\nexport const DEFAULT_HUB_PORT = 7890;\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\n\nexport const WS_PATH_CHANNEL = '/ws/channel';\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\n\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\n\nexport class SessionManager {\n private sessions = new Map<string, SessionInfo>();\n\n register(session: SessionInfo): void {\n this.sessions.set(session.id, { ...session });\n }\n\n unregister(sessionId: string): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n this.sessions.delete(sessionId);\n return session;\n }\n\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.status = status;\n session.lastActivity = Date.now();\n }\n return session;\n }\n\n updateActivity(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActivity = Date.now();\n }\n }\n\n get(sessionId: string): SessionInfo | undefined {\n return this.sessions.get(sessionId);\n }\n\n getAll(): SessionInfo[] {\n return Array.from(this.sessions.values());\n }\n\n count(): number {\n return this.sessions.size;\n }\n}\n","import notifier from 'node-notifier';\nimport { execFile } from 'node:child_process';\nimport { logger } from '../shared/logger.js';\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\n\nexport class Notifier {\n private webhooks: WebhookConfig[] = [];\n private desktopEnabled = true;\n private notificationSettingsOpened = false;\n private dashboardUrl?: string;\n\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string }): void {\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\n if (options.webhooks) this.webhooks = options.webhooks;\n }\n\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\n const promises: Promise<void>[] = [];\n\n if (this.desktopEnabled) {\n promises.push(this.sendDesktop(title, message, level));\n }\n\n for (const webhook of this.webhooks) {\n promises.push(this.sendWebhook(webhook, title, message, level));\n }\n\n await Promise.allSettled(promises);\n }\n\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\n if (process.platform === 'win32') {\n // Check if notifications are enabled by running snoretoast directly\n const enabled = await this.checkWindowsNotifications();\n if (!enabled) {\n this.openNotificationSettings();\n return;\n }\n }\n\n return new Promise((resolve) => {\n const notification = (notifier as any).notify(\n {\n title: `Claude Alarm: ${title}`,\n message,\n sound: true,\n wait: true,\n },\n (err: Error | null) => {\n if (err) {\n logger.warn(`Desktop notification failed: ${err.message}`);\n }\n resolve();\n },\n );\n\n if (this.dashboardUrl && notification) {\n const url = this.dashboardUrl;\n notification.on('click', () => {\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', `Start-Process \"${url}\"`]);\n } else if (process.platform === 'darwin') {\n execFile('open', [url]);\n } else {\n execFile('xdg-open', [url]);\n }\n });\n }\n });\n }\n\n private checkWindowsNotifications(): Promise<boolean> {\n return new Promise((resolve) => {\n execFile(\n 'powershell',\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\n (err, stdout) => {\n if (err) { resolve(true); return; } // assume enabled on error\n const value = stdout.trim();\n resolve(value !== '0');\n },\n );\n });\n }\n\n private openNotificationSettings(): void {\n if (this.notificationSettingsOpened) return;\n this.notificationSettingsOpened = true;\n\n logger.warn('Windows notifications are disabled. Opening notification settings...');\n logger.warn('Please enable notifications for this app, then try again.');\n\n if (process.platform === 'win32') {\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\n }\n\n // Allow re-opening after 5 minutes\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\n }\n\n private async sendWebhook(\n webhook: WebhookConfig,\n title: string,\n message: string,\n level: NotifyLevel,\n ): Promise<void> {\n try {\n const response = await fetch(webhook.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...webhook.headers,\n },\n body: JSON.stringify({\n title,\n message,\n level,\n timestamp: Date.now(),\n source: 'claude-alarm',\n }),\n });\n\n if (!response.ok) {\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\n }\n } catch (err) {\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\n }\n }\n}\n","import WebSocket from 'ws';\nimport { logger } from '../shared/logger.js';\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\n\nexport class HubClient {\n private ws: WebSocket | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\n private queue: ChannelMessage[] = [];\n private connected = false;\n\n constructor(\n private sessionId: string,\n private sessionName: string,\n private hubHost = DEFAULT_HUB_HOST,\n private hubPort = DEFAULT_HUB_PORT,\n private token?: string,\n ) {}\n\n connect(): void {\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\n logger.debug(`Connecting to hub at ${url}`);\n\n try {\n this.ws = new WebSocket(url);\n\n this.ws.on('open', () => {\n logger.info('Connected to hub');\n this.connected = true;\n\n // Register this session\n const registration: ChannelMessage = {\n type: 'register',\n session: {\n id: this.sessionId,\n name: this.sessionName,\n status: 'idle',\n connectedAt: Date.now(),\n lastActivity: Date.now(),\n cwd: process.cwd(),\n channelEnabled: true,\n },\n };\n this.ws!.send(JSON.stringify(registration));\n\n // Flush queued messages\n for (const msg of this.queue) {\n this.ws!.send(JSON.stringify(msg));\n }\n this.queue = [];\n });\n\n this.ws.on('message', (data) => {\n try {\n const msg = JSON.parse(data.toString()) as ChannelMessage;\n for (const handler of this.messageHandlers) {\n handler(msg);\n }\n } catch (err) {\n logger.warn('Failed to parse hub message:', err);\n }\n });\n\n this.ws.on('close', () => {\n logger.info('Disconnected from hub');\n this.connected = false;\n this.scheduleReconnect();\n });\n\n this.ws.on('error', (err) => {\n logger.debug(`Hub connection error: ${err.message}`);\n this.connected = false;\n });\n } catch {\n logger.debug('Failed to connect to hub, will retry');\n this.scheduleReconnect();\n }\n }\n\n send(msg: ChannelMessage): void {\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n } else {\n if (this.queue.length < 100) {\n this.queue.push(msg);\n }\n logger.debug('Hub not connected, message queued');\n }\n }\n\n onMessage(handler: (msg: ChannelMessage) => void): void {\n this.messageHandlers.push(handler);\n }\n\n disconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.connected = false;\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, 5000);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\nimport type { AppConfig } from './types.js';\n\nconst DEFAULT_CONFIG: AppConfig = {\n hub: {\n host: DEFAULT_HUB_HOST,\n port: DEFAULT_HUB_PORT,\n },\n notifications: {\n desktop: true,\n sound: true,\n },\n webhooks: [],\n};\n\nexport function ensureConfigDir(): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): AppConfig {\n ensureConfigDir();\n let config: AppConfig;\n if (!fs.existsSync(CONFIG_FILE)) {\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\n } else {\n try {\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n const parsed = JSON.parse(raw);\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };\n } catch {\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\n }\n }\n\n // Auto-generate token if missing\n if (!config.hub.token) {\n config.hub.token = randomUUID();\n saveConfig(config);\n }\n\n return config;\n}\n\n/** Get the current token, generating one if needed */\nexport function getOrCreateToken(): string {\n const config = loadConfig();\n return config.hub.token!;\n}\n\nexport function saveConfig(config: AppConfig): void {\n ensureConfigDir();\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\n}\n\n/**\n * Add claude-alarm as an MCP channel server to .mcp.json\n */\nexport function setupMcpConfig(targetDir?: string): string {\n const dir = targetDir ?? process.cwd();\n const mcpPath = path.join(dir, '.mcp.json');\n\n let mcpConfig: Record<string, any> = {};\n if (fs.existsSync(mcpPath)) {\n try {\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\n } catch {\n mcpConfig = {};\n }\n }\n\n if (!mcpConfig.mcpServers) {\n mcpConfig.mcpServers = {};\n }\n\n mcpConfig.mcpServers['claude-alarm'] = {\n command: 'npx',\n args: ['-y', '@delt/claude-alarm', 'serve'],\n env: {\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\n },\n };\n\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\n return mcpPath;\n}\n"],"mappings":"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","names":["path","path","WebSocket","WebSocket","fs","path","fs","fs","path"]}
|
package/package.json
CHANGED
package/src/dashboard/index.html
CHANGED
|
@@ -99,6 +99,16 @@
|
|
|
99
99
|
.session-status.idle { background: rgba(139,143,163,0.15); color: var(--text-dim); }
|
|
100
100
|
.session-status.working { background: rgba(96,165,250,0.15); color: var(--blue); }
|
|
101
101
|
.session-status.waiting_input { background: rgba(245,197,66,0.15); color: var(--yellow); }
|
|
102
|
+
.channel-badge {
|
|
103
|
+
display: inline-block;
|
|
104
|
+
font-size: 10px;
|
|
105
|
+
padding: 1px 6px;
|
|
106
|
+
border-radius: 8px;
|
|
107
|
+
margin-left: 6px;
|
|
108
|
+
font-weight: 500;
|
|
109
|
+
}
|
|
110
|
+
.channel-badge.enabled { background: rgba(61,214,140,0.15); color: var(--green); }
|
|
111
|
+
.channel-badge.disabled { background: rgba(239,68,68,0.15); color: var(--red); }
|
|
102
112
|
|
|
103
113
|
.no-sessions {
|
|
104
114
|
color: var(--text-dim);
|
|
@@ -217,6 +227,54 @@
|
|
|
217
227
|
.notif-level.warning { background: var(--yellow); }
|
|
218
228
|
.notif-level.error { background: var(--red); }
|
|
219
229
|
|
|
230
|
+
.message-body h2, .message-body h3, .message-body h4 {
|
|
231
|
+
margin: 8px 0 4px;
|
|
232
|
+
font-size: 14px;
|
|
233
|
+
}
|
|
234
|
+
.message-body h2 { font-size: 16px; }
|
|
235
|
+
.message-body h3 { font-size: 15px; }
|
|
236
|
+
.message-body pre {
|
|
237
|
+
background: var(--bg);
|
|
238
|
+
border: 1px solid var(--border);
|
|
239
|
+
border-radius: 6px;
|
|
240
|
+
padding: 8px 10px;
|
|
241
|
+
overflow-x: auto;
|
|
242
|
+
margin: 6px 0;
|
|
243
|
+
font-size: 12px;
|
|
244
|
+
}
|
|
245
|
+
.message-body code {
|
|
246
|
+
background: var(--bg);
|
|
247
|
+
padding: 1px 4px;
|
|
248
|
+
border-radius: 3px;
|
|
249
|
+
font-size: 12px;
|
|
250
|
+
font-family: monospace;
|
|
251
|
+
}
|
|
252
|
+
.message-body pre code {
|
|
253
|
+
background: none;
|
|
254
|
+
padding: 0;
|
|
255
|
+
}
|
|
256
|
+
.message-body table {
|
|
257
|
+
border-collapse: collapse;
|
|
258
|
+
margin: 6px 0;
|
|
259
|
+
font-size: 12px;
|
|
260
|
+
width: 100%;
|
|
261
|
+
}
|
|
262
|
+
.message-body th, .message-body td {
|
|
263
|
+
border: 1px solid var(--border);
|
|
264
|
+
padding: 4px 8px;
|
|
265
|
+
text-align: left;
|
|
266
|
+
}
|
|
267
|
+
.message-body th {
|
|
268
|
+
background: var(--bg);
|
|
269
|
+
font-weight: 600;
|
|
270
|
+
}
|
|
271
|
+
.message-body ul {
|
|
272
|
+
margin: 4px 0;
|
|
273
|
+
padding-left: 20px;
|
|
274
|
+
}
|
|
275
|
+
.message-body li { margin: 2px 0; }
|
|
276
|
+
.message-body strong { font-weight: 600; }
|
|
277
|
+
|
|
220
278
|
.empty-state {
|
|
221
279
|
color: var(--text-dim);
|
|
222
280
|
font-size: 13px;
|
|
@@ -482,6 +540,7 @@
|
|
|
482
540
|
if (!state.messages[id]) state.messages[id] = [];
|
|
483
541
|
$('#msgInput').disabled = false;
|
|
484
542
|
$('#sendBtn').disabled = false;
|
|
543
|
+
$('#msgInput').placeholder = 'Send a message to session...';
|
|
485
544
|
renderSessions();
|
|
486
545
|
renderMessages();
|
|
487
546
|
}
|
|
@@ -510,9 +569,10 @@
|
|
|
510
569
|
el.innerHTML = msgs.map(m => {
|
|
511
570
|
const cls = m.from === 'session' ? 'from-session' : 'from-dashboard';
|
|
512
571
|
const timeStr = new Date(m.time).toLocaleTimeString();
|
|
572
|
+
const content = m.from === 'session' ? renderMarkdown(m.content) : esc(m.content);
|
|
513
573
|
return `<div class="message ${cls}">
|
|
514
574
|
<div class="message-meta">${m.from === 'session' ? 'Claude' : 'You'} · ${timeStr}</div>
|
|
515
|
-
|
|
575
|
+
<div class="message-body">${content}</div>
|
|
516
576
|
</div>`;
|
|
517
577
|
}).join('');
|
|
518
578
|
|
|
@@ -571,6 +631,40 @@
|
|
|
571
631
|
return d.innerHTML;
|
|
572
632
|
}
|
|
573
633
|
|
|
634
|
+
function renderMarkdown(text) {
|
|
635
|
+
let html = esc(text);
|
|
636
|
+
// Code blocks (```)
|
|
637
|
+
html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
|
|
638
|
+
// Inline code
|
|
639
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
640
|
+
// Bold
|
|
641
|
+
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
642
|
+
// Italic
|
|
643
|
+
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
644
|
+
// Headers
|
|
645
|
+
html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
|
|
646
|
+
html = html.replace(/^## (.+)$/gm, '<h3>$1</h3>');
|
|
647
|
+
html = html.replace(/^# (.+)$/gm, '<h2>$1</h2>');
|
|
648
|
+
// Tables
|
|
649
|
+
html = html.replace(/^(\|.+\|)\n\|[-| :]+\|\n((?:\|.+\|\n?)*)/gm, (_, header, body) => {
|
|
650
|
+
const ths = header.split('|').filter(c => c.trim()).map(c => `<th>${c.trim()}</th>`).join('');
|
|
651
|
+
const rows = body.trim().split('\n').map(row => {
|
|
652
|
+
const tds = row.split('|').filter(c => c.trim()).map(c => `<td>${c.trim()}</td>`).join('');
|
|
653
|
+
return `<tr>${tds}</tr>`;
|
|
654
|
+
}).join('');
|
|
655
|
+
return `<table><thead><tr>${ths}</tr></thead><tbody>${rows}</tbody></table>`;
|
|
656
|
+
});
|
|
657
|
+
// List items
|
|
658
|
+
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
659
|
+
html = html.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');
|
|
660
|
+
// Line breaks (but not inside pre/table)
|
|
661
|
+
html = html.replace(/\n/g, '<br>');
|
|
662
|
+
// Clean up extra <br> around block elements
|
|
663
|
+
html = html.replace(/<br>\s*(<\/?(?:pre|h[2-4]|ul|li|table|thead|tbody|tr))/g, '$1');
|
|
664
|
+
html = html.replace(/(<\/(?:pre|h[2-4]|ul|table)>)\s*<br>/g, '$1');
|
|
665
|
+
return html;
|
|
666
|
+
}
|
|
667
|
+
|
|
574
668
|
// Start - try to get token from URL or storage
|
|
575
669
|
state.token = getToken();
|
|
576
670
|
connect();
|