@delt/claude-alarm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +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 this.notifier.configure({ dashboardUrl: `http://${this.host}:${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 logger.info(`Hub server listening on http://${this.host}:${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 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 execFile('powershell', ['-Command', `Start-Process \"${url}\"`]);\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'],\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":";AAAA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAOA,WAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB,iBAAiB;;;ACCpC,IAAM,SAAS;AAAA,EACpB,KAAK,QAAgB,MAAiB;AACpC,YAAQ,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI;AAAA,EAChD;AAAA,EACA,KAAK,QAAgB,MAAiB;AACpC,YAAQ,MAAM,uBAAuB,GAAG,IAAI,GAAG,IAAI;AAAA,EACrD;AAAA,EACA,MAAM,QAAgB,MAAiB;AACrC,YAAQ,MAAM,wBAAwB,GAAG,IAAI,GAAG,IAAI;AAAA,EACtD;AAAA,EACA,MAAM,QAAgB,MAAiB;AACrC,QAAI,QAAQ,IAAI,oBAAoB;AAClC,cAAQ,MAAM,wBAAwB,GAAG,IAAI,GAAG,IAAI;AAAA,IACtD;AAAA,EACF;AACF;;;ACpBA,OAAO,UAAU;AACjB,OAAO,QAAQ;AAER,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AAEzB,IAAM,aAAa,KAAK,KAAK,GAAG,QAAQ,GAAG,eAAe;AAC1D,IAAM,cAAc,KAAK,KAAK,YAAY,aAAa;AACvD,IAAM,WAAW,KAAK,KAAK,YAAY,SAAS;AAChD,IAAM,WAAW,KAAK,KAAK,YAAY,SAAS;AAEhD,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAE1B,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;;;ACb/B,IAAM,iBAAN,MAAqB;AAAA,EAClB,WAAW,oBAAI,IAAyB;AAAA,EAEhD,SAAS,SAA4B;AACnC,SAAK,SAAS,IAAI,QAAQ,IAAI,EAAE,GAAG,QAAQ,CAAC;AAAA,EAC9C;AAAA,EAEA,WAAW,WAA4C;AACrD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,SAAK,SAAS,OAAO,SAAS;AAC9B,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,WAAmB,QAAgD;AAC9E,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,SAAS;AACX,cAAQ,SAAS;AACjB,cAAQ,eAAe,KAAK,IAAI;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,WAAyB;AACtC,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,SAAS;AACX,cAAQ,eAAe,KAAK,IAAI;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,IAAI,WAA4C;AAC9C,WAAO,KAAK,SAAS,IAAI,SAAS;AAAA,EACpC;AAAA,EAEA,SAAwB;AACtB,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;AAAA,EAC1C;AAAA,EAEA,QAAgB;AACd,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;;;AC1CA,OAAO,cAAc;AACrB,SAAS,gBAAgB;AAIlB,IAAM,WAAN,MAAe;AAAA,EACZ,WAA4B,CAAC;AAAA,EAC7B,iBAAiB;AAAA,EACjB,6BAA6B;AAAA,EAC7B;AAAA,EAER,UAAU,SAAyF;AACjG,QAAI,QAAQ,aAAc,MAAK,eAAe,QAAQ;AACtD,QAAI,QAAQ,YAAY,OAAW,MAAK,iBAAiB,QAAQ;AACjE,QAAI,QAAQ,SAAU,MAAK,WAAW,QAAQ;AAAA,EAChD;AAAA,EAEA,MAAM,OAAO,OAAe,SAAiB,QAAqB,QAAuB;AACvF,UAAM,WAA4B,CAAC;AAEnC,QAAI,KAAK,gBAAgB;AACvB,eAAS,KAAK,KAAK,YAAY,OAAO,SAAS,KAAK,CAAC;AAAA,IACvD;AAEA,eAAW,WAAW,KAAK,UAAU;AACnC,eAAS,KAAK,KAAK,YAAY,SAAS,OAAO,SAAS,KAAK,CAAC;AAAA,IAChE;AAEA,UAAM,QAAQ,WAAW,QAAQ;AAAA,EACnC;AAAA,EAEA,MAAc,YAAY,OAAe,SAAiB,QAAoC;AAC5F,QAAI,QAAQ,aAAa,SAAS;AAEhC,YAAM,UAAU,MAAM,KAAK,0BAA0B;AACrD,UAAI,CAAC,SAAS;AACZ,aAAK,yBAAyB;AAC9B;AAAA,MACF;AAAA,IACF;AAEA,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,eAAgB,SAAiB;AAAA,QACrC;AAAA,UACE,OAAO,iBAAiB,KAAK;AAAA,UAC7B;AAAA,UACA,OAAO;AAAA,UACP,MAAM;AAAA,QACR;AAAA,QACA,CAAC,QAAsB;AACrB,cAAI,KAAK;AACP,mBAAO,KAAK,gCAAgC,IAAI,OAAO,EAAE;AAAA,UAC3D;AACA,kBAAQ;AAAA,QACV;AAAA,MACF;AAEA,UAAI,KAAK,gBAAgB,cAAc;AACrC,cAAM,MAAM,KAAK;AACjB,qBAAa,GAAG,SAAS,MAAM;AAC7B,mBAAS,cAAc,CAAC,YAAY,kBAAkB,GAAG,GAAG,CAAC;AAAA,QAC/D,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,4BAA8C;AACpD,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B;AAAA,QACE;AAAA,QACA,CAAC,YAAY,iKAAiK;AAAA,QAC9K,CAAC,KAAK,WAAW;AACf,cAAI,KAAK;AAAE,oBAAQ,IAAI;AAAG;AAAA,UAAQ;AAClC,gBAAM,QAAQ,OAAO,KAAK;AAC1B,kBAAQ,UAAU,GAAG;AAAA,QACvB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,2BAAiC;AACvC,QAAI,KAAK,2BAA4B;AACrC,SAAK,6BAA6B;AAElC,WAAO,KAAK,sEAAsE;AAClF,WAAO,KAAK,2DAA2D;AAEvE,QAAI,QAAQ,aAAa,SAAS;AAChC,eAAS,cAAc,CAAC,YAAY,yCAAyC,CAAC;AAAA,IAChF;AAGA,eAAW,MAAM;AAAE,WAAK,6BAA6B;AAAA,IAAO,GAAG,IAAI,KAAK,GAAI;AAAA,EAC9E;AAAA,EAEA,MAAc,YACZ,SACA,OACA,SACA,OACe;AACf,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,QAAQ,KAAK;AAAA,QACxC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAG,QAAQ;AAAA,QACb;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA;AAAA,UACA;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,UACpB,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO,KAAK,WAAW,QAAQ,GAAG,aAAa,SAAS,MAAM,EAAE;AAAA,MAClE;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,KAAK,WAAW,QAAQ,GAAG,YAAa,IAAc,OAAO,EAAE;AAAA,IACxE;AAAA,EACF;AACF;;;AJ5GA,IAAM,YAAYC,MAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAEtD,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,IAAI,eAAe;AAAA,EAC9B,WAAW,IAAI,SAAS;AAAA,EACxB,YAAY,KAAK,IAAI;AAAA;AAAA,EAGrB,iBAAiB,oBAAI,IAAuB;AAAA;AAAA,EAE5C,mBAAmB,oBAAI,IAAe;AAAA,EAEtC;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAA6B;AACvC,SAAK,OAAO,QAAQ,KAAK,QAAQ;AACjC,SAAK,OAAO,QAAQ,KAAK,QAAQ;AACjC,SAAK,QAAQ,QAAQ,KAAK;AAE1B,QAAI,QAAQ,eAAe;AACzB,WAAK,SAAS,UAAU;AAAA,QACtB,SAAS,OAAO,cAAc;AAAA,MAChC,CAAC;AAAA,IACH;AACA,QAAI,QAAQ,UAAU;AACpB,WAAK,SAAS,UAAU,EAAE,UAAU,OAAO,SAAS,CAAC;AAAA,IACvD;AACA,SAAK,SAAS,UAAU,EAAE,cAAc,UAAU,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,CAAC;AAG5E,SAAK,aAAa,KAAK,aAAa,CAAC,KAAK,QAAQ,KAAK,WAAW,KAAK,GAAG,CAAC;AAG3E,SAAK,aAAa,IAAI,gBAAgB,EAAE,UAAU,KAAK,CAAC;AACxD,SAAK,WAAW,GAAG,cAAc,CAAC,OAAO,KAAK,wBAAwB,EAAE,CAAC;AAGzE,SAAK,eAAe,IAAI,gBAAgB,EAAE,UAAU,KAAK,CAAC;AAC1D,SAAK,aAAa,GAAG,cAAc,CAAC,OAAO,KAAK,0BAA0B,EAAE,CAAC;AAG7E,SAAK,WAAW,GAAG,WAAW,CAAC,KAAK,QAAQ,SAAS;AACnD,YAAM,MAAM,IAAI,IAAI,IAAI,KAAM,UAAU,IAAI,QAAQ,IAAI,EAAE;AAC1D,YAAM,WAAW,IAAI;AAGrB,UAAI,KAAK,SAAS,CAAC,KAAK,eAAe,GAAG,GAAG;AAC3C,cAAM,UAAU,IAAI,aAAa,IAAI,OAAO;AAC5C,YAAI,YAAY,KAAK,OAAO;AAC1B,iBAAO,QAAQ;AACf;AAAA,QACF;AAAA,MACF;AAEA,UAAI,aAAa,iBAAiB;AAChC,aAAK,WAAW,cAAc,KAAK,QAAQ,MAAM,CAAC,OAAO;AACvD,eAAK,WAAW,KAAK,cAAc,IAAI,GAAG;AAAA,QAC5C,CAAC;AAAA,MACH,WAAW,aAAa,mBAAmB;AACzC,aAAK,aAAa,cAAc,KAAK,QAAQ,MAAM,CAAC,OAAO;AACzD,eAAK,aAAa,KAAK,cAAc,IAAI,GAAG;AAAA,QAC9C,CAAC;AAAA,MACH,OAAO;AACL,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,WAAW,GAAG,SAAS,MAAM;AAClC,WAAK,WAAW,OAAO,KAAK,MAAM,KAAK,MAAM,MAAM;AACjD,eAAO,KAAK,kCAAkC,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;AACtE,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,OAAsB;AACpB,WAAO,IAAI,QAAQ,CAAC,YAAY;AAE9B,iBAAW,MAAM,KAAK,eAAe,OAAO,EAAG,IAAG,MAAM;AACxD,iBAAW,MAAM,KAAK,iBAAkB,IAAG,MAAM;AAEjD,WAAK,WAAW,MAAM;AACtB,WAAK,aAAa,MAAM;AACxB,WAAK,WAAW,MAAM,MAAM;AAC1B,eAAO,KAAK,oBAAoB;AAChC,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,WAAW,KAA2B,KAAgC;AAC5E,UAAM,MAAM,IAAI,IAAI,IAAI,KAAM,UAAU,IAAI,QAAQ,IAAI,EAAE;AAG1D,UAAM,SAAS,IAAI,QAAQ;AAC3B,QAAI,WAAW,OAAO,SAAS,WAAW,KAAK,OAAO,SAAS,WAAW,IAAI;AAC5E,UAAI,UAAU,+BAA+B,MAAM;AAAA,IACrD;AACA,QAAI,UAAU,gCAAgC,oBAAoB;AAClE,QAAI,UAAU,gCAAgC,6BAA6B;AAE3E,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI;AACR;AAAA,IACF;AAGA,QAAI,IAAI,aAAa,OAAO,KAAK,OAAO;AACtC,UAAI,CAAC,KAAK,eAAe,GAAG,GAAG;AAC7B,cAAM,aAAa,IAAI,QAAQ,eAAe;AAC9C,cAAM,cAAc,YAAY,WAAW,SAAS,IAAI,WAAW,MAAM,CAAC,IAAI;AAC9E,YAAI,gBAAgB,KAAK,OAAO;AAC9B,eAAK,aAAa,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;AACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,IAAI,aAAa,OAAO,IAAI,WAAW,OAAO;AAChD,WAAK,eAAe,GAAG;AAAA,IACzB,WAAW,IAAI,aAAa,mBAAmB,IAAI,WAAW,OAAO;AACnE,WAAK,aAAa,KAAK,KAAK,EAAE,UAAU,KAAK,SAAS,OAAO,EAAE,CAAC;AAAA,IAClE,WAAW,IAAI,aAAa,iBAAiB,IAAI,WAAW,OAAO;AACjE,WAAK,aAAa,KAAK,KAAK;AAAA,QAC1B,SAAS;AAAA,QACT,KAAK,QAAQ;AAAA,QACb,MAAM,KAAK;AAAA,QACX,UAAU,KAAK,SAAS,MAAM;AAAA,QAC9B,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC5B,CAAC;AAAA,IACH,WAAW,IAAI,aAAa,eAAe,IAAI,WAAW,QAAQ;AAChE,WAAK,cAAc,KAAK,GAAG;AAAA,IAC7B,WAAW,IAAI,aAAa,iBAAiB,IAAI,WAAW,QAAQ;AAClE,WAAK,gBAAgB,KAAK,GAAG;AAAA,IAC/B,OAAO;AACL,WAAK,aAAa,KAAK,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IACpD;AAAA,EACF;AAAA,EAEQ,eAAe,KAAgC;AAErD,UAAM,aAAa;AAAA,MACjBA,MAAK,KAAK,WAAW,MAAM,aAAa,YAAY;AAAA;AAAA,MACpDA,MAAK,KAAK,WAAW,MAAM,MAAM,OAAO,aAAa,YAAY;AAAA;AAAA,MACjEA,MAAK,KAAK,QAAQ,IAAI,GAAG,QAAQ,aAAa,YAAY;AAAA;AAAA,MAC1DA,MAAK,KAAK,QAAQ,IAAI,GAAG,OAAO,aAAa,YAAY;AAAA;AAAA,IAC3D;AACA,WAAO,MAAM,yBAAyB,KAAK,UAAU,UAAU,CAAC,EAAE;AAElE,eAAW,aAAa,YAAY;AAClC,UAAI,GAAG,WAAW,SAAS,GAAG;AAC5B,cAAM,OAAO,GAAG,aAAa,WAAW,OAAO;AAC/C,YAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,YAAI,IAAI,IAAI;AACZ;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,QAAI,IAAI,wGAAwG;AAAA,EAClH;AAAA,EAEA,MAAc,cAAc,KAA2B,KAAyC;AAC9F,UAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,QAAI,CAAC,MAAM;AAAE,WAAK,aAAa,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;AAAG;AAAA,IAAQ;AAE7E,UAAM,EAAE,WAAW,QAAQ,IAAI;AAC/B,QAAI,CAAC,aAAa,CAAC,SAAS;AAC1B,WAAK,aAAa,KAAK,KAAK,EAAE,OAAO,qCAAqC,CAAC;AAC3E;AAAA,IACF;AAEA,UAAM,KAAK,KAAK,eAAe,IAAI,SAAS;AAC5C,QAAI,CAAC,MAAM,GAAG,eAAe,UAAU,MAAM;AAC3C,WAAK,aAAa,KAAK,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAC9D;AAAA,IACF;AAEA,UAAM,MAAsB,EAAE,MAAM,sBAAsB,WAAW,QAAQ;AAC7E,OAAG,KAAK,KAAK,UAAU,GAAG,CAAC;AAC3B,SAAK,aAAa,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EAC1C;AAAA,EAEA,MAAc,gBAAgB,KAA2B,KAAyC;AAChG,UAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,QAAI,CAAC,MAAM;AAAE,WAAK,aAAa,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;AAAG;AAAA,IAAQ;AAE7E,UAAM,EAAE,OAAO,SAAS,MAAM,IAAI;AAClC,QAAI,CAAC,SAAS,CAAC,SAAS;AACtB,WAAK,aAAa,KAAK,KAAK,EAAE,OAAO,iCAAiC,CAAC;AACvE;AAAA,IACF;AAEA,UAAM,KAAK,SAAS,OAAO,OAAO,SAAU,SAAiB,MAAM;AACnE,SAAK,aAAa,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EAC1C;AAAA;AAAA,EAIQ,wBAAwB,IAAqB;AACnD,WAAO,KAAK,0BAA0B;AAEtC,OAAG,GAAG,WAAW,CAAC,SAAS;AACzB,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AACtC,aAAK,qBAAqB,IAAI,GAAG;AAAA,MACnC,QAAQ;AACN,eAAO,KAAK,8BAA8B;AAAA,MAC5C;AAAA,IACF,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AAEnB,iBAAW,CAAC,WAAW,IAAI,KAAK,KAAK,gBAAgB;AACnD,YAAI,SAAS,IAAI;AACf,gBAAM,UAAU,KAAK,SAAS,WAAW,SAAS;AAClD,eAAK,eAAe,OAAO,SAAS;AACpC,iBAAO,KAAK,yBAAyB,SAAS,EAAE;AAChD,eAAK,sBAAsB;AAAA,YACzB,MAAM;AAAA,YACN;AAAA,UACF,CAAC;AACD;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,qBAAqB,IAAe,KAA2B;AACrE,YAAQ,IAAI,MAAM;AAAA,MAChB,KAAK,YAAY;AACf,cAAM,UAAU,IAAI;AACpB,aAAK,SAAS,SAAS,OAAO;AAC9B,aAAK,eAAe,IAAI,QAAQ,IAAI,EAAE;AACtC,eAAO,KAAK,uBAAuB,QAAQ,EAAE,KAAK,QAAQ,IAAI,GAAG;AACjE,aAAK,sBAAsB,EAAE,MAAM,qBAAqB,QAAQ,CAAC;AACjE;AAAA,MACF;AAAA,MAEA,KAAK,UAAU;AACb,cAAM,UAAU,KAAK,SAAS,aAAa,IAAI,WAAW,IAAI,MAAM;AACpE,YAAI,SAAS;AACX,eAAK,sBAAsB,EAAE,MAAM,mBAAmB,SAAS,QAAQ,CAAC;AAAA,QAC1E;AACA;AAAA,MACF;AAAA,MAEA,KAAK,UAAU;AACb,aAAK,SAAS,eAAe,IAAI,SAAS;AAC1C,aAAK,SAAS,OAAO,IAAI,OAAO,IAAI,SAAS,IAAI,SAAS,MAAM;AAChE,aAAK,sBAAsB;AAAA,UACzB,MAAM;AAAA,UACN,WAAW,IAAI;AAAA,UACf,OAAO,IAAI;AAAA,UACX,SAAS,IAAI;AAAA,UACb,OAAO,IAAI;AAAA,UACX,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,aAAK,SAAS,eAAe,IAAI,SAAS;AAC1C,aAAK,sBAAsB;AAAA,UACzB,MAAM;AAAA,UACN,WAAW,IAAI;AAAA,UACf,SAAS,IAAI;AAAA,UACb,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AACD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIQ,0BAA0B,IAAqB;AACrD,SAAK,iBAAiB,IAAI,EAAE;AAC5B,WAAO,KAAK,+BAA+B,KAAK,iBAAiB,IAAI,GAAG;AAGxE,UAAM,cAA8B;AAAA,MAClC,MAAM;AAAA,MACN,UAAU,KAAK,SAAS,OAAO;AAAA,IACjC;AACA,OAAG,KAAK,KAAK,UAAU,WAAW,CAAC;AAEnC,OAAG,GAAG,WAAW,CAAC,SAAS;AACzB,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AACtC,YAAI,IAAI,SAAS,sBAAsB;AACrC,gBAAM,YAAY,KAAK,eAAe,IAAI,IAAI,SAAS;AACvD,cAAI,WAAW,eAAe,UAAU,MAAM;AAC5C,sBAAU,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,UACpC;AAAA,QACF;AAAA,MACF,QAAQ;AACN,eAAO,KAAK,gCAAgC;AAAA,MAC9C;AAAA,IACF,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AACnB,WAAK,iBAAiB,OAAO,EAAE;AAC/B,aAAO,KAAK,kCAAkC,KAAK,iBAAiB,IAAI,GAAG;AAAA,IAC7E,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,sBAAsB,KAA2B;AACvD,UAAM,UAAU,KAAK,UAAU,GAAG;AAClC,eAAW,MAAM,KAAK,kBAAkB;AACtC,UAAI,GAAG,eAAe,UAAU,MAAM;AACpC,WAAG,KAAK,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aAAa,KAA0B,QAAgB,MAAqB;AAClF,QAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAC5D,QAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,EAC9B;AAAA,EAEQ,eAAe,KAAoC;AACzD,UAAM,OAAO,IAAI,OAAO;AACxB,WAAO,SAAS,eAAe,SAAS,SAAS,SAAS;AAAA,EAC5D;AAAA,EAEQ,SAAS,KAA2B,UAAU,OAAO,MAA+B;AAC1F,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,OAAO;AACX,UAAI,OAAO;AACX,UAAI,GAAG,QAAQ,CAAC,UAAU;AACxB,gBAAQ,MAAM;AACd,YAAI,OAAO,SAAS;AAClB,cAAI,QAAQ;AACZ,kBAAQ,IAAI;AACZ;AAAA,QACF;AACA,gBAAQ;AAAA,MACV,CAAC;AACD,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI;AACF,kBAAQ,KAAK,MAAM,IAAI,CAAC;AAAA,QAC1B,QAAQ;AACN,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAGA,IAAI,QAAQ,KAAK,CAAC,MAChB,QAAQ,KAAK,CAAC,EAAE,SAAS,eAAe,KACxC,QAAQ,KAAK,CAAC,EAAE,SAAS,eAAe,IACvC;AACD,QAAM,MAAM,IAAI,UAAU;AAC1B,MAAI,MAAM,EAAE,MAAM,CAAC,QAAQ;AACzB,WAAO,MAAM,wBAAwB,GAAG;AACxC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,QAAM,WAAW,MAAM;AACrB,QAAI,KAAK,EAAE,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,EACvC;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;;;AK5YA,OAAOC,gBAAe;AAKf,IAAM,YAAN,MAAgB;AAAA,EAOrB,YACU,WACA,aACA,UAAU,kBACV,UAAU,kBACV,OACR;AALQ;AACA;AACA;AACA;AACA;AAAA,EACP;AAAA,EAZK,KAAuB;AAAA,EACvB,iBAAuD;AAAA,EACvD,kBAAwD,CAAC;AAAA,EACzD,QAA0B,CAAC;AAAA,EAC3B,YAAY;AAAA,EAUpB,UAAgB;AACd,UAAM,aAAa,KAAK,QAAQ,UAAU,mBAAmB,KAAK,KAAK,CAAC,KAAK;AAC7E,UAAM,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,OAAO,GAAG,eAAe,GAAG,UAAU;AAC/E,WAAO,MAAM,wBAAwB,GAAG,EAAE;AAE1C,QAAI;AACF,WAAK,KAAK,IAAIC,WAAU,GAAG;AAE3B,WAAK,GAAG,GAAG,QAAQ,MAAM;AACvB,eAAO,KAAK,kBAAkB;AAC9B,aAAK,YAAY;AAGjB,cAAM,eAA+B;AAAA,UACnC,MAAM;AAAA,UACN,SAAS;AAAA,YACP,IAAI,KAAK;AAAA,YACT,MAAM,KAAK;AAAA,YACX,QAAQ;AAAA,YACR,aAAa,KAAK,IAAI;AAAA,YACtB,cAAc,KAAK,IAAI;AAAA,YACvB,KAAK,QAAQ,IAAI;AAAA,UACnB;AAAA,QACF;AACA,aAAK,GAAI,KAAK,KAAK,UAAU,YAAY,CAAC;AAG1C,mBAAW,OAAO,KAAK,OAAO;AAC5B,eAAK,GAAI,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,QACnC;AACA,aAAK,QAAQ,CAAC;AAAA,MAChB,CAAC;AAED,WAAK,GAAG,GAAG,WAAW,CAAC,SAAS;AAC9B,YAAI;AACF,gBAAM,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AACtC,qBAAW,WAAW,KAAK,iBAAiB;AAC1C,oBAAQ,GAAG;AAAA,UACb;AAAA,QACF,SAAS,KAAK;AACZ,iBAAO,KAAK,gCAAgC,GAAG;AAAA,QACjD;AAAA,MACF,CAAC;AAED,WAAK,GAAG,GAAG,SAAS,MAAM;AACxB,eAAO,KAAK,uBAAuB;AACnC,aAAK,YAAY;AACjB,aAAK,kBAAkB;AAAA,MACzB,CAAC;AAED,WAAK,GAAG,GAAG,SAAS,CAAC,QAAQ;AAC3B,eAAO,MAAM,yBAAyB,IAAI,OAAO,EAAE;AACnD,aAAK,YAAY;AAAA,MACnB,CAAC;AAAA,IACH,QAAQ;AACN,aAAO,MAAM,sCAAsC;AACnD,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,KAAK,KAA2B;AAC9B,QAAI,KAAK,aAAa,KAAK,IAAI,eAAeA,WAAU,MAAM;AAC5D,WAAK,GAAG,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,IAClC,OAAO;AACL,UAAI,KAAK,MAAM,SAAS,KAAK;AAC3B,aAAK,MAAM,KAAK,GAAG;AAAA,MACrB;AACA,aAAO,MAAM,mCAAmC;AAAA,IAClD;AAAA,EACF;AAAA,EAEA,UAAU,SAA8C;AACtD,SAAK,gBAAgB,KAAK,OAAO;AAAA,EACnC;AAAA,EAEA,aAAmB;AACjB,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;AAAA,IACZ;AACA,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,oBAA0B;AAChC,QAAI,KAAK,eAAgB;AACzB,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AACtB,WAAK,QAAQ;AAAA,IACf,GAAG,GAAI;AAAA,EACT;AACF;;;AClHA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,kBAAkB;AAI3B,IAAM,iBAA4B;AAAA,EAChC,KAAK;AAAA,IACH,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,eAAe;AAAA,IACb,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA,UAAU,CAAC;AACb;AAEO,SAAS,kBAAwB;AACtC,MAAI,CAACC,IAAG,WAAW,UAAU,GAAG;AAC9B,IAAAA,IAAG,UAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,aAAwB;AACtC,kBAAgB;AAChB,MAAI;AACJ,MAAI,CAACA,IAAG,WAAW,WAAW,GAAG;AAC/B,aAAS,EAAE,GAAG,gBAAgB,KAAK,EAAE,GAAG,eAAe,IAAI,EAAE;AAAA,EAC/D,OAAO;AACL,QAAI;AACF,YAAM,MAAMA,IAAG,aAAa,aAAa,OAAO;AAChD,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,eAAS,EAAE,GAAG,gBAAgB,GAAG,QAAQ,KAAK,EAAE,GAAG,eAAe,KAAK,GAAG,OAAO,IAAI,EAAE;AAAA,IACzF,QAAQ;AACN,eAAS,EAAE,GAAG,gBAAgB,KAAK,EAAE,GAAG,eAAe,IAAI,EAAE;AAAA,IAC/D;AAAA,EACF;AAGA,MAAI,CAAC,OAAO,IAAI,OAAO;AACrB,WAAO,IAAI,QAAQ,WAAW;AAC9B,eAAW,MAAM;AAAA,EACnB;AAEA,SAAO;AACT;AAQO,SAAS,WAAW,QAAyB;AAClD,kBAAgB;AAChB,EAAAC,IAAG,cAAc,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,UAAU,SAAS,MAAM,IAAM,CAAC;AACnG;AAKO,SAAS,eAAe,WAA4B;AACzD,QAAM,MAAM,aAAa,QAAQ,IAAI;AACrC,QAAM,UAAUC,MAAK,KAAK,KAAK,WAAW;AAE1C,MAAI,YAAiC,CAAC;AACtC,MAAID,IAAG,WAAW,OAAO,GAAG;AAC1B,QAAI;AACF,kBAAY,KAAK,MAAMA,IAAG,aAAa,SAAS,OAAO,CAAC;AAAA,IAC1D,QAAQ;AACN,kBAAY,CAAC;AAAA,IACf;AAAA,EACF;AAEA,MAAI,CAAC,UAAU,YAAY;AACzB,cAAU,aAAa,CAAC;AAAA,EAC1B;AAEA,YAAU,WAAW,cAAc,IAAI;AAAA,IACrC,SAAS;AAAA,IACT,MAAM,CAAC,MAAM,oBAAoB;AAAA,IACjC,KAAK;AAAA,MACH,2BAA2BC,MAAK,SAAS,GAAG;AAAA,IAC9C;AAAA,EACF;AAEA,EAAAD,IAAG,cAAc,SAAS,KAAK,UAAU,WAAW,MAAM,CAAC,GAAG,OAAO;AACrE,SAAO;AACT;","names":["path","path","WebSocket","WebSocket","fs","path","fs","fs","path"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@delt/claude-alarm",
3
+ "version": "0.1.0",
4
+ "description": "Monitor and get notifications from multiple Claude Code sessions via MCP Channels",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "claude-alarm": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./channel": {
17
+ "import": "./dist/channel/server.js",
18
+ "types": "./dist/channel/server.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src/dashboard/index.html"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup && node -e \"const fs=require('fs');const p=require('path');fs.mkdirSync(p.join('dist','dashboard'),{recursive:true});fs.copyFileSync(p.join('src','dashboard','index.html'),p.join('dist','dashboard','index.html'));\"",
27
+ "dev": "tsup --watch",
28
+ "start:hub": "node dist/cli.js hub start",
29
+ "start:channel": "node dist/channel/server.js"
30
+ },
31
+ "keywords": [
32
+ "claude",
33
+ "claude-code",
34
+ "mcp",
35
+ "channels",
36
+ "notifications",
37
+ "monitoring"
38
+ ],
39
+ "license": "MIT",
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.27.0",
42
+ "node-notifier": "^10.0.0",
43
+ "ws": "^8.18.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "@types/ws": "^8.5.0",
48
+ "tsup": "^8.0.0",
49
+ "typescript": "^5.5.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ }
54
+ }