@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.
@@ -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
@@ -8,6 +8,7 @@ interface SessionInfo {
8
8
  connectedAt: number;
9
9
  lastActivity: number;
10
10
  cwd?: string;
11
+ channelEnabled?: boolean;
11
12
  }
12
13
  /** Messages sent between channel server and hub */
13
14
  type ChannelMessage = {
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.close();
260
- for (const ws of this.dashboardSockets) ws.close();
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({ type: "session_connected", session });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delt/claude-alarm",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Monitor and get notifications from multiple Claude Code sessions via MCP Channels",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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'} &middot; ${timeStr}</div>
515
- ${esc(m.content)}
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();