@delt/claude-alarm 0.3.13 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +23 -0
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +158 -4
- package/dist/hub/server.js +87 -20
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +111 -89
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +158 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/hub/server.ts
|
|
2
2
|
import http from "http";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs2 from "fs";
|
|
4
|
+
import path3 from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { WebSocketServer, WebSocket } from "ws";
|
|
7
7
|
|
|
@@ -24,7 +24,7 @@ var logger = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
// src/hub/server.ts
|
|
27
|
-
import { randomUUID } from "crypto";
|
|
27
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
28
28
|
|
|
29
29
|
// src/shared/constants.ts
|
|
30
30
|
import path from "path";
|
|
@@ -178,8 +178,77 @@ var Notifier = class {
|
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
+
// src/shared/config.ts
|
|
182
|
+
import fs from "fs";
|
|
183
|
+
import path2 from "path";
|
|
184
|
+
import { randomUUID } from "crypto";
|
|
185
|
+
var DEFAULT_CONFIG = {
|
|
186
|
+
hub: {
|
|
187
|
+
host: DEFAULT_HUB_HOST,
|
|
188
|
+
port: DEFAULT_HUB_PORT
|
|
189
|
+
},
|
|
190
|
+
notifications: {
|
|
191
|
+
desktop: true,
|
|
192
|
+
sound: true
|
|
193
|
+
},
|
|
194
|
+
webhooks: []
|
|
195
|
+
};
|
|
196
|
+
function ensureConfigDir() {
|
|
197
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
198
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function loadConfig() {
|
|
202
|
+
ensureConfigDir();
|
|
203
|
+
let config;
|
|
204
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
205
|
+
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
206
|
+
} else {
|
|
207
|
+
try {
|
|
208
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
209
|
+
const parsed = JSON.parse(raw);
|
|
210
|
+
config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };
|
|
211
|
+
} catch {
|
|
212
|
+
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!config.hub.token) {
|
|
216
|
+
config.hub.token = randomUUID();
|
|
217
|
+
saveConfig(config);
|
|
218
|
+
}
|
|
219
|
+
return config;
|
|
220
|
+
}
|
|
221
|
+
function saveConfig(config) {
|
|
222
|
+
ensureConfigDir();
|
|
223
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
224
|
+
}
|
|
225
|
+
function setupMcpConfig(targetDir) {
|
|
226
|
+
const dir = targetDir ?? process.cwd();
|
|
227
|
+
const mcpPath = path2.join(dir, ".mcp.json");
|
|
228
|
+
let mcpConfig = {};
|
|
229
|
+
if (fs.existsSync(mcpPath)) {
|
|
230
|
+
try {
|
|
231
|
+
mcpConfig = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
232
|
+
} catch {
|
|
233
|
+
mcpConfig = {};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!mcpConfig.mcpServers) {
|
|
237
|
+
mcpConfig.mcpServers = {};
|
|
238
|
+
}
|
|
239
|
+
mcpConfig.mcpServers["claude-alarm"] = {
|
|
240
|
+
command: "npx",
|
|
241
|
+
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
242
|
+
env: {
|
|
243
|
+
CLAUDE_ALARM_SESSION_NAME: path2.basename(dir)
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
247
|
+
return mcpPath;
|
|
248
|
+
}
|
|
249
|
+
|
|
181
250
|
// src/hub/server.ts
|
|
182
|
-
var __dirname =
|
|
251
|
+
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
183
252
|
var HubServer = class {
|
|
184
253
|
httpServer;
|
|
185
254
|
wssChannel;
|
|
@@ -307,29 +376,34 @@ var HubServer = class {
|
|
|
307
376
|
this.handleApiSend(req, res);
|
|
308
377
|
} else if (url.pathname === "/api/notify" && req.method === "POST") {
|
|
309
378
|
this.handleApiNotify(req, res);
|
|
379
|
+
} else if (url.pathname === "/api/webhooks" && req.method === "GET") {
|
|
380
|
+
const config = loadConfig();
|
|
381
|
+
this.jsonResponse(res, 200, { webhooks: config.webhooks || [] });
|
|
382
|
+
} else if (url.pathname === "/api/webhooks" && req.method === "POST") {
|
|
383
|
+
this.handleWebhookSave(req, res);
|
|
310
384
|
} else {
|
|
311
385
|
this.jsonResponse(res, 404, { error: "Not found" });
|
|
312
386
|
}
|
|
313
387
|
}
|
|
314
388
|
serveDashboard(res) {
|
|
315
389
|
const candidates = [
|
|
316
|
-
|
|
390
|
+
path3.join(__dirname, "..", "dashboard", "index.html"),
|
|
317
391
|
// from dist/hub/
|
|
318
|
-
|
|
392
|
+
path3.join(__dirname, "dashboard", "index.html"),
|
|
319
393
|
// from dist/ (bundled index.js)
|
|
320
|
-
|
|
394
|
+
path3.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
321
395
|
// from dist/hub/ -> src/
|
|
322
|
-
|
|
396
|
+
path3.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
323
397
|
// from dist/ -> src/
|
|
324
|
-
|
|
398
|
+
path3.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
325
399
|
// from cwd
|
|
326
|
-
|
|
400
|
+
path3.join(process.cwd(), "src", "dashboard", "index.html")
|
|
327
401
|
// from cwd/src
|
|
328
402
|
];
|
|
329
403
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
330
404
|
for (const candidate of candidates) {
|
|
331
|
-
if (
|
|
332
|
-
const html =
|
|
405
|
+
if (fs2.existsSync(candidate)) {
|
|
406
|
+
const html = fs2.readFileSync(candidate, "utf-8");
|
|
333
407
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
334
408
|
res.end(html);
|
|
335
409
|
return;
|
|
@@ -507,11 +581,11 @@ var HubServer = class {
|
|
|
507
581
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
508
582
|
return;
|
|
509
583
|
}
|
|
510
|
-
|
|
584
|
+
fs2.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
511
585
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
512
|
-
const filename = `${
|
|
513
|
-
const filePath =
|
|
514
|
-
|
|
586
|
+
const filename = `${randomUUID2()}.${ext}`;
|
|
587
|
+
const filePath = path3.join(UPLOADS_DIR, filename);
|
|
588
|
+
fs2.writeFileSync(filePath, buffer);
|
|
515
589
|
const forwardMsg = {
|
|
516
590
|
type: "image_to_session",
|
|
517
591
|
sessionId,
|
|
@@ -523,18 +597,35 @@ var HubServer = class {
|
|
|
523
597
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
524
598
|
setTimeout(() => {
|
|
525
599
|
try {
|
|
526
|
-
|
|
600
|
+
fs2.unlinkSync(filePath);
|
|
527
601
|
} catch {
|
|
528
602
|
}
|
|
529
603
|
}, 5 * 60 * 1e3);
|
|
530
604
|
}
|
|
605
|
+
async handleWebhookSave(req, res) {
|
|
606
|
+
const body = await this.readBody(req);
|
|
607
|
+
if (!body) {
|
|
608
|
+
this.jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const { webhooks } = body;
|
|
612
|
+
if (!Array.isArray(webhooks)) {
|
|
613
|
+
this.jsonResponse(res, 400, { error: "webhooks must be an array" });
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const config = loadConfig();
|
|
617
|
+
config.webhooks = webhooks;
|
|
618
|
+
saveConfig(config);
|
|
619
|
+
this.notifier.configure({ webhooks });
|
|
620
|
+
this.jsonResponse(res, 200, { ok: true });
|
|
621
|
+
}
|
|
531
622
|
cleanupUploads() {
|
|
532
623
|
try {
|
|
533
|
-
if (!
|
|
534
|
-
const files =
|
|
624
|
+
if (!fs2.existsSync(UPLOADS_DIR)) return;
|
|
625
|
+
const files = fs2.readdirSync(UPLOADS_DIR);
|
|
535
626
|
for (const file of files) {
|
|
536
627
|
try {
|
|
537
|
-
|
|
628
|
+
fs2.unlinkSync(path3.join(UPLOADS_DIR, file));
|
|
538
629
|
} catch {
|
|
539
630
|
}
|
|
540
631
|
}
|
|
@@ -688,75 +779,6 @@ var HubClient = class {
|
|
|
688
779
|
}, 5e3);
|
|
689
780
|
}
|
|
690
781
|
};
|
|
691
|
-
|
|
692
|
-
// src/shared/config.ts
|
|
693
|
-
import fs2 from "fs";
|
|
694
|
-
import path3 from "path";
|
|
695
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
696
|
-
var DEFAULT_CONFIG = {
|
|
697
|
-
hub: {
|
|
698
|
-
host: DEFAULT_HUB_HOST,
|
|
699
|
-
port: DEFAULT_HUB_PORT
|
|
700
|
-
},
|
|
701
|
-
notifications: {
|
|
702
|
-
desktop: true,
|
|
703
|
-
sound: true
|
|
704
|
-
},
|
|
705
|
-
webhooks: []
|
|
706
|
-
};
|
|
707
|
-
function ensureConfigDir() {
|
|
708
|
-
if (!fs2.existsSync(CONFIG_DIR)) {
|
|
709
|
-
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
function loadConfig() {
|
|
713
|
-
ensureConfigDir();
|
|
714
|
-
let config;
|
|
715
|
-
if (!fs2.existsSync(CONFIG_FILE)) {
|
|
716
|
-
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
717
|
-
} else {
|
|
718
|
-
try {
|
|
719
|
-
const raw = fs2.readFileSync(CONFIG_FILE, "utf-8");
|
|
720
|
-
const parsed = JSON.parse(raw);
|
|
721
|
-
config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };
|
|
722
|
-
} catch {
|
|
723
|
-
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
if (!config.hub.token) {
|
|
727
|
-
config.hub.token = randomUUID2();
|
|
728
|
-
saveConfig(config);
|
|
729
|
-
}
|
|
730
|
-
return config;
|
|
731
|
-
}
|
|
732
|
-
function saveConfig(config) {
|
|
733
|
-
ensureConfigDir();
|
|
734
|
-
fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
735
|
-
}
|
|
736
|
-
function setupMcpConfig(targetDir) {
|
|
737
|
-
const dir = targetDir ?? process.cwd();
|
|
738
|
-
const mcpPath = path3.join(dir, ".mcp.json");
|
|
739
|
-
let mcpConfig = {};
|
|
740
|
-
if (fs2.existsSync(mcpPath)) {
|
|
741
|
-
try {
|
|
742
|
-
mcpConfig = JSON.parse(fs2.readFileSync(mcpPath, "utf-8"));
|
|
743
|
-
} catch {
|
|
744
|
-
mcpConfig = {};
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
if (!mcpConfig.mcpServers) {
|
|
748
|
-
mcpConfig.mcpServers = {};
|
|
749
|
-
}
|
|
750
|
-
mcpConfig.mcpServers["claude-alarm"] = {
|
|
751
|
-
command: "npx",
|
|
752
|
-
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
753
|
-
env: {
|
|
754
|
-
CLAUDE_ALARM_SESSION_NAME: path3.basename(dir)
|
|
755
|
-
}
|
|
756
|
-
};
|
|
757
|
-
fs2.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
758
|
-
return mcpPath;
|
|
759
|
-
}
|
|
760
782
|
export {
|
|
761
783
|
CHANNEL_SERVER_NAME,
|
|
762
784
|
CHANNEL_SERVER_VERSION,
|
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';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { WebSocketServer, WebSocket } from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { randomUUID } from 'node:crypto';\r\nimport {\r\n DEFAULT_HUB_HOST,\r\n DEFAULT_HUB_PORT,\r\n WS_PATH_CHANNEL,\r\n WS_PATH_DASHBOARD,\r\n UPLOADS_DIR,\r\n} from '../shared/constants.js';\r\nimport { SessionManager } from './session-manager.js';\r\nimport { Notifier } from './notifier.js';\r\nimport type { ChannelMessage, AppConfig, SessionInfo } from '../shared/types.js';\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nexport class HubServer {\r\n private httpServer: http.Server;\r\n private wssChannel: WebSocketServer;\r\n private wssDashboard: WebSocketServer;\r\n private sessions = new SessionManager();\r\n private notifier = new Notifier();\r\n private startTime = Date.now();\r\n\r\n // Map sessionId -> channel WebSocket\r\n private channelSockets = new Map<string, WebSocket>();\r\n // Track which channel connections are local\r\n private localChannels = new Set<string>();\r\n // All connected dashboard WebSockets\r\n private dashboardSockets = new Set<WebSocket>();\r\n\r\n private host: string;\r\n private port: number;\r\n private token?: string;\r\n\r\n constructor(config?: Partial<AppConfig>) {\r\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\r\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\r\n this.token = config?.hub?.token;\r\n\r\n if (config?.notifications) {\r\n this.notifier.configure({\r\n desktop: config.notifications.desktop,\r\n });\r\n }\r\n if (config?.webhooks) {\r\n this.notifier.configure({ webhooks: config.webhooks });\r\n }\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\r\n\r\n // HTTP Server\r\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\r\n\r\n // WebSocket for channel servers\r\n this.wssChannel = new WebSocketServer({ noServer: true });\r\n this.wssChannel.on('connection', (ws: WebSocket, req: http.IncomingMessage) => this.handleChannelConnection(ws, req));\r\n\r\n // WebSocket for dashboard\r\n this.wssDashboard = new WebSocketServer({ noServer: true });\r\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\r\n\r\n // Route WebSocket upgrade requests\r\n this.httpServer.on('upgrade', (req, socket, head) => {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n const pathname = url.pathname;\r\n\r\n // Token auth for WebSocket connections (skip for local requests)\r\n if (this.token && !this.isLocalRequest(req)) {\r\n const wsToken = url.searchParams.get('token');\r\n if (wsToken !== this.token) {\r\n socket.destroy();\r\n return;\r\n }\r\n }\r\n\r\n if (pathname === WS_PATH_CHANNEL) {\r\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssChannel.emit('connection', ws, req);\r\n });\r\n } else if (pathname === WS_PATH_DASHBOARD) {\r\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssDashboard.emit('connection', ws, req);\r\n });\r\n } else {\r\n socket.destroy();\r\n }\r\n });\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.cleanupUploads();\r\n return new Promise((resolve, reject) => {\r\n this.httpServer.on('error', reject);\r\n this.httpServer.listen(this.port, this.host, () => {\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n stop(): Promise<void> {\r\n return new Promise((resolve) => {\r\n // Force-close all WebSocket connections\r\n for (const ws of this.channelSockets.values()) ws.terminate();\r\n for (const ws of this.dashboardSockets) ws.terminate();\r\n this.channelSockets.clear();\r\n this.dashboardSockets.clear();\r\n\r\n this.wssChannel.close();\r\n this.wssDashboard.close();\r\n this.httpServer.close(() => {\r\n logger.info('Hub server stopped');\r\n resolve();\r\n });\r\n\r\n // Force resolve after 3 seconds if server won't close\r\n setTimeout(() => {\r\n logger.warn('Force shutting down');\r\n resolve();\r\n }, 3000);\r\n });\r\n }\r\n\r\n // --- HTTP Handler ---\r\n\r\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n\r\n // CORS headers - restrict to same origin\r\n const origin = req.headers.origin;\r\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\r\n res.setHeader('Access-Control-Allow-Origin', origin);\r\n }\r\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\r\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\r\n\r\n if (req.method === 'OPTIONS') {\r\n res.writeHead(204);\r\n res.end();\r\n return;\r\n }\r\n\r\n // Token auth for API endpoints (skip dashboard HTML serving)\r\n if (url.pathname !== '/' && this.token) {\r\n if (!this.isLocalRequest(req)) {\r\n const authHeader = req.headers['authorization'];\r\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\r\n if (bearerToken !== this.token) {\r\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Route\r\n if (url.pathname === '/' && req.method === 'GET') {\r\n this.serveDashboard(res);\r\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\r\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, {\r\n running: true,\r\n pid: process.pid,\r\n port: this.port,\r\n sessions: this.sessions.count(),\r\n uptime: Date.now() - this.startTime,\r\n });\r\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\r\n this.handleApiSend(req, res);\r\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\r\n this.handleApiNotify(req, res);\r\n } else {\r\n this.jsonResponse(res, 404, { error: 'Not found' });\r\n }\r\n }\r\n\r\n private serveDashboard(res: http.ServerResponse): void {\r\n // Look for dashboard HTML relative to this file (dist) or source\r\n const candidates = [\r\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\r\n path.join(__dirname, 'dashboard', 'index.html'), // from dist/ (bundled index.js)\r\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\r\n path.join(__dirname, '..', 'src', 'dashboard', 'index.html'), // from dist/ -> src/\r\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\r\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\r\n ];\r\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\r\n\r\n for (const candidate of candidates) {\r\n if (fs.existsSync(candidate)) {\r\n const html = fs.readFileSync(candidate, 'utf-8');\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end(html);\r\n return;\r\n }\r\n }\r\n\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\r\n }\r\n\r\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { sessionId, content } = body as { sessionId?: string; content?: string };\r\n if (!sessionId || !content) {\r\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\r\n return;\r\n }\r\n\r\n const ws = this.channelSockets.get(sessionId);\r\n if (!ws || ws.readyState !== WebSocket.OPEN) {\r\n this.jsonResponse(res, 404, { error: 'Session not connected' });\r\n return;\r\n }\r\n\r\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\r\n ws.send(JSON.stringify(msg));\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\r\n if (!title || !message) {\r\n this.jsonResponse(res, 400, { error: 'title and message are required' });\r\n return;\r\n }\r\n\r\n await this.notifier.notify(title, message, (level as any) ?? 'info');\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n // --- Channel WebSocket ---\r\n\r\n private handleChannelConnection(ws: WebSocket, req: http.IncomingMessage): void {\r\n const isLocal = this.isLocalRequest(req);\r\n logger.info(`Channel server connected (local: ${isLocal})`);\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n this.handleChannelMessage(ws, isLocal, msg);\r\n } catch {\r\n logger.warn('Invalid message from channel');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n // Find and remove the session for this socket\r\n for (const [sessionId, sock] of this.channelSockets) {\r\n if (sock === ws) {\r\n const session = this.sessions.unregister(sessionId);\r\n this.channelSockets.delete(sessionId);\r\n this.localChannels.delete(sessionId);\r\n logger.info(`Channel disconnected: ${sessionId}`);\r\n this.broadcastToDashboards({\r\n type: 'session_disconnected',\r\n sessionId,\r\n });\r\n break;\r\n }\r\n }\r\n });\r\n }\r\n\r\n private handleChannelMessage(ws: WebSocket, isLocal: boolean, msg: ChannelMessage): void {\r\n switch (msg.type) {\r\n case 'register': {\r\n const session = msg.session;\r\n session.isLocal = isLocal;\r\n const isReregister = !!this.sessions.get(session.id);\r\n this.sessions.register(session);\r\n this.channelSockets.set(session.id, ws);\r\n if (isLocal) this.localChannels.add(session.id);\r\n logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);\r\n this.broadcastToDashboards({\r\n type: isReregister ? 'session_updated' : 'session_connected',\r\n session,\r\n });\r\n break;\r\n }\r\n\r\n case 'status': {\r\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\r\n if (updated) {\r\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\r\n }\r\n break;\r\n }\r\n\r\n case 'notify': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const notifySession = this.sessions.get(msg.sessionId);\r\n const notifyLabel = this.getSessionLabel(notifySession);\r\n this.notifier.notify(`[${notifyLabel}] ${msg.title}`, msg.message, msg.level ?? 'info');\r\n this.broadcastToDashboards({\r\n type: 'notification',\r\n sessionId: msg.sessionId,\r\n title: msg.title,\r\n message: msg.message,\r\n level: msg.level,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n\r\n case 'reply': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const replySession = this.sessions.get(msg.sessionId);\r\n const replyLabel = this.getSessionLabel(replySession);\r\n this.notifier.notify(`[${replyLabel}] Reply`, msg.content.slice(0, 200), 'info');\r\n this.broadcastToDashboards({\r\n type: 'reply_from_session',\r\n sessionId: msg.sessionId,\r\n content: msg.content,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // --- Dashboard WebSocket ---\r\n\r\n private handleDashboardConnection(ws: WebSocket): void {\r\n this.dashboardSockets.add(ws);\r\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\r\n\r\n // Send current session list\r\n const sessionsMsg: ChannelMessage = {\r\n type: 'sessions_list',\r\n sessions: this.sessions.getAll(),\r\n };\r\n ws.send(JSON.stringify(sessionsMsg));\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n if (msg.type === 'message_to_session') {\r\n const channelWs = this.channelSockets.get(msg.sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n channelWs.send(JSON.stringify(msg));\r\n }\r\n } else if (msg.type === 'image_upload') {\r\n this.handleImageUpload(msg);\r\n }\r\n } catch {\r\n logger.warn('Invalid message from dashboard');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n this.dashboardSockets.delete(ws);\r\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\r\n });\r\n }\r\n\r\n // --- Helpers ---\r\n\r\n private broadcastToDashboards(msg: ChannelMessage): void {\r\n const payload = JSON.stringify(msg);\r\n for (const ws of this.dashboardSockets) {\r\n if (ws.readyState === WebSocket.OPEN) {\r\n ws.send(payload);\r\n }\r\n }\r\n }\r\n\r\n private handleImageUpload(msg: ChannelMessage & { type: 'image_upload' }): void {\r\n const { sessionId, imageData, mimeType, originalName } = msg;\r\n\r\n // Only allow for local sessions\r\n if (!this.localChannels.has(sessionId)) {\r\n logger.warn(`Image upload rejected: session ${sessionId} is not local`);\r\n return;\r\n }\r\n\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (!channelWs || channelWs.readyState !== WebSocket.OPEN) return;\r\n\r\n // Validate mime type\r\n const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];\r\n if (!allowedTypes.includes(mimeType)) return;\r\n\r\n // Extract base64 data (remove data URL prefix if present)\r\n const base64Data = imageData.replace(/^data:image\\/\\w+;base64,/, '');\r\n const buffer = Buffer.from(base64Data, 'base64');\r\n\r\n // Validate size (10MB max)\r\n if (buffer.length > 10 * 1024 * 1024) {\r\n logger.warn('Image upload rejected: exceeds 10MB');\r\n return;\r\n }\r\n\r\n // Save to uploads dir\r\n fs.mkdirSync(UPLOADS_DIR, { recursive: true });\r\n const ext = mimeType.split('/')[1] === 'jpeg' ? 'jpg' : mimeType.split('/')[1];\r\n const filename = `${randomUUID()}.${ext}`;\r\n const filePath = path.join(UPLOADS_DIR, filename);\r\n fs.writeFileSync(filePath, buffer);\r\n\r\n // Forward file path to channel\r\n const forwardMsg: ChannelMessage = {\r\n type: 'image_to_session',\r\n sessionId,\r\n imagePath: filePath,\r\n mimeType,\r\n originalName,\r\n };\r\n channelWs.send(JSON.stringify(forwardMsg));\r\n logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);\r\n\r\n // Cleanup after 5 minutes\r\n setTimeout(() => {\r\n try { fs.unlinkSync(filePath); } catch {}\r\n }, 5 * 60 * 1000);\r\n }\r\n\r\n private cleanupUploads(): void {\r\n try {\r\n if (!fs.existsSync(UPLOADS_DIR)) return;\r\n const files = fs.readdirSync(UPLOADS_DIR);\r\n for (const file of files) {\r\n try { fs.unlinkSync(path.join(UPLOADS_DIR, file)); } catch {}\r\n }\r\n if (files.length > 0) logger.info(`Cleaned up ${files.length} leftover upload(s)`);\r\n } catch {}\r\n }\r\n\r\n private getSessionLabel(session?: SessionInfo): string {\r\n if (!session) return 'unknown';\r\n return session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n }\r\n\r\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' });\r\n res.end(JSON.stringify(body));\r\n }\r\n\r\n private isLocalRequest(req: http.IncomingMessage): boolean {\r\n const addr = req.socket.remoteAddress;\r\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\r\n }\r\n\r\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\r\n return new Promise((resolve) => {\r\n let data = '';\r\n let size = 0;\r\n req.on('data', (chunk) => {\r\n size += chunk.length;\r\n if (size > maxSize) {\r\n req.destroy();\r\n resolve(null);\r\n return;\r\n }\r\n data += chunk;\r\n });\r\n req.on('end', () => {\r\n try {\r\n resolve(JSON.parse(data));\r\n } catch {\r\n resolve(null);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n\r\n// Direct execution support\r\nif (process.argv[1] && (\r\n process.argv[1].endsWith('hub/server.js') ||\r\n process.argv[1].endsWith('hub/server.ts')\r\n)) {\r\n const hub = new HubServer();\r\n hub.start().catch((err) => {\r\n logger.error('Failed to start hub:', err);\r\n process.exit(1);\r\n });\r\n\r\n const shutdown = () => {\r\n hub.stop().then(() => process.exit(0));\r\n };\r\n process.on('SIGINT', shutdown);\r\n process.on('SIGTERM', shutdown);\r\n}\r\n","/**\r\n * Logger that writes to stderr only.\r\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\r\n * Any console.log() would corrupt the protocol. Always use this logger.\r\n */\r\nexport const logger = {\r\n info(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm] ${msg}`, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\r\n },\r\n debug(msg: string, ...args: unknown[]) {\r\n if (process.env.CLAUDE_ALARM_DEBUG) {\r\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n};\r\n","import path from 'node:path';\r\nimport os from 'node:os';\r\n\r\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\r\nexport const DEFAULT_HUB_PORT = 7900;\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\r\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\r\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\r\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\r\nexport const UPLOADS_DIR = path.join(CONFIG_DIR, 'uploads');\r\n\r\nexport const WS_PATH_CHANNEL = '/ws/channel';\r\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\r\n\r\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\r\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\r\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\r\n\r\nexport class SessionManager {\r\n private sessions = new Map<string, SessionInfo>();\r\n\r\n register(session: SessionInfo): void {\r\n this.sessions.set(session.id, { ...session });\r\n }\r\n\r\n unregister(sessionId: string): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n this.sessions.delete(sessionId);\r\n return session;\r\n }\r\n\r\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.status = status;\r\n session.lastActivity = Date.now();\r\n }\r\n return session;\r\n }\r\n\r\n updateActivity(sessionId: string): void {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.lastActivity = Date.now();\r\n }\r\n }\r\n\r\n get(sessionId: string): SessionInfo | undefined {\r\n return this.sessions.get(sessionId);\r\n }\r\n\r\n getAll(): SessionInfo[] {\r\n return Array.from(this.sessions.values());\r\n }\r\n\r\n count(): number {\r\n return this.sessions.size;\r\n }\r\n}\r\n","import notifier from 'node-notifier';\r\nimport { execFile } from 'node:child_process';\r\nimport { logger } from '../shared/logger.js';\r\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\r\n\r\nexport class Notifier {\r\n private webhooks: WebhookConfig[] = [];\r\n private desktopEnabled = true;\r\n private notificationSettingsOpened = false;\r\n private dashboardUrl?: string;\r\n\r\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string }): void {\r\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\r\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\r\n if (options.webhooks) this.webhooks = options.webhooks;\r\n }\r\n\r\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\r\n const promises: Promise<void>[] = [];\r\n\r\n if (this.desktopEnabled) {\r\n promises.push(this.sendDesktop(title, message, level));\r\n }\r\n\r\n for (const webhook of this.webhooks) {\r\n promises.push(this.sendWebhook(webhook, title, message, level));\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n }\r\n\r\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\r\n if (process.platform === 'win32') {\r\n // Check if notifications are enabled by running snoretoast directly\r\n const enabled = await this.checkWindowsNotifications();\r\n if (!enabled) {\r\n this.openNotificationSettings();\r\n return;\r\n }\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const notification = (notifier as any).notify(\r\n {\r\n title: `Claude Alarm: ${title}`,\r\n message,\r\n sound: true,\r\n wait: true,\r\n },\r\n (err: Error | null) => {\r\n if (err) {\r\n logger.warn(`Desktop notification failed: ${err.message}`);\r\n }\r\n resolve();\r\n },\r\n );\r\n\r\n // No click handler - dashboard is already open and notifications\r\n // can be clicked there to focus the relevant session\r\n });\r\n }\r\n\r\n private checkWindowsNotifications(): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n execFile(\r\n 'powershell',\r\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\r\n (err, stdout) => {\r\n if (err) { resolve(true); return; } // assume enabled on error\r\n const value = stdout.trim();\r\n resolve(value !== '0');\r\n },\r\n );\r\n });\r\n }\r\n\r\n private openNotificationSettings(): void {\r\n if (this.notificationSettingsOpened) return;\r\n this.notificationSettingsOpened = true;\r\n\r\n logger.warn('Windows notifications are disabled. Opening notification settings...');\r\n logger.warn('Please enable notifications for this app, then try again.');\r\n\r\n if (process.platform === 'win32') {\r\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\r\n }\r\n\r\n // Allow re-opening after 5 minutes\r\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\r\n }\r\n\r\n private async sendWebhook(\r\n webhook: WebhookConfig,\r\n title: string,\r\n message: string,\r\n level: NotifyLevel,\r\n ): Promise<void> {\r\n try {\r\n const response = await fetch(webhook.url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...webhook.headers,\r\n },\r\n body: JSON.stringify({\r\n title,\r\n message,\r\n level,\r\n timestamp: Date.now(),\r\n source: 'claude-alarm',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\r\n }\r\n } catch (err) {\r\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\r\n }\r\n }\r\n}\r\n","import WebSocket from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\r\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\r\n\r\nexport class HubClient {\r\n private ws: WebSocket | null = null;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\r\n private queue: ChannelMessage[] = [];\r\n private connected = false;\r\n\r\n constructor(\r\n private sessionId: string,\r\n private sessionName: string,\r\n private hubHost = DEFAULT_HUB_HOST,\r\n private hubPort = DEFAULT_HUB_PORT,\r\n private token?: string,\r\n ) {}\r\n\r\n connect(): void {\r\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\r\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\r\n logger.debug(`Connecting to hub at ${url}`);\r\n\r\n try {\r\n this.ws = new WebSocket(url);\r\n\r\n this.ws.on('open', () => {\r\n logger.info('Connected to hub');\r\n this.connected = true;\r\n\r\n // Register this session\r\n const registration: ChannelMessage = {\r\n type: 'register',\r\n session: {\r\n id: this.sessionId,\r\n name: this.sessionName,\r\n status: 'idle',\r\n connectedAt: Date.now(),\r\n lastActivity: Date.now(),\r\n cwd: process.cwd(),\r\n channelEnabled: true,\r\n },\r\n };\r\n this.ws!.send(JSON.stringify(registration));\r\n\r\n // Flush queued messages\r\n for (const msg of this.queue) {\r\n this.ws!.send(JSON.stringify(msg));\r\n }\r\n this.queue = [];\r\n });\r\n\r\n this.ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n for (const handler of this.messageHandlers) {\r\n handler(msg);\r\n }\r\n } catch (err) {\r\n logger.warn('Failed to parse hub message:', err);\r\n }\r\n });\r\n\r\n this.ws.on('close', () => {\r\n logger.info('Disconnected from hub');\r\n this.connected = false;\r\n this.scheduleReconnect();\r\n });\r\n\r\n this.ws.on('error', (err) => {\r\n logger.debug(`Hub connection error: ${err.message}`);\r\n this.connected = false;\r\n });\r\n } catch {\r\n logger.debug('Failed to connect to hub, will retry');\r\n this.scheduleReconnect();\r\n }\r\n }\r\n\r\n send(msg: ChannelMessage): void {\r\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify(msg));\r\n } else {\r\n if (this.queue.length < 100) {\r\n this.queue.push(msg);\r\n }\r\n logger.debug('Hub not connected, message queued');\r\n }\r\n }\r\n\r\n onMessage(handler: (msg: ChannelMessage) => void): void {\r\n this.messageHandlers.push(handler);\r\n }\r\n\r\n disconnect(): void {\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n this.connected = false;\r\n }\r\n\r\n private scheduleReconnect(): void {\r\n if (this.reconnectTimer) return;\r\n this.reconnectTimer = setTimeout(() => {\r\n this.reconnectTimer = null;\r\n this.connect();\r\n }, 5000);\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\r\nimport type { AppConfig } from './types.js';\r\n\r\nconst DEFAULT_CONFIG: AppConfig = {\r\n hub: {\r\n host: DEFAULT_HUB_HOST,\r\n port: DEFAULT_HUB_PORT,\r\n },\r\n notifications: {\r\n desktop: true,\r\n sound: true,\r\n },\r\n webhooks: [],\r\n};\r\n\r\nexport function ensureConfigDir(): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nexport function loadConfig(): AppConfig {\r\n ensureConfigDir();\r\n let config: AppConfig;\r\n if (!fs.existsSync(CONFIG_FILE)) {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n } else {\r\n try {\r\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\r\n const parsed = JSON.parse(raw);\r\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };\r\n } catch {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n }\r\n }\r\n\r\n // Auto-generate token if missing\r\n if (!config.hub.token) {\r\n config.hub.token = randomUUID();\r\n saveConfig(config);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n/** Get the current token, generating one if needed */\r\nexport function getOrCreateToken(): string {\r\n const config = loadConfig();\r\n return config.hub.token!;\r\n}\r\n\r\nexport function saveConfig(config: AppConfig): void {\r\n ensureConfigDir();\r\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\r\n}\r\n\r\n/**\r\n * Add claude-alarm as an MCP channel server to .mcp.json\r\n */\r\nexport function setupMcpConfig(targetDir?: string): string {\r\n const dir = targetDir ?? process.cwd();\r\n const mcpPath = path.join(dir, '.mcp.json');\r\n\r\n let mcpConfig: Record<string, any> = {};\r\n if (fs.existsSync(mcpPath)) {\r\n try {\r\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\r\n } catch {\r\n mcpConfig = {};\r\n }\r\n }\r\n\r\n if (!mcpConfig.mcpServers) {\r\n mcpConfig.mcpServers = {};\r\n }\r\n\r\n mcpConfig.mcpServers['claude-alarm'] = {\r\n command: 'npx',\r\n args: ['-y', '@delt/claude-alarm', 'serve'],\r\n env: {\r\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\r\n },\r\n };\r\n\r\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\r\n return mcpPath;\r\n}\r\n"],"mappings":"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","names":["path","path","WebSocket","WebSocket","fs","path","randomUUID","fs","randomUUID","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/shared/config.ts","../src/channel/hub-client.ts"],"sourcesContent":["import http from 'node:http';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { WebSocketServer, WebSocket } from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { randomUUID } from 'node:crypto';\r\nimport {\r\n DEFAULT_HUB_HOST,\r\n DEFAULT_HUB_PORT,\r\n WS_PATH_CHANNEL,\r\n WS_PATH_DASHBOARD,\r\n UPLOADS_DIR,\r\n} from '../shared/constants.js';\r\nimport { SessionManager } from './session-manager.js';\r\nimport { Notifier } from './notifier.js';\r\nimport { loadConfig, saveConfig } from '../shared/config.js';\r\nimport type { ChannelMessage, AppConfig, SessionInfo, WebhookConfig } from '../shared/types.js';\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\r\n\r\nexport class HubServer {\r\n private httpServer: http.Server;\r\n private wssChannel: WebSocketServer;\r\n private wssDashboard: WebSocketServer;\r\n private sessions = new SessionManager();\r\n private notifier = new Notifier();\r\n private startTime = Date.now();\r\n\r\n // Map sessionId -> channel WebSocket\r\n private channelSockets = new Map<string, WebSocket>();\r\n // Track which channel connections are local\r\n private localChannels = new Set<string>();\r\n // All connected dashboard WebSockets\r\n private dashboardSockets = new Set<WebSocket>();\r\n\r\n private host: string;\r\n private port: number;\r\n private token?: string;\r\n\r\n constructor(config?: Partial<AppConfig>) {\r\n this.host = config?.hub?.host ?? DEFAULT_HUB_HOST;\r\n this.port = config?.hub?.port ?? DEFAULT_HUB_PORT;\r\n this.token = config?.hub?.token;\r\n\r\n if (config?.notifications) {\r\n this.notifier.configure({\r\n desktop: config.notifications.desktop,\r\n });\r\n }\r\n if (config?.webhooks) {\r\n this.notifier.configure({ webhooks: config.webhooks });\r\n }\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });\r\n\r\n // HTTP Server\r\n this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));\r\n\r\n // WebSocket for channel servers\r\n this.wssChannel = new WebSocketServer({ noServer: true });\r\n this.wssChannel.on('connection', (ws: WebSocket, req: http.IncomingMessage) => this.handleChannelConnection(ws, req));\r\n\r\n // WebSocket for dashboard\r\n this.wssDashboard = new WebSocketServer({ noServer: true });\r\n this.wssDashboard.on('connection', (ws) => this.handleDashboardConnection(ws));\r\n\r\n // Route WebSocket upgrade requests\r\n this.httpServer.on('upgrade', (req, socket, head) => {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n const pathname = url.pathname;\r\n\r\n // Token auth for WebSocket connections (skip for local requests)\r\n if (this.token && !this.isLocalRequest(req)) {\r\n const wsToken = url.searchParams.get('token');\r\n if (wsToken !== this.token) {\r\n socket.destroy();\r\n return;\r\n }\r\n }\r\n\r\n if (pathname === WS_PATH_CHANNEL) {\r\n this.wssChannel.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssChannel.emit('connection', ws, req);\r\n });\r\n } else if (pathname === WS_PATH_DASHBOARD) {\r\n this.wssDashboard.handleUpgrade(req, socket, head, (ws) => {\r\n this.wssDashboard.emit('connection', ws, req);\r\n });\r\n } else {\r\n socket.destroy();\r\n }\r\n });\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.cleanupUploads();\r\n return new Promise((resolve, reject) => {\r\n this.httpServer.on('error', reject);\r\n this.httpServer.listen(this.port, this.host, () => {\r\n const displayHost = this.host === '0.0.0.0' ? '127.0.0.1' : this.host;\r\n logger.info(`Hub server listening on http://${displayHost}:${this.port}`);\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n stop(): Promise<void> {\r\n return new Promise((resolve) => {\r\n // Force-close all WebSocket connections\r\n for (const ws of this.channelSockets.values()) ws.terminate();\r\n for (const ws of this.dashboardSockets) ws.terminate();\r\n this.channelSockets.clear();\r\n this.dashboardSockets.clear();\r\n\r\n this.wssChannel.close();\r\n this.wssDashboard.close();\r\n this.httpServer.close(() => {\r\n logger.info('Hub server stopped');\r\n resolve();\r\n });\r\n\r\n // Force resolve after 3 seconds if server won't close\r\n setTimeout(() => {\r\n logger.warn('Force shutting down');\r\n resolve();\r\n }, 3000);\r\n });\r\n }\r\n\r\n // --- HTTP Handler ---\r\n\r\n private handleHttp(req: http.IncomingMessage, res: http.ServerResponse): void {\r\n const url = new URL(req.url!, `http://${req.headers.host}`);\r\n\r\n // CORS headers - restrict to same origin\r\n const origin = req.headers.origin;\r\n if (origin && (origin.includes('127.0.0.1') || origin.includes('localhost'))) {\r\n res.setHeader('Access-Control-Allow-Origin', origin);\r\n }\r\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\r\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\r\n\r\n if (req.method === 'OPTIONS') {\r\n res.writeHead(204);\r\n res.end();\r\n return;\r\n }\r\n\r\n // Token auth for API endpoints (skip dashboard HTML serving)\r\n if (url.pathname !== '/' && this.token) {\r\n if (!this.isLocalRequest(req)) {\r\n const authHeader = req.headers['authorization'];\r\n const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\r\n if (bearerToken !== this.token) {\r\n this.jsonResponse(res, 401, { error: 'Unauthorized' });\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Route\r\n if (url.pathname === '/' && req.method === 'GET') {\r\n this.serveDashboard(res);\r\n } else if (url.pathname === '/api/sessions' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });\r\n } else if (url.pathname === '/api/status' && req.method === 'GET') {\r\n this.jsonResponse(res, 200, {\r\n running: true,\r\n pid: process.pid,\r\n port: this.port,\r\n sessions: this.sessions.count(),\r\n uptime: Date.now() - this.startTime,\r\n });\r\n } else if (url.pathname === '/api/send' && req.method === 'POST') {\r\n this.handleApiSend(req, res);\r\n } else if (url.pathname === '/api/notify' && req.method === 'POST') {\r\n this.handleApiNotify(req, res);\r\n } else if (url.pathname === '/api/webhooks' && req.method === 'GET') {\r\n const config = loadConfig();\r\n this.jsonResponse(res, 200, { webhooks: config.webhooks || [] });\r\n } else if (url.pathname === '/api/webhooks' && req.method === 'POST') {\r\n this.handleWebhookSave(req, res);\r\n } else {\r\n this.jsonResponse(res, 404, { error: 'Not found' });\r\n }\r\n }\r\n\r\n private serveDashboard(res: http.ServerResponse): void {\r\n // Look for dashboard HTML relative to this file (dist) or source\r\n const candidates = [\r\n path.join(__dirname, '..', 'dashboard', 'index.html'), // from dist/hub/\r\n path.join(__dirname, 'dashboard', 'index.html'), // from dist/ (bundled index.js)\r\n path.join(__dirname, '..', '..', 'src', 'dashboard', 'index.html'), // from dist/hub/ -> src/\r\n path.join(__dirname, '..', 'src', 'dashboard', 'index.html'), // from dist/ -> src/\r\n path.join(process.cwd(), 'dist', 'dashboard', 'index.html'), // from cwd\r\n path.join(process.cwd(), 'src', 'dashboard', 'index.html'), // from cwd/src\r\n ];\r\n logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);\r\n\r\n for (const candidate of candidates) {\r\n if (fs.existsSync(candidate)) {\r\n const html = fs.readFileSync(candidate, 'utf-8');\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end(html);\r\n return;\r\n }\r\n }\r\n\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\r\n res.end('<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>');\r\n }\r\n\r\n private async handleApiSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { sessionId, content } = body as { sessionId?: string; content?: string };\r\n if (!sessionId || !content) {\r\n this.jsonResponse(res, 400, { error: 'sessionId and content are required' });\r\n return;\r\n }\r\n\r\n const ws = this.channelSockets.get(sessionId);\r\n if (!ws || ws.readyState !== WebSocket.OPEN) {\r\n this.jsonResponse(res, 404, { error: 'Session not connected' });\r\n return;\r\n }\r\n\r\n const msg: ChannelMessage = { type: 'message_to_session', sessionId, content };\r\n ws.send(JSON.stringify(msg));\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private async handleApiNotify(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n\r\n const { title, message, level } = body as { title?: string; message?: string; level?: string };\r\n if (!title || !message) {\r\n this.jsonResponse(res, 400, { error: 'title and message are required' });\r\n return;\r\n }\r\n\r\n await this.notifier.notify(title, message, (level as any) ?? 'info');\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n // --- Channel WebSocket ---\r\n\r\n private handleChannelConnection(ws: WebSocket, req: http.IncomingMessage): void {\r\n const isLocal = this.isLocalRequest(req);\r\n logger.info(`Channel server connected (local: ${isLocal})`);\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n this.handleChannelMessage(ws, isLocal, msg);\r\n } catch {\r\n logger.warn('Invalid message from channel');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n // Find and remove the session for this socket\r\n for (const [sessionId, sock] of this.channelSockets) {\r\n if (sock === ws) {\r\n const session = this.sessions.unregister(sessionId);\r\n this.channelSockets.delete(sessionId);\r\n this.localChannels.delete(sessionId);\r\n logger.info(`Channel disconnected: ${sessionId}`);\r\n this.broadcastToDashboards({\r\n type: 'session_disconnected',\r\n sessionId,\r\n });\r\n break;\r\n }\r\n }\r\n });\r\n }\r\n\r\n private handleChannelMessage(ws: WebSocket, isLocal: boolean, msg: ChannelMessage): void {\r\n switch (msg.type) {\r\n case 'register': {\r\n const session = msg.session;\r\n session.isLocal = isLocal;\r\n const isReregister = !!this.sessions.get(session.id);\r\n this.sessions.register(session);\r\n this.channelSockets.set(session.id, ws);\r\n if (isLocal) this.localChannels.add(session.id);\r\n logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);\r\n this.broadcastToDashboards({\r\n type: isReregister ? 'session_updated' : 'session_connected',\r\n session,\r\n });\r\n break;\r\n }\r\n\r\n case 'status': {\r\n const updated = this.sessions.updateStatus(msg.sessionId, msg.status);\r\n if (updated) {\r\n this.broadcastToDashboards({ type: 'session_updated', session: updated });\r\n }\r\n break;\r\n }\r\n\r\n case 'notify': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const notifySession = this.sessions.get(msg.sessionId);\r\n const notifyLabel = this.getSessionLabel(notifySession);\r\n this.notifier.notify(`[${notifyLabel}] ${msg.title}`, msg.message, msg.level ?? 'info');\r\n this.broadcastToDashboards({\r\n type: 'notification',\r\n sessionId: msg.sessionId,\r\n title: msg.title,\r\n message: msg.message,\r\n level: msg.level,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n\r\n case 'reply': {\r\n this.sessions.updateActivity(msg.sessionId);\r\n const replySession = this.sessions.get(msg.sessionId);\r\n const replyLabel = this.getSessionLabel(replySession);\r\n this.notifier.notify(`[${replyLabel}] Reply`, msg.content.slice(0, 200), 'info');\r\n this.broadcastToDashboards({\r\n type: 'reply_from_session',\r\n sessionId: msg.sessionId,\r\n content: msg.content,\r\n timestamp: Date.now(),\r\n });\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // --- Dashboard WebSocket ---\r\n\r\n private handleDashboardConnection(ws: WebSocket): void {\r\n this.dashboardSockets.add(ws);\r\n logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);\r\n\r\n // Send current session list\r\n const sessionsMsg: ChannelMessage = {\r\n type: 'sessions_list',\r\n sessions: this.sessions.getAll(),\r\n };\r\n ws.send(JSON.stringify(sessionsMsg));\r\n\r\n ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n if (msg.type === 'message_to_session') {\r\n const channelWs = this.channelSockets.get(msg.sessionId);\r\n if (channelWs?.readyState === WebSocket.OPEN) {\r\n channelWs.send(JSON.stringify(msg));\r\n }\r\n } else if (msg.type === 'image_upload') {\r\n this.handleImageUpload(msg);\r\n }\r\n } catch {\r\n logger.warn('Invalid message from dashboard');\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n this.dashboardSockets.delete(ws);\r\n logger.info(`Dashboard disconnected (total: ${this.dashboardSockets.size})`);\r\n });\r\n }\r\n\r\n // --- Helpers ---\r\n\r\n private broadcastToDashboards(msg: ChannelMessage): void {\r\n const payload = JSON.stringify(msg);\r\n for (const ws of this.dashboardSockets) {\r\n if (ws.readyState === WebSocket.OPEN) {\r\n ws.send(payload);\r\n }\r\n }\r\n }\r\n\r\n private handleImageUpload(msg: ChannelMessage & { type: 'image_upload' }): void {\r\n const { sessionId, imageData, mimeType, originalName } = msg;\r\n\r\n // Only allow for local sessions\r\n if (!this.localChannels.has(sessionId)) {\r\n logger.warn(`Image upload rejected: session ${sessionId} is not local`);\r\n return;\r\n }\r\n\r\n const channelWs = this.channelSockets.get(sessionId);\r\n if (!channelWs || channelWs.readyState !== WebSocket.OPEN) return;\r\n\r\n // Validate mime type\r\n const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];\r\n if (!allowedTypes.includes(mimeType)) return;\r\n\r\n // Extract base64 data (remove data URL prefix if present)\r\n const base64Data = imageData.replace(/^data:image\\/\\w+;base64,/, '');\r\n const buffer = Buffer.from(base64Data, 'base64');\r\n\r\n // Validate size (10MB max)\r\n if (buffer.length > 10 * 1024 * 1024) {\r\n logger.warn('Image upload rejected: exceeds 10MB');\r\n return;\r\n }\r\n\r\n // Save to uploads dir\r\n fs.mkdirSync(UPLOADS_DIR, { recursive: true });\r\n const ext = mimeType.split('/')[1] === 'jpeg' ? 'jpg' : mimeType.split('/')[1];\r\n const filename = `${randomUUID()}.${ext}`;\r\n const filePath = path.join(UPLOADS_DIR, filename);\r\n fs.writeFileSync(filePath, buffer);\r\n\r\n // Forward file path to channel\r\n const forwardMsg: ChannelMessage = {\r\n type: 'image_to_session',\r\n sessionId,\r\n imagePath: filePath,\r\n mimeType,\r\n originalName,\r\n };\r\n channelWs.send(JSON.stringify(forwardMsg));\r\n logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);\r\n\r\n // Cleanup after 5 minutes\r\n setTimeout(() => {\r\n try { fs.unlinkSync(filePath); } catch {}\r\n }, 5 * 60 * 1000);\r\n }\r\n\r\n private async handleWebhookSave(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\r\n const body = await this.readBody(req);\r\n if (!body) { this.jsonResponse(res, 400, { error: 'Invalid JSON' }); return; }\r\n const { webhooks } = body as { webhooks?: WebhookConfig[] };\r\n if (!Array.isArray(webhooks)) { this.jsonResponse(res, 400, { error: 'webhooks must be an array' }); return; }\r\n const config = loadConfig();\r\n config.webhooks = webhooks;\r\n saveConfig(config);\r\n this.notifier.configure({ webhooks });\r\n this.jsonResponse(res, 200, { ok: true });\r\n }\r\n\r\n private cleanupUploads(): void {\r\n try {\r\n if (!fs.existsSync(UPLOADS_DIR)) return;\r\n const files = fs.readdirSync(UPLOADS_DIR);\r\n for (const file of files) {\r\n try { fs.unlinkSync(path.join(UPLOADS_DIR, file)); } catch {}\r\n }\r\n if (files.length > 0) logger.info(`Cleaned up ${files.length} leftover upload(s)`);\r\n } catch {}\r\n }\r\n\r\n private getSessionLabel(session?: SessionInfo): string {\r\n if (!session) return 'unknown';\r\n return session.cwd?.replace(/^.*[/\\\\]/, '') || session.name;\r\n }\r\n\r\n private jsonResponse(res: http.ServerResponse, status: number, body: unknown): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' });\r\n res.end(JSON.stringify(body));\r\n }\r\n\r\n private isLocalRequest(req: http.IncomingMessage): boolean {\r\n const addr = req.socket.remoteAddress;\r\n return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';\r\n }\r\n\r\n private readBody(req: http.IncomingMessage, maxSize = 1024 * 1024): Promise<unknown | null> {\r\n return new Promise((resolve) => {\r\n let data = '';\r\n let size = 0;\r\n req.on('data', (chunk) => {\r\n size += chunk.length;\r\n if (size > maxSize) {\r\n req.destroy();\r\n resolve(null);\r\n return;\r\n }\r\n data += chunk;\r\n });\r\n req.on('end', () => {\r\n try {\r\n resolve(JSON.parse(data));\r\n } catch {\r\n resolve(null);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n\r\n// Direct execution support\r\nif (process.argv[1] && (\r\n process.argv[1].endsWith('hub/server.js') ||\r\n process.argv[1].endsWith('hub/server.ts')\r\n)) {\r\n const hub = new HubServer();\r\n hub.start().catch((err) => {\r\n logger.error('Failed to start hub:', err);\r\n process.exit(1);\r\n });\r\n\r\n const shutdown = () => {\r\n hub.stop().then(() => process.exit(0));\r\n };\r\n process.on('SIGINT', shutdown);\r\n process.on('SIGTERM', shutdown);\r\n}\r\n","/**\r\n * Logger that writes to stderr only.\r\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\r\n * Any console.log() would corrupt the protocol. Always use this logger.\r\n */\r\nexport const logger = {\r\n info(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm] ${msg}`, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\r\n },\r\n debug(msg: string, ...args: unknown[]) {\r\n if (process.env.CLAUDE_ALARM_DEBUG) {\r\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n};\r\n","import path from 'node:path';\r\nimport os from 'node:os';\r\n\r\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\r\nexport const DEFAULT_HUB_PORT = 7900;\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\r\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\r\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\r\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\r\nexport const UPLOADS_DIR = path.join(CONFIG_DIR, 'uploads');\r\n\r\nexport const WS_PATH_CHANNEL = '/ws/channel';\r\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\r\n\r\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\r\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\r\n","import type { SessionInfo, SessionStatus } from '../shared/types.js';\r\n\r\nexport class SessionManager {\r\n private sessions = new Map<string, SessionInfo>();\r\n\r\n register(session: SessionInfo): void {\r\n this.sessions.set(session.id, { ...session });\r\n }\r\n\r\n unregister(sessionId: string): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n this.sessions.delete(sessionId);\r\n return session;\r\n }\r\n\r\n updateStatus(sessionId: string, status: SessionStatus): SessionInfo | undefined {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.status = status;\r\n session.lastActivity = Date.now();\r\n }\r\n return session;\r\n }\r\n\r\n updateActivity(sessionId: string): void {\r\n const session = this.sessions.get(sessionId);\r\n if (session) {\r\n session.lastActivity = Date.now();\r\n }\r\n }\r\n\r\n get(sessionId: string): SessionInfo | undefined {\r\n return this.sessions.get(sessionId);\r\n }\r\n\r\n getAll(): SessionInfo[] {\r\n return Array.from(this.sessions.values());\r\n }\r\n\r\n count(): number {\r\n return this.sessions.size;\r\n }\r\n}\r\n","import notifier from 'node-notifier';\r\nimport { execFile } from 'node:child_process';\r\nimport { logger } from '../shared/logger.js';\r\nimport type { NotifyLevel, WebhookConfig } from '../shared/types.js';\r\n\r\nexport class Notifier {\r\n private webhooks: WebhookConfig[] = [];\r\n private desktopEnabled = true;\r\n private notificationSettingsOpened = false;\r\n private dashboardUrl?: string;\r\n\r\n configure(options: { desktop?: boolean; webhooks?: WebhookConfig[]; dashboardUrl?: string }): void {\r\n if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;\r\n if (options.desktop !== undefined) this.desktopEnabled = options.desktop;\r\n if (options.webhooks) this.webhooks = options.webhooks;\r\n }\r\n\r\n async notify(title: string, message: string, level: NotifyLevel = 'info'): Promise<void> {\r\n const promises: Promise<void>[] = [];\r\n\r\n if (this.desktopEnabled) {\r\n promises.push(this.sendDesktop(title, message, level));\r\n }\r\n\r\n for (const webhook of this.webhooks) {\r\n promises.push(this.sendWebhook(webhook, title, message, level));\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n }\r\n\r\n private async sendDesktop(title: string, message: string, _level: NotifyLevel): Promise<void> {\r\n if (process.platform === 'win32') {\r\n // Check if notifications are enabled by running snoretoast directly\r\n const enabled = await this.checkWindowsNotifications();\r\n if (!enabled) {\r\n this.openNotificationSettings();\r\n return;\r\n }\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const notification = (notifier as any).notify(\r\n {\r\n title: `Claude Alarm: ${title}`,\r\n message,\r\n sound: true,\r\n wait: true,\r\n },\r\n (err: Error | null) => {\r\n if (err) {\r\n logger.warn(`Desktop notification failed: ${err.message}`);\r\n }\r\n resolve();\r\n },\r\n );\r\n\r\n // No click handler - dashboard is already open and notifications\r\n // can be clicked there to focus the relevant session\r\n });\r\n }\r\n\r\n private checkWindowsNotifications(): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n execFile(\r\n 'powershell',\r\n ['-Command', '(Get-ItemProperty -Path \"HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\PushNotifications\" -Name ToastEnabled -ErrorAction SilentlyContinue).ToastEnabled'],\r\n (err, stdout) => {\r\n if (err) { resolve(true); return; } // assume enabled on error\r\n const value = stdout.trim();\r\n resolve(value !== '0');\r\n },\r\n );\r\n });\r\n }\r\n\r\n private openNotificationSettings(): void {\r\n if (this.notificationSettingsOpened) return;\r\n this.notificationSettingsOpened = true;\r\n\r\n logger.warn('Windows notifications are disabled. Opening notification settings...');\r\n logger.warn('Please enable notifications for this app, then try again.');\r\n\r\n if (process.platform === 'win32') {\r\n execFile('powershell', ['-Command', 'Start-Process ms-settings:notifications']);\r\n }\r\n\r\n // Allow re-opening after 5 minutes\r\n setTimeout(() => { this.notificationSettingsOpened = false; }, 5 * 60 * 1000);\r\n }\r\n\r\n private async sendWebhook(\r\n webhook: WebhookConfig,\r\n title: string,\r\n message: string,\r\n level: NotifyLevel,\r\n ): Promise<void> {\r\n try {\r\n const response = await fetch(webhook.url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...webhook.headers,\r\n },\r\n body: JSON.stringify({\r\n title,\r\n message,\r\n level,\r\n timestamp: Date.now(),\r\n source: 'claude-alarm',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n logger.warn(`Webhook ${webhook.url} returned ${response.status}`);\r\n }\r\n } catch (err) {\r\n logger.warn(`Webhook ${webhook.url} failed: ${(err as Error).message}`);\r\n }\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\r\nimport type { AppConfig } from './types.js';\r\n\r\nconst DEFAULT_CONFIG: AppConfig = {\r\n hub: {\r\n host: DEFAULT_HUB_HOST,\r\n port: DEFAULT_HUB_PORT,\r\n },\r\n notifications: {\r\n desktop: true,\r\n sound: true,\r\n },\r\n webhooks: [],\r\n};\r\n\r\nexport function ensureConfigDir(): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nexport function loadConfig(): AppConfig {\r\n ensureConfigDir();\r\n let config: AppConfig;\r\n if (!fs.existsSync(CONFIG_FILE)) {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n } else {\r\n try {\r\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\r\n const parsed = JSON.parse(raw);\r\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };\r\n } catch {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n }\r\n }\r\n\r\n // Auto-generate token if missing\r\n if (!config.hub.token) {\r\n config.hub.token = randomUUID();\r\n saveConfig(config);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n/** Get the current token, generating one if needed */\r\nexport function getOrCreateToken(): string {\r\n const config = loadConfig();\r\n return config.hub.token!;\r\n}\r\n\r\nexport function saveConfig(config: AppConfig): void {\r\n ensureConfigDir();\r\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\r\n}\r\n\r\n/**\r\n * Add claude-alarm as an MCP channel server to .mcp.json\r\n */\r\nexport function setupMcpConfig(targetDir?: string): string {\r\n const dir = targetDir ?? process.cwd();\r\n const mcpPath = path.join(dir, '.mcp.json');\r\n\r\n let mcpConfig: Record<string, any> = {};\r\n if (fs.existsSync(mcpPath)) {\r\n try {\r\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\r\n } catch {\r\n mcpConfig = {};\r\n }\r\n }\r\n\r\n if (!mcpConfig.mcpServers) {\r\n mcpConfig.mcpServers = {};\r\n }\r\n\r\n mcpConfig.mcpServers['claude-alarm'] = {\r\n command: 'npx',\r\n args: ['-y', '@delt/claude-alarm', 'serve'],\r\n env: {\r\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\r\n },\r\n };\r\n\r\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\r\n return mcpPath;\r\n}\r\n","import WebSocket from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\r\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\r\n\r\nexport class HubClient {\r\n private ws: WebSocket | null = null;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\r\n private queue: ChannelMessage[] = [];\r\n private connected = false;\r\n\r\n constructor(\r\n private sessionId: string,\r\n private sessionName: string,\r\n private hubHost = DEFAULT_HUB_HOST,\r\n private hubPort = DEFAULT_HUB_PORT,\r\n private token?: string,\r\n ) {}\r\n\r\n connect(): void {\r\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\r\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\r\n logger.debug(`Connecting to hub at ${url}`);\r\n\r\n try {\r\n this.ws = new WebSocket(url);\r\n\r\n this.ws.on('open', () => {\r\n logger.info('Connected to hub');\r\n this.connected = true;\r\n\r\n // Register this session\r\n const registration: ChannelMessage = {\r\n type: 'register',\r\n session: {\r\n id: this.sessionId,\r\n name: this.sessionName,\r\n status: 'idle',\r\n connectedAt: Date.now(),\r\n lastActivity: Date.now(),\r\n cwd: process.cwd(),\r\n channelEnabled: true,\r\n },\r\n };\r\n this.ws!.send(JSON.stringify(registration));\r\n\r\n // Flush queued messages\r\n for (const msg of this.queue) {\r\n this.ws!.send(JSON.stringify(msg));\r\n }\r\n this.queue = [];\r\n });\r\n\r\n this.ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n for (const handler of this.messageHandlers) {\r\n handler(msg);\r\n }\r\n } catch (err) {\r\n logger.warn('Failed to parse hub message:', err);\r\n }\r\n });\r\n\r\n this.ws.on('close', () => {\r\n logger.info('Disconnected from hub');\r\n this.connected = false;\r\n this.scheduleReconnect();\r\n });\r\n\r\n this.ws.on('error', (err) => {\r\n logger.debug(`Hub connection error: ${err.message}`);\r\n this.connected = false;\r\n });\r\n } catch {\r\n logger.debug('Failed to connect to hub, will retry');\r\n this.scheduleReconnect();\r\n }\r\n }\r\n\r\n send(msg: ChannelMessage): void {\r\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify(msg));\r\n } else {\r\n if (this.queue.length < 100) {\r\n this.queue.push(msg);\r\n }\r\n logger.debug('Hub not connected, message queued');\r\n }\r\n }\r\n\r\n onMessage(handler: (msg: ChannelMessage) => void): void {\r\n this.messageHandlers.push(handler);\r\n }\r\n\r\n disconnect(): void {\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n this.connected = false;\r\n }\r\n\r\n private scheduleReconnect(): void {\r\n if (this.reconnectTimer) return;\r\n this.reconnectTimer = setTimeout(() => {\r\n this.reconnectTimer = null;\r\n this.connect();\r\n }, 5000);\r\n }\r\n}\r\n"],"mappings":"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","names":["fs","path","randomUUID","path","path","path","fs","randomUUID","WebSocket","WebSocket"]}
|