@delt/claude-alarm 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/dist/cli.js +143 -69
- package/dist/cli.js.map +1 -1
- package/dist/hub/server.js +124 -51
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +129 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,17 +140,22 @@ Configure via dashboard (⚙ Settings → Webhook tab) or in config:
|
|
|
140
140
|
|
|
141
141
|
### Telegram Bot
|
|
142
142
|
|
|
143
|
-
Two-way messaging with Claude sessions via Telegram
|
|
143
|
+
Two-way messaging with Claude sessions via Telegram — text and images.
|
|
144
|
+
|
|
145
|
+
**Setup (guided wizard in dashboard):**
|
|
144
146
|
|
|
145
147
|
1. Create a bot with [@BotFather](https://t.me/BotFather) on Telegram
|
|
146
|
-
2.
|
|
147
|
-
3.
|
|
148
|
-
4.
|
|
148
|
+
2. Open dashboard → ⚙ Settings → Telegram tab
|
|
149
|
+
3. **Step 1:** Paste your Bot Token → Next
|
|
150
|
+
4. **Step 2:** Send any message to your bot, then click **Detect Chat ID** → select your chat → Next
|
|
151
|
+
5. **Step 3:** Send Test → Save
|
|
149
152
|
|
|
150
153
|
**Features:**
|
|
151
154
|
- Notifications forwarded to Telegram with session labels
|
|
152
|
-
- Reply to a notification
|
|
155
|
+
- Reply to a notification → routed to the correct session
|
|
153
156
|
- Send a new message → auto-delivered if 1 session, or pick from a list
|
|
157
|
+
- Send photos from Telegram → downloaded and forwarded to Claude
|
|
158
|
+
- Photo captions included as text alongside the image
|
|
154
159
|
|
|
155
160
|
```json
|
|
156
161
|
{
|
|
@@ -188,14 +193,19 @@ Two-way messaging with Claude sessions via Telegram:
|
|
|
188
193
|
}
|
|
189
194
|
```
|
|
190
195
|
|
|
191
|
-
## Image
|
|
196
|
+
## Image Support
|
|
192
197
|
|
|
193
|
-
|
|
198
|
+
**Dashboard (local sessions):**
|
|
194
199
|
- **Ctrl+V** — Paste from clipboard
|
|
195
200
|
- **Drag & Drop** — Drop image onto message area
|
|
196
201
|
- **Attach button** — Click 📎 to browse files
|
|
202
|
+
- Images + text sent together as one message
|
|
203
|
+
|
|
204
|
+
**Telegram:**
|
|
205
|
+
- Send photos to the bot → forwarded to Claude session
|
|
206
|
+
- Photo captions included as text
|
|
197
207
|
|
|
198
|
-
>
|
|
208
|
+
> Dashboard images are only available for local sessions (same machine as Hub). Max 10MB, auto-deleted after 5 minutes.
|
|
199
209
|
|
|
200
210
|
## Platform Support
|
|
201
211
|
|
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;
|
|
@@ -647,23 +713,23 @@ var init_server = __esm({
|
|
|
647
713
|
}
|
|
648
714
|
serveDashboard(res) {
|
|
649
715
|
const candidates = [
|
|
650
|
-
|
|
716
|
+
path4.join(__dirname, "..", "dashboard", "index.html"),
|
|
651
717
|
// from dist/hub/
|
|
652
|
-
|
|
718
|
+
path4.join(__dirname, "dashboard", "index.html"),
|
|
653
719
|
// from dist/ (bundled index.js)
|
|
654
|
-
|
|
720
|
+
path4.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
655
721
|
// from dist/hub/ -> src/
|
|
656
|
-
|
|
722
|
+
path4.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
657
723
|
// from dist/ -> src/
|
|
658
|
-
|
|
724
|
+
path4.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
659
725
|
// from cwd
|
|
660
|
-
|
|
726
|
+
path4.join(process.cwd(), "src", "dashboard", "index.html")
|
|
661
727
|
// from cwd/src
|
|
662
728
|
];
|
|
663
729
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
664
730
|
for (const candidate of candidates) {
|
|
665
|
-
if (
|
|
666
|
-
const html =
|
|
731
|
+
if (fs3.existsSync(candidate)) {
|
|
732
|
+
const html = fs3.readFileSync(candidate, "utf-8");
|
|
667
733
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
668
734
|
res.end(html);
|
|
669
735
|
return;
|
|
@@ -841,11 +907,11 @@ var init_server = __esm({
|
|
|
841
907
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
842
908
|
return;
|
|
843
909
|
}
|
|
844
|
-
|
|
910
|
+
fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
845
911
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
846
|
-
const filename = `${
|
|
847
|
-
const filePath =
|
|
848
|
-
|
|
912
|
+
const filename = `${randomUUID3()}.${ext}`;
|
|
913
|
+
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
914
|
+
fs3.writeFileSync(filePath, buffer);
|
|
849
915
|
const forwardMsg = {
|
|
850
916
|
type: "image_to_session",
|
|
851
917
|
sessionId: sessionId2,
|
|
@@ -858,7 +924,7 @@ var init_server = __esm({
|
|
|
858
924
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
859
925
|
setTimeout(() => {
|
|
860
926
|
try {
|
|
861
|
-
|
|
927
|
+
fs3.unlinkSync(filePath);
|
|
862
928
|
} catch {
|
|
863
929
|
}
|
|
864
930
|
}, 5 * 60 * 1e3);
|
|
@@ -891,6 +957,14 @@ var init_server = __esm({
|
|
|
891
957
|
logger.info(`Telegram message forwarded to session: ${sessionId2}`);
|
|
892
958
|
}
|
|
893
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
|
+
};
|
|
894
968
|
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
895
969
|
this.telegramBot.startPolling();
|
|
896
970
|
logger.info("Telegram bot initialized");
|
|
@@ -995,11 +1069,11 @@ var init_server = __esm({
|
|
|
995
1069
|
}
|
|
996
1070
|
cleanupUploads() {
|
|
997
1071
|
try {
|
|
998
|
-
if (!
|
|
999
|
-
const files =
|
|
1072
|
+
if (!fs3.existsSync(UPLOADS_DIR)) return;
|
|
1073
|
+
const files = fs3.readdirSync(UPLOADS_DIR);
|
|
1000
1074
|
for (const file of files) {
|
|
1001
1075
|
try {
|
|
1002
|
-
|
|
1076
|
+
fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
1003
1077
|
} catch {
|
|
1004
1078
|
}
|
|
1005
1079
|
}
|
|
@@ -1172,8 +1246,8 @@ import {
|
|
|
1172
1246
|
CallToolRequestSchema,
|
|
1173
1247
|
ListToolsRequestSchema
|
|
1174
1248
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1175
|
-
import { randomUUID as
|
|
1176
|
-
import
|
|
1249
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1250
|
+
import path5 from "path";
|
|
1177
1251
|
async function main() {
|
|
1178
1252
|
logger.info(`Starting MCP channel server (session: ${sessionId})`);
|
|
1179
1253
|
hubClient.connect();
|
|
@@ -1212,8 +1286,8 @@ var init_server2 = __esm({
|
|
|
1212
1286
|
init_constants();
|
|
1213
1287
|
init_config();
|
|
1214
1288
|
init_hub_client();
|
|
1215
|
-
sessionId =
|
|
1216
|
-
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ??
|
|
1289
|
+
sessionId = randomUUID4();
|
|
1290
|
+
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path5.basename(process.cwd());
|
|
1217
1291
|
server = new Server(
|
|
1218
1292
|
{
|
|
1219
1293
|
name: CHANNEL_SERVER_NAME,
|
|
@@ -1347,11 +1421,11 @@ init_config();
|
|
|
1347
1421
|
init_constants();
|
|
1348
1422
|
init_logger();
|
|
1349
1423
|
import { spawn } from "child_process";
|
|
1350
|
-
import
|
|
1351
|
-
import
|
|
1424
|
+
import fs4 from "fs";
|
|
1425
|
+
import path6 from "path";
|
|
1352
1426
|
import readline from "readline";
|
|
1353
1427
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1354
|
-
var __dirname2 =
|
|
1428
|
+
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1355
1429
|
function printUsage() {
|
|
1356
1430
|
console.log(`
|
|
1357
1431
|
claude-alarm - Monitor Claude Code sessions with notifications
|
|
@@ -1372,8 +1446,8 @@ Quick start:
|
|
|
1372
1446
|
}
|
|
1373
1447
|
async function checkForUpdates() {
|
|
1374
1448
|
try {
|
|
1375
|
-
const pkgPath =
|
|
1376
|
-
const pkg = JSON.parse(
|
|
1449
|
+
const pkgPath = path6.join(__dirname2, "..", "package.json");
|
|
1450
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
1377
1451
|
const currentVersion = pkg.version;
|
|
1378
1452
|
const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
|
|
1379
1453
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -1396,25 +1470,25 @@ async function hubStart(daemon) {
|
|
|
1396
1470
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
1397
1471
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1398
1472
|
checkForUpdates();
|
|
1399
|
-
if (
|
|
1400
|
-
const pid = parseInt(
|
|
1473
|
+
if (fs4.existsSync(PID_FILE)) {
|
|
1474
|
+
const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1401
1475
|
if (isProcessRunning(pid)) {
|
|
1402
1476
|
console.log(`Hub is already running (PID: ${pid}) on http://${displayHost}:${port}`);
|
|
1403
1477
|
return;
|
|
1404
1478
|
}
|
|
1405
|
-
|
|
1479
|
+
fs4.unlinkSync(PID_FILE);
|
|
1406
1480
|
}
|
|
1407
1481
|
if (daemon) {
|
|
1408
1482
|
ensureConfigDir();
|
|
1409
|
-
const logFd =
|
|
1410
|
-
const hubScript =
|
|
1483
|
+
const logFd = fs4.openSync(LOG_FILE, "a");
|
|
1484
|
+
const hubScript = path6.join(__dirname2, "hub", "server.js");
|
|
1411
1485
|
const child = spawn(process.execPath, [hubScript], {
|
|
1412
1486
|
detached: true,
|
|
1413
1487
|
stdio: ["ignore", logFd, logFd],
|
|
1414
1488
|
env: { ...process.env }
|
|
1415
1489
|
});
|
|
1416
1490
|
if (child.pid) {
|
|
1417
|
-
|
|
1491
|
+
fs4.writeFileSync(PID_FILE, String(child.pid), "utf-8");
|
|
1418
1492
|
child.unref();
|
|
1419
1493
|
console.log(`Hub started as daemon (PID: ${child.pid})`);
|
|
1420
1494
|
console.log(`Dashboard: http://${displayHost}:${port}`);
|
|
@@ -1431,11 +1505,11 @@ async function hubStart(daemon) {
|
|
|
1431
1505
|
const hub = new HubServer2(config2);
|
|
1432
1506
|
await hub.start();
|
|
1433
1507
|
ensureConfigDir();
|
|
1434
|
-
|
|
1508
|
+
fs4.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
1435
1509
|
const shutdown = async () => {
|
|
1436
1510
|
console.log("\nShutting down...");
|
|
1437
1511
|
await hub.stop();
|
|
1438
|
-
if (
|
|
1512
|
+
if (fs4.existsSync(PID_FILE)) fs4.unlinkSync(PID_FILE);
|
|
1439
1513
|
process.exit(0);
|
|
1440
1514
|
};
|
|
1441
1515
|
process.on("SIGINT", shutdown);
|
|
@@ -1443,18 +1517,18 @@ async function hubStart(daemon) {
|
|
|
1443
1517
|
}
|
|
1444
1518
|
}
|
|
1445
1519
|
function hubStop() {
|
|
1446
|
-
if (!
|
|
1520
|
+
if (!fs4.existsSync(PID_FILE)) {
|
|
1447
1521
|
console.log("Hub is not running (no PID file found)");
|
|
1448
1522
|
return;
|
|
1449
1523
|
}
|
|
1450
|
-
const pid = parseInt(
|
|
1524
|
+
const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1451
1525
|
try {
|
|
1452
1526
|
process.kill(pid, "SIGTERM");
|
|
1453
1527
|
console.log(`Hub stopped (PID: ${pid})`);
|
|
1454
1528
|
} catch {
|
|
1455
1529
|
console.log("Hub process not found (may have already stopped)");
|
|
1456
1530
|
}
|
|
1457
|
-
|
|
1531
|
+
fs4.unlinkSync(PID_FILE);
|
|
1458
1532
|
}
|
|
1459
1533
|
async function hubStatus() {
|
|
1460
1534
|
const config2 = loadConfig();
|
|
@@ -1462,8 +1536,8 @@ async function hubStatus() {
|
|
|
1462
1536
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
1463
1537
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1464
1538
|
let pidInfo = "not running";
|
|
1465
|
-
if (
|
|
1466
|
-
const pid = parseInt(
|
|
1539
|
+
if (fs4.existsSync(PID_FILE)) {
|
|
1540
|
+
const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1467
1541
|
if (isProcessRunning(pid)) {
|
|
1468
1542
|
pidInfo = `running (PID: ${pid})`;
|
|
1469
1543
|
} else {
|
|
@@ -1535,7 +1609,7 @@ function ask(question) {
|
|
|
1535
1609
|
}
|
|
1536
1610
|
async function init() {
|
|
1537
1611
|
const dir = process.cwd();
|
|
1538
|
-
const projectName =
|
|
1612
|
+
const projectName = path6.basename(dir);
|
|
1539
1613
|
console.log(`
|
|
1540
1614
|
claude-alarm init for "${projectName}"
|
|
1541
1615
|
`);
|
|
@@ -1555,11 +1629,11 @@ claude-alarm init for "${projectName}"
|
|
|
1555
1629
|
if (port) env.CLAUDE_ALARM_HUB_PORT = port;
|
|
1556
1630
|
if (token) env.CLAUDE_ALARM_HUB_TOKEN = token;
|
|
1557
1631
|
}
|
|
1558
|
-
const mcpPath =
|
|
1632
|
+
const mcpPath = path6.join(dir, ".mcp.json");
|
|
1559
1633
|
let mcpConfig = {};
|
|
1560
|
-
if (
|
|
1634
|
+
if (fs4.existsSync(mcpPath)) {
|
|
1561
1635
|
try {
|
|
1562
|
-
mcpConfig = JSON.parse(
|
|
1636
|
+
mcpConfig = JSON.parse(fs4.readFileSync(mcpPath, "utf-8"));
|
|
1563
1637
|
} catch {
|
|
1564
1638
|
mcpConfig = {};
|
|
1565
1639
|
}
|
|
@@ -1570,7 +1644,7 @@ claude-alarm init for "${projectName}"
|
|
|
1570
1644
|
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
1571
1645
|
env
|
|
1572
1646
|
};
|
|
1573
|
-
|
|
1647
|
+
fs4.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
1574
1648
|
console.log(`
|
|
1575
1649
|
\u2713 Created ${mcpPath}`);
|
|
1576
1650
|
if (remote.toLowerCase() !== "y") {
|