@delt/claude-alarm 0.5.3 → 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 +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/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 fs3 from "fs";
|
|
4
|
+
import path4 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 as
|
|
27
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28
28
|
|
|
29
29
|
// src/shared/constants.ts
|
|
30
30
|
import path from "path";
|
|
@@ -187,6 +187,9 @@ var Notifier = class {
|
|
|
187
187
|
};
|
|
188
188
|
|
|
189
189
|
// src/hub/telegram.ts
|
|
190
|
+
import fs from "fs";
|
|
191
|
+
import path2 from "path";
|
|
192
|
+
import { randomUUID } from "crypto";
|
|
190
193
|
var TELEGRAM_API = "https://api.telegram.org/bot";
|
|
191
194
|
var TelegramBot = class {
|
|
192
195
|
config;
|
|
@@ -195,13 +198,15 @@ var TelegramBot = class {
|
|
|
195
198
|
pollTimer = null;
|
|
196
199
|
// message_id -> sessionId mapping for reply-based routing
|
|
197
200
|
messageSessionMap = /* @__PURE__ */ new Map();
|
|
198
|
-
// Callback: when a message arrives from Telegram for a session
|
|
201
|
+
// Callback: when a text message arrives from Telegram for a session
|
|
199
202
|
onMessageToSession;
|
|
203
|
+
// Callback: when an image arrives from Telegram for a session
|
|
204
|
+
onImageToSession;
|
|
200
205
|
// Callback: get current sessions list
|
|
201
206
|
getSessions;
|
|
202
|
-
//
|
|
207
|
+
// Pending messages for session selection
|
|
203
208
|
pendingMessages = /* @__PURE__ */ new Map();
|
|
204
|
-
// chatId -> pending
|
|
209
|
+
// chatId -> pending
|
|
205
210
|
constructor(config) {
|
|
206
211
|
this.config = config;
|
|
207
212
|
}
|
|
@@ -297,31 +302,42 @@ ${message}`;
|
|
|
297
302
|
if (!this.polling) return;
|
|
298
303
|
this.pollTimer = setTimeout(() => this.poll(), delay);
|
|
299
304
|
}
|
|
300
|
-
handleIncomingMessage(msg) {
|
|
301
|
-
if (!msg.text) return;
|
|
305
|
+
async handleIncomingMessage(msg) {
|
|
302
306
|
if (String(msg.chat.id) !== String(this.config.chatId)) return;
|
|
303
|
-
const
|
|
307
|
+
const hasPhoto = msg.photo && msg.photo.length > 0;
|
|
308
|
+
const text = (msg.text || msg.caption || "").trim();
|
|
309
|
+
if (!text && !hasPhoto) return;
|
|
304
310
|
if (msg.reply_to_message) {
|
|
305
311
|
const sessionId = this.messageSessionMap.get(msg.reply_to_message.message_id);
|
|
306
312
|
if (sessionId) {
|
|
307
|
-
|
|
313
|
+
if (hasPhoto) {
|
|
314
|
+
await this.deliverPhotoToSession(sessionId, msg.photo, text);
|
|
315
|
+
} else {
|
|
316
|
+
this.deliverToSession(sessionId, text);
|
|
317
|
+
}
|
|
308
318
|
return;
|
|
309
319
|
}
|
|
310
320
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
if (text) {
|
|
322
|
+
const selectMatch = text.match(/^\/s_(\d+)$/);
|
|
323
|
+
if (selectMatch) {
|
|
324
|
+
const pending = this.pendingMessages.get(msg.chat.id);
|
|
325
|
+
if (pending) {
|
|
326
|
+
this.pendingMessages.delete(msg.chat.id);
|
|
327
|
+
const sessions2 = this.getSessions?.() ?? [];
|
|
328
|
+
const idx = parseInt(selectMatch[1], 10) - 1;
|
|
329
|
+
if (idx >= 0 && idx < sessions2.length) {
|
|
330
|
+
if (pending.photoFileId) {
|
|
331
|
+
await this.deliverPhotoToSessionByFileId(sessions2[idx].id, pending.photoFileId, pending.caption);
|
|
332
|
+
} else if (pending.text) {
|
|
333
|
+
this.deliverToSession(sessions2[idx].id, pending.text);
|
|
334
|
+
}
|
|
335
|
+
this.sendMessage(`Sent to [${this.getLabel(sessions2[idx])}]`);
|
|
336
|
+
} else {
|
|
337
|
+
this.sendMessage("Invalid session number.");
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
323
340
|
}
|
|
324
|
-
return;
|
|
325
341
|
}
|
|
326
342
|
}
|
|
327
343
|
const sessions = this.getSessions?.() ?? [];
|
|
@@ -330,10 +346,19 @@ ${message}`;
|
|
|
330
346
|
return;
|
|
331
347
|
}
|
|
332
348
|
if (sessions.length === 1) {
|
|
333
|
-
|
|
349
|
+
if (hasPhoto) {
|
|
350
|
+
await this.deliverPhotoToSession(sessions[0].id, msg.photo, text);
|
|
351
|
+
} else {
|
|
352
|
+
this.deliverToSession(sessions[0].id, text);
|
|
353
|
+
}
|
|
334
354
|
return;
|
|
335
355
|
}
|
|
336
|
-
|
|
356
|
+
if (hasPhoto) {
|
|
357
|
+
const largest = msg.photo[msg.photo.length - 1];
|
|
358
|
+
this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });
|
|
359
|
+
} else {
|
|
360
|
+
this.pendingMessages.set(msg.chat.id, { text });
|
|
361
|
+
}
|
|
337
362
|
const sessionList = sessions.map((s, i) => `/s_${i + 1} - ${this.getLabel(s)}`).join("\n");
|
|
338
363
|
this.sendMessage(`Multiple sessions active. Reply with a command to select:
|
|
339
364
|
|
|
@@ -344,6 +369,46 @@ ${sessionList}`);
|
|
|
344
369
|
this.onMessageToSession(sessionId, content);
|
|
345
370
|
}
|
|
346
371
|
}
|
|
372
|
+
async deliverPhotoToSession(sessionId, photos, caption) {
|
|
373
|
+
const largest = photos[photos.length - 1];
|
|
374
|
+
await this.deliverPhotoToSessionByFileId(sessionId, largest.file_id, caption);
|
|
375
|
+
}
|
|
376
|
+
async deliverPhotoToSessionByFileId(sessionId, fileId, caption) {
|
|
377
|
+
try {
|
|
378
|
+
const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);
|
|
379
|
+
if (!fileRes.ok) {
|
|
380
|
+
logger.warn("Failed to get Telegram file info");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const fileData = await fileRes.json();
|
|
384
|
+
if (!fileData.ok) return;
|
|
385
|
+
const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;
|
|
386
|
+
const imgRes = await fetch(downloadUrl);
|
|
387
|
+
if (!imgRes.ok) {
|
|
388
|
+
logger.warn("Failed to download Telegram photo");
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const buffer = Buffer.from(await imgRes.arrayBuffer());
|
|
392
|
+
const ext = fileData.result.file_path.split(".").pop() || "jpg";
|
|
393
|
+
const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
394
|
+
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
395
|
+
const filename = `${randomUUID()}.${ext}`;
|
|
396
|
+
const filePath = path2.join(UPLOADS_DIR, filename);
|
|
397
|
+
fs.writeFileSync(filePath, buffer);
|
|
398
|
+
logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
|
|
399
|
+
if (this.onImageToSession) {
|
|
400
|
+
this.onImageToSession(sessionId, filePath, mimeType, caption);
|
|
401
|
+
}
|
|
402
|
+
setTimeout(() => {
|
|
403
|
+
try {
|
|
404
|
+
fs.unlinkSync(filePath);
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}, 5 * 60 * 1e3);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
logger.warn(`Telegram photo download failed: ${err.message}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
347
412
|
getLabel(session) {
|
|
348
413
|
return session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
349
414
|
}
|
|
@@ -357,9 +422,9 @@ ${sessionList}`);
|
|
|
357
422
|
};
|
|
358
423
|
|
|
359
424
|
// src/shared/config.ts
|
|
360
|
-
import
|
|
361
|
-
import
|
|
362
|
-
import { randomUUID } from "crypto";
|
|
425
|
+
import fs2 from "fs";
|
|
426
|
+
import path3 from "path";
|
|
427
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
363
428
|
var DEFAULT_CONFIG = {
|
|
364
429
|
hub: {
|
|
365
430
|
host: DEFAULT_HUB_HOST,
|
|
@@ -372,18 +437,18 @@ var DEFAULT_CONFIG = {
|
|
|
372
437
|
webhooks: []
|
|
373
438
|
};
|
|
374
439
|
function ensureConfigDir() {
|
|
375
|
-
if (!
|
|
376
|
-
|
|
440
|
+
if (!fs2.existsSync(CONFIG_DIR)) {
|
|
441
|
+
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
377
442
|
}
|
|
378
443
|
}
|
|
379
444
|
function loadConfig() {
|
|
380
445
|
ensureConfigDir();
|
|
381
446
|
let config;
|
|
382
|
-
if (!
|
|
447
|
+
if (!fs2.existsSync(CONFIG_FILE)) {
|
|
383
448
|
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
384
449
|
} else {
|
|
385
450
|
try {
|
|
386
|
-
const raw =
|
|
451
|
+
const raw = fs2.readFileSync(CONFIG_FILE, "utf-8");
|
|
387
452
|
const parsed = JSON.parse(raw);
|
|
388
453
|
config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...parsed.telegram ? { telegram: parsed.telegram } : {} };
|
|
389
454
|
} catch {
|
|
@@ -391,22 +456,22 @@ function loadConfig() {
|
|
|
391
456
|
}
|
|
392
457
|
}
|
|
393
458
|
if (!config.hub.token) {
|
|
394
|
-
config.hub.token =
|
|
459
|
+
config.hub.token = randomUUID2();
|
|
395
460
|
saveConfig(config);
|
|
396
461
|
}
|
|
397
462
|
return config;
|
|
398
463
|
}
|
|
399
464
|
function saveConfig(config) {
|
|
400
465
|
ensureConfigDir();
|
|
401
|
-
|
|
466
|
+
fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
402
467
|
}
|
|
403
468
|
function setupMcpConfig(targetDir) {
|
|
404
469
|
const dir = targetDir ?? process.cwd();
|
|
405
|
-
const mcpPath =
|
|
470
|
+
const mcpPath = path3.join(dir, ".mcp.json");
|
|
406
471
|
let mcpConfig = {};
|
|
407
|
-
if (
|
|
472
|
+
if (fs2.existsSync(mcpPath)) {
|
|
408
473
|
try {
|
|
409
|
-
mcpConfig = JSON.parse(
|
|
474
|
+
mcpConfig = JSON.parse(fs2.readFileSync(mcpPath, "utf-8"));
|
|
410
475
|
} catch {
|
|
411
476
|
mcpConfig = {};
|
|
412
477
|
}
|
|
@@ -418,15 +483,15 @@ function setupMcpConfig(targetDir) {
|
|
|
418
483
|
command: "npx",
|
|
419
484
|
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
420
485
|
env: {
|
|
421
|
-
CLAUDE_ALARM_SESSION_NAME:
|
|
486
|
+
CLAUDE_ALARM_SESSION_NAME: path3.basename(dir)
|
|
422
487
|
}
|
|
423
488
|
};
|
|
424
|
-
|
|
489
|
+
fs2.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
425
490
|
return mcpPath;
|
|
426
491
|
}
|
|
427
492
|
|
|
428
493
|
// src/hub/server.ts
|
|
429
|
-
var __dirname =
|
|
494
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
430
495
|
var HubServer = class {
|
|
431
496
|
httpServer;
|
|
432
497
|
wssChannel;
|
|
@@ -583,23 +648,23 @@ var HubServer = class {
|
|
|
583
648
|
}
|
|
584
649
|
serveDashboard(res) {
|
|
585
650
|
const candidates = [
|
|
586
|
-
|
|
651
|
+
path4.join(__dirname, "..", "dashboard", "index.html"),
|
|
587
652
|
// from dist/hub/
|
|
588
|
-
|
|
653
|
+
path4.join(__dirname, "dashboard", "index.html"),
|
|
589
654
|
// from dist/ (bundled index.js)
|
|
590
|
-
|
|
655
|
+
path4.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
591
656
|
// from dist/hub/ -> src/
|
|
592
|
-
|
|
657
|
+
path4.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
593
658
|
// from dist/ -> src/
|
|
594
|
-
|
|
659
|
+
path4.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
595
660
|
// from cwd
|
|
596
|
-
|
|
661
|
+
path4.join(process.cwd(), "src", "dashboard", "index.html")
|
|
597
662
|
// from cwd/src
|
|
598
663
|
];
|
|
599
664
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
600
665
|
for (const candidate of candidates) {
|
|
601
|
-
if (
|
|
602
|
-
const html =
|
|
666
|
+
if (fs3.existsSync(candidate)) {
|
|
667
|
+
const html = fs3.readFileSync(candidate, "utf-8");
|
|
603
668
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
604
669
|
res.end(html);
|
|
605
670
|
return;
|
|
@@ -777,11 +842,11 @@ var HubServer = class {
|
|
|
777
842
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
778
843
|
return;
|
|
779
844
|
}
|
|
780
|
-
|
|
845
|
+
fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
781
846
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
782
|
-
const filename = `${
|
|
783
|
-
const filePath =
|
|
784
|
-
|
|
847
|
+
const filename = `${randomUUID3()}.${ext}`;
|
|
848
|
+
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
849
|
+
fs3.writeFileSync(filePath, buffer);
|
|
785
850
|
const forwardMsg = {
|
|
786
851
|
type: "image_to_session",
|
|
787
852
|
sessionId,
|
|
@@ -794,7 +859,7 @@ var HubServer = class {
|
|
|
794
859
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
795
860
|
setTimeout(() => {
|
|
796
861
|
try {
|
|
797
|
-
|
|
862
|
+
fs3.unlinkSync(filePath);
|
|
798
863
|
} catch {
|
|
799
864
|
}
|
|
800
865
|
}, 5 * 60 * 1e3);
|
|
@@ -827,6 +892,14 @@ var HubServer = class {
|
|
|
827
892
|
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
828
893
|
}
|
|
829
894
|
};
|
|
895
|
+
this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {
|
|
896
|
+
const channelWs = this.channelSockets.get(sessionId);
|
|
897
|
+
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
898
|
+
const msg = { type: "image_to_session", sessionId, imagePath, mimeType, content: caption };
|
|
899
|
+
channelWs.send(JSON.stringify(msg));
|
|
900
|
+
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
901
|
+
}
|
|
902
|
+
};
|
|
830
903
|
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
831
904
|
this.telegramBot.startPolling();
|
|
832
905
|
logger.info("Telegram bot initialized");
|
|
@@ -931,11 +1004,11 @@ var HubServer = class {
|
|
|
931
1004
|
}
|
|
932
1005
|
cleanupUploads() {
|
|
933
1006
|
try {
|
|
934
|
-
if (!
|
|
935
|
-
const files =
|
|
1007
|
+
if (!fs3.existsSync(UPLOADS_DIR)) return;
|
|
1008
|
+
const files = fs3.readdirSync(UPLOADS_DIR);
|
|
936
1009
|
for (const file of files) {
|
|
937
1010
|
try {
|
|
938
|
-
|
|
1011
|
+
fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
939
1012
|
} catch {
|
|
940
1013
|
}
|
|
941
1014
|
}
|