@delt/claude-alarm 0.5.2 → 0.5.4
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 +189 -69
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +136 -20
- package/dist/hub/server.js +170 -51
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +175 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +136 -20
package/dist/cli.js
CHANGED
|
@@ -293,11 +293,15 @@ var init_notifier = __esm({
|
|
|
293
293
|
});
|
|
294
294
|
|
|
295
295
|
// src/hub/telegram.ts
|
|
296
|
+
import fs2 from "fs";
|
|
297
|
+
import path3 from "path";
|
|
298
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
296
299
|
var TELEGRAM_API, TelegramBot;
|
|
297
300
|
var init_telegram = __esm({
|
|
298
301
|
"src/hub/telegram.ts"() {
|
|
299
302
|
"use strict";
|
|
300
303
|
init_logger();
|
|
304
|
+
init_constants();
|
|
301
305
|
TELEGRAM_API = "https://api.telegram.org/bot";
|
|
302
306
|
TelegramBot = class {
|
|
303
307
|
config;
|
|
@@ -306,13 +310,15 @@ var init_telegram = __esm({
|
|
|
306
310
|
pollTimer = null;
|
|
307
311
|
// message_id -> sessionId mapping for reply-based routing
|
|
308
312
|
messageSessionMap = /* @__PURE__ */ new Map();
|
|
309
|
-
// Callback: when a message arrives from Telegram for a session
|
|
313
|
+
// Callback: when a text message arrives from Telegram for a session
|
|
310
314
|
onMessageToSession;
|
|
315
|
+
// Callback: when an image arrives from Telegram for a session
|
|
316
|
+
onImageToSession;
|
|
311
317
|
// Callback: get current sessions list
|
|
312
318
|
getSessions;
|
|
313
|
-
//
|
|
319
|
+
// Pending messages for session selection
|
|
314
320
|
pendingMessages = /* @__PURE__ */ new Map();
|
|
315
|
-
// chatId -> pending
|
|
321
|
+
// chatId -> pending
|
|
316
322
|
constructor(config2) {
|
|
317
323
|
this.config = config2;
|
|
318
324
|
}
|
|
@@ -408,31 +414,42 @@ ${message}`;
|
|
|
408
414
|
if (!this.polling) return;
|
|
409
415
|
this.pollTimer = setTimeout(() => this.poll(), delay);
|
|
410
416
|
}
|
|
411
|
-
handleIncomingMessage(msg) {
|
|
412
|
-
if (!msg.text) return;
|
|
417
|
+
async handleIncomingMessage(msg) {
|
|
413
418
|
if (String(msg.chat.id) !== String(this.config.chatId)) return;
|
|
414
|
-
const
|
|
419
|
+
const hasPhoto = msg.photo && msg.photo.length > 0;
|
|
420
|
+
const text = (msg.text || msg.caption || "").trim();
|
|
421
|
+
if (!text && !hasPhoto) return;
|
|
415
422
|
if (msg.reply_to_message) {
|
|
416
423
|
const sessionId2 = this.messageSessionMap.get(msg.reply_to_message.message_id);
|
|
417
424
|
if (sessionId2) {
|
|
418
|
-
|
|
425
|
+
if (hasPhoto) {
|
|
426
|
+
await this.deliverPhotoToSession(sessionId2, msg.photo, text);
|
|
427
|
+
} else {
|
|
428
|
+
this.deliverToSession(sessionId2, text);
|
|
429
|
+
}
|
|
419
430
|
return;
|
|
420
431
|
}
|
|
421
432
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
433
|
+
if (text) {
|
|
434
|
+
const selectMatch = text.match(/^\/s_(\d+)$/);
|
|
435
|
+
if (selectMatch) {
|
|
436
|
+
const pending = this.pendingMessages.get(msg.chat.id);
|
|
437
|
+
if (pending) {
|
|
438
|
+
this.pendingMessages.delete(msg.chat.id);
|
|
439
|
+
const sessions2 = this.getSessions?.() ?? [];
|
|
440
|
+
const idx = parseInt(selectMatch[1], 10) - 1;
|
|
441
|
+
if (idx >= 0 && idx < sessions2.length) {
|
|
442
|
+
if (pending.photoFileId) {
|
|
443
|
+
await this.deliverPhotoToSessionByFileId(sessions2[idx].id, pending.photoFileId, pending.caption);
|
|
444
|
+
} else if (pending.text) {
|
|
445
|
+
this.deliverToSession(sessions2[idx].id, pending.text);
|
|
446
|
+
}
|
|
447
|
+
this.sendMessage(`Sent to [${this.getLabel(sessions2[idx])}]`);
|
|
448
|
+
} else {
|
|
449
|
+
this.sendMessage("Invalid session number.");
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
434
452
|
}
|
|
435
|
-
return;
|
|
436
453
|
}
|
|
437
454
|
}
|
|
438
455
|
const sessions = this.getSessions?.() ?? [];
|
|
@@ -441,10 +458,19 @@ ${message}`;
|
|
|
441
458
|
return;
|
|
442
459
|
}
|
|
443
460
|
if (sessions.length === 1) {
|
|
444
|
-
|
|
461
|
+
if (hasPhoto) {
|
|
462
|
+
await this.deliverPhotoToSession(sessions[0].id, msg.photo, text);
|
|
463
|
+
} else {
|
|
464
|
+
this.deliverToSession(sessions[0].id, text);
|
|
465
|
+
}
|
|
445
466
|
return;
|
|
446
467
|
}
|
|
447
|
-
|
|
468
|
+
if (hasPhoto) {
|
|
469
|
+
const largest = msg.photo[msg.photo.length - 1];
|
|
470
|
+
this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });
|
|
471
|
+
} else {
|
|
472
|
+
this.pendingMessages.set(msg.chat.id, { text });
|
|
473
|
+
}
|
|
448
474
|
const sessionList = sessions.map((s, i) => `/s_${i + 1} - ${this.getLabel(s)}`).join("\n");
|
|
449
475
|
this.sendMessage(`Multiple sessions active. Reply with a command to select:
|
|
450
476
|
|
|
@@ -455,6 +481,46 @@ ${sessionList}`);
|
|
|
455
481
|
this.onMessageToSession(sessionId2, content);
|
|
456
482
|
}
|
|
457
483
|
}
|
|
484
|
+
async deliverPhotoToSession(sessionId2, photos, caption) {
|
|
485
|
+
const largest = photos[photos.length - 1];
|
|
486
|
+
await this.deliverPhotoToSessionByFileId(sessionId2, largest.file_id, caption);
|
|
487
|
+
}
|
|
488
|
+
async deliverPhotoToSessionByFileId(sessionId2, fileId, caption) {
|
|
489
|
+
try {
|
|
490
|
+
const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);
|
|
491
|
+
if (!fileRes.ok) {
|
|
492
|
+
logger.warn("Failed to get Telegram file info");
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const fileData = await fileRes.json();
|
|
496
|
+
if (!fileData.ok) return;
|
|
497
|
+
const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;
|
|
498
|
+
const imgRes = await fetch(downloadUrl);
|
|
499
|
+
if (!imgRes.ok) {
|
|
500
|
+
logger.warn("Failed to download Telegram photo");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const buffer = Buffer.from(await imgRes.arrayBuffer());
|
|
504
|
+
const ext = fileData.result.file_path.split(".").pop() || "jpg";
|
|
505
|
+
const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
506
|
+
fs2.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
507
|
+
const filename = `${randomUUID2()}.${ext}`;
|
|
508
|
+
const filePath = path3.join(UPLOADS_DIR, filename);
|
|
509
|
+
fs2.writeFileSync(filePath, buffer);
|
|
510
|
+
logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
|
|
511
|
+
if (this.onImageToSession) {
|
|
512
|
+
this.onImageToSession(sessionId2, filePath, mimeType, caption);
|
|
513
|
+
}
|
|
514
|
+
setTimeout(() => {
|
|
515
|
+
try {
|
|
516
|
+
fs2.unlinkSync(filePath);
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}, 5 * 60 * 1e3);
|
|
520
|
+
} catch (err) {
|
|
521
|
+
logger.warn(`Telegram photo download failed: ${err.message}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
458
524
|
getLabel(session) {
|
|
459
525
|
return session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
460
526
|
}
|
|
@@ -475,11 +541,11 @@ __export(server_exports, {
|
|
|
475
541
|
HubServer: () => HubServer
|
|
476
542
|
});
|
|
477
543
|
import http from "http";
|
|
478
|
-
import
|
|
479
|
-
import
|
|
544
|
+
import fs3 from "fs";
|
|
545
|
+
import path4 from "path";
|
|
480
546
|
import { fileURLToPath } from "url";
|
|
481
547
|
import { WebSocketServer, WebSocket } from "ws";
|
|
482
|
-
import { randomUUID as
|
|
548
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
483
549
|
var __dirname, HubServer;
|
|
484
550
|
var init_server = __esm({
|
|
485
551
|
"src/hub/server.ts"() {
|
|
@@ -490,7 +556,7 @@ var init_server = __esm({
|
|
|
490
556
|
init_notifier();
|
|
491
557
|
init_telegram();
|
|
492
558
|
init_config();
|
|
493
|
-
__dirname =
|
|
559
|
+
__dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
494
560
|
HubServer = class {
|
|
495
561
|
httpServer;
|
|
496
562
|
wssChannel;
|
|
@@ -639,29 +705,31 @@ var init_server = __esm({
|
|
|
639
705
|
this.handleTelegramSave(req, res);
|
|
640
706
|
} else if (url.pathname === "/api/telegram/test" && req.method === "POST") {
|
|
641
707
|
this.handleTelegramTest(req, res);
|
|
708
|
+
} else if (url.pathname === "/api/telegram/detect" && req.method === "POST") {
|
|
709
|
+
this.handleTelegramDetect(req, res);
|
|
642
710
|
} else {
|
|
643
711
|
this.jsonResponse(res, 404, { error: "Not found" });
|
|
644
712
|
}
|
|
645
713
|
}
|
|
646
714
|
serveDashboard(res) {
|
|
647
715
|
const candidates = [
|
|
648
|
-
|
|
716
|
+
path4.join(__dirname, "..", "dashboard", "index.html"),
|
|
649
717
|
// from dist/hub/
|
|
650
|
-
|
|
718
|
+
path4.join(__dirname, "dashboard", "index.html"),
|
|
651
719
|
// from dist/ (bundled index.js)
|
|
652
|
-
|
|
720
|
+
path4.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
653
721
|
// from dist/hub/ -> src/
|
|
654
|
-
|
|
722
|
+
path4.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
655
723
|
// from dist/ -> src/
|
|
656
|
-
|
|
724
|
+
path4.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
657
725
|
// from cwd
|
|
658
|
-
|
|
726
|
+
path4.join(process.cwd(), "src", "dashboard", "index.html")
|
|
659
727
|
// from cwd/src
|
|
660
728
|
];
|
|
661
729
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
662
730
|
for (const candidate of candidates) {
|
|
663
|
-
if (
|
|
664
|
-
const html =
|
|
731
|
+
if (fs3.existsSync(candidate)) {
|
|
732
|
+
const html = fs3.readFileSync(candidate, "utf-8");
|
|
665
733
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
666
734
|
res.end(html);
|
|
667
735
|
return;
|
|
@@ -839,11 +907,11 @@ var init_server = __esm({
|
|
|
839
907
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
840
908
|
return;
|
|
841
909
|
}
|
|
842
|
-
|
|
910
|
+
fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
843
911
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
844
|
-
const filename = `${
|
|
845
|
-
const filePath =
|
|
846
|
-
|
|
912
|
+
const filename = `${randomUUID3()}.${ext}`;
|
|
913
|
+
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
914
|
+
fs3.writeFileSync(filePath, buffer);
|
|
847
915
|
const forwardMsg = {
|
|
848
916
|
type: "image_to_session",
|
|
849
917
|
sessionId: sessionId2,
|
|
@@ -856,7 +924,7 @@ var init_server = __esm({
|
|
|
856
924
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
857
925
|
setTimeout(() => {
|
|
858
926
|
try {
|
|
859
|
-
|
|
927
|
+
fs3.unlinkSync(filePath);
|
|
860
928
|
} catch {
|
|
861
929
|
}
|
|
862
930
|
}, 5 * 60 * 1e3);
|
|
@@ -889,6 +957,14 @@ var init_server = __esm({
|
|
|
889
957
|
logger.info(`Telegram message forwarded to session: ${sessionId2}`);
|
|
890
958
|
}
|
|
891
959
|
};
|
|
960
|
+
this.telegramBot.onImageToSession = (sessionId2, imagePath, mimeType, caption) => {
|
|
961
|
+
const channelWs = this.channelSockets.get(sessionId2);
|
|
962
|
+
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
963
|
+
const msg = { type: "image_to_session", sessionId: sessionId2, imagePath, mimeType, content: caption };
|
|
964
|
+
channelWs.send(JSON.stringify(msg));
|
|
965
|
+
logger.info(`Telegram photo forwarded to session: ${sessionId2}`);
|
|
966
|
+
}
|
|
967
|
+
};
|
|
892
968
|
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
893
969
|
this.telegramBot.startPolling();
|
|
894
970
|
logger.info("Telegram bot initialized");
|
|
@@ -947,13 +1023,57 @@ var init_server = __esm({
|
|
|
947
1023
|
this.jsonResponse(res, 500, { error: err.message });
|
|
948
1024
|
}
|
|
949
1025
|
}
|
|
1026
|
+
async handleTelegramDetect(req, res) {
|
|
1027
|
+
const body = await this.readBody(req);
|
|
1028
|
+
if (!body) {
|
|
1029
|
+
this.jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const { botToken } = body;
|
|
1033
|
+
if (!botToken) {
|
|
1034
|
+
this.jsonResponse(res, 400, { error: "botToken required" });
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
try {
|
|
1038
|
+
const detectRes = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates?timeout=0&limit=10`, {
|
|
1039
|
+
signal: AbortSignal.timeout(1e4)
|
|
1040
|
+
});
|
|
1041
|
+
if (!detectRes.ok) {
|
|
1042
|
+
const err = await detectRes.json();
|
|
1043
|
+
this.jsonResponse(res, 400, { error: err.description || "Invalid bot token" });
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const data = await detectRes.json();
|
|
1047
|
+
if (!data.ok || !data.result.length) {
|
|
1048
|
+
this.jsonResponse(res, 200, { ok: false, chats: [] });
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const chatMap = /* @__PURE__ */ new Map();
|
|
1052
|
+
for (const update of data.result) {
|
|
1053
|
+
if (update.message?.chat) {
|
|
1054
|
+
const chat = update.message.chat;
|
|
1055
|
+
const id = String(chat.id);
|
|
1056
|
+
if (!chatMap.has(id)) {
|
|
1057
|
+
chatMap.set(id, {
|
|
1058
|
+
id,
|
|
1059
|
+
name: chat.title || chat.first_name || id,
|
|
1060
|
+
type: chat.type
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
this.jsonResponse(res, 200, { ok: true, chats: [...chatMap.values()] });
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
this.jsonResponse(res, 500, { error: err.message });
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
950
1070
|
cleanupUploads() {
|
|
951
1071
|
try {
|
|
952
|
-
if (!
|
|
953
|
-
const files =
|
|
1072
|
+
if (!fs3.existsSync(UPLOADS_DIR)) return;
|
|
1073
|
+
const files = fs3.readdirSync(UPLOADS_DIR);
|
|
954
1074
|
for (const file of files) {
|
|
955
1075
|
try {
|
|
956
|
-
|
|
1076
|
+
fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
957
1077
|
} catch {
|
|
958
1078
|
}
|
|
959
1079
|
}
|
|
@@ -1126,8 +1246,8 @@ import {
|
|
|
1126
1246
|
CallToolRequestSchema,
|
|
1127
1247
|
ListToolsRequestSchema
|
|
1128
1248
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1129
|
-
import { randomUUID as
|
|
1130
|
-
import
|
|
1249
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1250
|
+
import path5 from "path";
|
|
1131
1251
|
async function main() {
|
|
1132
1252
|
logger.info(`Starting MCP channel server (session: ${sessionId})`);
|
|
1133
1253
|
hubClient.connect();
|
|
@@ -1166,8 +1286,8 @@ var init_server2 = __esm({
|
|
|
1166
1286
|
init_constants();
|
|
1167
1287
|
init_config();
|
|
1168
1288
|
init_hub_client();
|
|
1169
|
-
sessionId =
|
|
1170
|
-
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ??
|
|
1289
|
+
sessionId = randomUUID4();
|
|
1290
|
+
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path5.basename(process.cwd());
|
|
1171
1291
|
server = new Server(
|
|
1172
1292
|
{
|
|
1173
1293
|
name: CHANNEL_SERVER_NAME,
|
|
@@ -1301,11 +1421,11 @@ init_config();
|
|
|
1301
1421
|
init_constants();
|
|
1302
1422
|
init_logger();
|
|
1303
1423
|
import { spawn } from "child_process";
|
|
1304
|
-
import
|
|
1305
|
-
import
|
|
1424
|
+
import fs4 from "fs";
|
|
1425
|
+
import path6 from "path";
|
|
1306
1426
|
import readline from "readline";
|
|
1307
1427
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1308
|
-
var __dirname2 =
|
|
1428
|
+
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1309
1429
|
function printUsage() {
|
|
1310
1430
|
console.log(`
|
|
1311
1431
|
claude-alarm - Monitor Claude Code sessions with notifications
|
|
@@ -1326,8 +1446,8 @@ Quick start:
|
|
|
1326
1446
|
}
|
|
1327
1447
|
async function checkForUpdates() {
|
|
1328
1448
|
try {
|
|
1329
|
-
const pkgPath =
|
|
1330
|
-
const pkg = JSON.parse(
|
|
1449
|
+
const pkgPath = path6.join(__dirname2, "..", "package.json");
|
|
1450
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
1331
1451
|
const currentVersion = pkg.version;
|
|
1332
1452
|
const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
|
|
1333
1453
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -1350,25 +1470,25 @@ async function hubStart(daemon) {
|
|
|
1350
1470
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
1351
1471
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1352
1472
|
checkForUpdates();
|
|
1353
|
-
if (
|
|
1354
|
-
const pid = parseInt(
|
|
1473
|
+
if (fs4.existsSync(PID_FILE)) {
|
|
1474
|
+
const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1355
1475
|
if (isProcessRunning(pid)) {
|
|
1356
1476
|
console.log(`Hub is already running (PID: ${pid}) on http://${displayHost}:${port}`);
|
|
1357
1477
|
return;
|
|
1358
1478
|
}
|
|
1359
|
-
|
|
1479
|
+
fs4.unlinkSync(PID_FILE);
|
|
1360
1480
|
}
|
|
1361
1481
|
if (daemon) {
|
|
1362
1482
|
ensureConfigDir();
|
|
1363
|
-
const logFd =
|
|
1364
|
-
const hubScript =
|
|
1483
|
+
const logFd = fs4.openSync(LOG_FILE, "a");
|
|
1484
|
+
const hubScript = path6.join(__dirname2, "hub", "server.js");
|
|
1365
1485
|
const child = spawn(process.execPath, [hubScript], {
|
|
1366
1486
|
detached: true,
|
|
1367
1487
|
stdio: ["ignore", logFd, logFd],
|
|
1368
1488
|
env: { ...process.env }
|
|
1369
1489
|
});
|
|
1370
1490
|
if (child.pid) {
|
|
1371
|
-
|
|
1491
|
+
fs4.writeFileSync(PID_FILE, String(child.pid), "utf-8");
|
|
1372
1492
|
child.unref();
|
|
1373
1493
|
console.log(`Hub started as daemon (PID: ${child.pid})`);
|
|
1374
1494
|
console.log(`Dashboard: http://${displayHost}:${port}`);
|
|
@@ -1385,11 +1505,11 @@ async function hubStart(daemon) {
|
|
|
1385
1505
|
const hub = new HubServer2(config2);
|
|
1386
1506
|
await hub.start();
|
|
1387
1507
|
ensureConfigDir();
|
|
1388
|
-
|
|
1508
|
+
fs4.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
1389
1509
|
const shutdown = async () => {
|
|
1390
1510
|
console.log("\nShutting down...");
|
|
1391
1511
|
await hub.stop();
|
|
1392
|
-
if (
|
|
1512
|
+
if (fs4.existsSync(PID_FILE)) fs4.unlinkSync(PID_FILE);
|
|
1393
1513
|
process.exit(0);
|
|
1394
1514
|
};
|
|
1395
1515
|
process.on("SIGINT", shutdown);
|
|
@@ -1397,18 +1517,18 @@ async function hubStart(daemon) {
|
|
|
1397
1517
|
}
|
|
1398
1518
|
}
|
|
1399
1519
|
function hubStop() {
|
|
1400
|
-
if (!
|
|
1520
|
+
if (!fs4.existsSync(PID_FILE)) {
|
|
1401
1521
|
console.log("Hub is not running (no PID file found)");
|
|
1402
1522
|
return;
|
|
1403
1523
|
}
|
|
1404
|
-
const pid = parseInt(
|
|
1524
|
+
const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1405
1525
|
try {
|
|
1406
1526
|
process.kill(pid, "SIGTERM");
|
|
1407
1527
|
console.log(`Hub stopped (PID: ${pid})`);
|
|
1408
1528
|
} catch {
|
|
1409
1529
|
console.log("Hub process not found (may have already stopped)");
|
|
1410
1530
|
}
|
|
1411
|
-
|
|
1531
|
+
fs4.unlinkSync(PID_FILE);
|
|
1412
1532
|
}
|
|
1413
1533
|
async function hubStatus() {
|
|
1414
1534
|
const config2 = loadConfig();
|
|
@@ -1416,8 +1536,8 @@ async function hubStatus() {
|
|
|
1416
1536
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
1417
1537
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1418
1538
|
let pidInfo = "not running";
|
|
1419
|
-
if (
|
|
1420
|
-
const pid = parseInt(
|
|
1539
|
+
if (fs4.existsSync(PID_FILE)) {
|
|
1540
|
+
const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1421
1541
|
if (isProcessRunning(pid)) {
|
|
1422
1542
|
pidInfo = `running (PID: ${pid})`;
|
|
1423
1543
|
} else {
|
|
@@ -1489,7 +1609,7 @@ function ask(question) {
|
|
|
1489
1609
|
}
|
|
1490
1610
|
async function init() {
|
|
1491
1611
|
const dir = process.cwd();
|
|
1492
|
-
const projectName =
|
|
1612
|
+
const projectName = path6.basename(dir);
|
|
1493
1613
|
console.log(`
|
|
1494
1614
|
claude-alarm init for "${projectName}"
|
|
1495
1615
|
`);
|
|
@@ -1509,11 +1629,11 @@ claude-alarm init for "${projectName}"
|
|
|
1509
1629
|
if (port) env.CLAUDE_ALARM_HUB_PORT = port;
|
|
1510
1630
|
if (token) env.CLAUDE_ALARM_HUB_TOKEN = token;
|
|
1511
1631
|
}
|
|
1512
|
-
const mcpPath =
|
|
1632
|
+
const mcpPath = path6.join(dir, ".mcp.json");
|
|
1513
1633
|
let mcpConfig = {};
|
|
1514
|
-
if (
|
|
1634
|
+
if (fs4.existsSync(mcpPath)) {
|
|
1515
1635
|
try {
|
|
1516
|
-
mcpConfig = JSON.parse(
|
|
1636
|
+
mcpConfig = JSON.parse(fs4.readFileSync(mcpPath, "utf-8"));
|
|
1517
1637
|
} catch {
|
|
1518
1638
|
mcpConfig = {};
|
|
1519
1639
|
}
|
|
@@ -1524,7 +1644,7 @@ claude-alarm init for "${projectName}"
|
|
|
1524
1644
|
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
1525
1645
|
env
|
|
1526
1646
|
};
|
|
1527
|
-
|
|
1647
|
+
fs4.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
1528
1648
|
console.log(`
|
|
1529
1649
|
\u2713 Created ${mcpPath}`);
|
|
1530
1650
|
if (remote.toLowerCase() !== "y") {
|