@delt/claude-alarm 0.8.0 → 0.8.2
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 +161 -38
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +66 -17
- package/dist/hub/server.js +127 -17
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +127 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +66 -17
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/hub/server.ts
|
|
2
2
|
import http from "http";
|
|
3
|
-
import
|
|
3
|
+
import fs4 from "fs";
|
|
4
4
|
import path4 from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -210,7 +210,7 @@ import fs from "fs";
|
|
|
210
210
|
import path2 from "path";
|
|
211
211
|
import { randomUUID } from "crypto";
|
|
212
212
|
var TELEGRAM_API = "https://api.telegram.org/bot";
|
|
213
|
-
var TelegramBot = class {
|
|
213
|
+
var TelegramBot = class _TelegramBot {
|
|
214
214
|
config;
|
|
215
215
|
offset = 0;
|
|
216
216
|
polling = false;
|
|
@@ -442,33 +442,45 @@ ${this.mdToHtml(message)}`;
|
|
|
442
442
|
}
|
|
443
443
|
/** Send a permission request with inline buttons */
|
|
444
444
|
async sendPermissionRequest(sessionId, sessionLabel, requestId, toolName, description, inputPreview) {
|
|
445
|
+
const isNotify = toolName.endsWith("__notify") || toolName === "notify";
|
|
445
446
|
let preview = inputPreview;
|
|
447
|
+
let kind = "code";
|
|
448
|
+
let lang = "";
|
|
446
449
|
let truncated = false;
|
|
447
450
|
try {
|
|
448
451
|
const p = JSON.parse(inputPreview);
|
|
449
|
-
if (p.command)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
+
if (p.command) {
|
|
453
|
+
preview = `$ ${p.command}`;
|
|
454
|
+
lang = "bash";
|
|
455
|
+
} else if (p.title && p.message) {
|
|
456
|
+
preview = p.message;
|
|
457
|
+
kind = "prose";
|
|
458
|
+
} else if (p.file_path) {
|
|
452
459
|
preview = p.file_path;
|
|
460
|
+
lang = this.langOf(p.file_path);
|
|
453
461
|
if (p.content) {
|
|
454
462
|
preview += "\n" + p.content.slice(0, 3e3);
|
|
455
463
|
if (p.content.length > 500) truncated = true;
|
|
456
464
|
}
|
|
457
465
|
} else if (p.content && typeof p.content === "string") {
|
|
458
466
|
preview = p.content.slice(0, 3e3);
|
|
467
|
+
if (isNotify) kind = "prose";
|
|
459
468
|
if (p.content.length > 500) truncated = true;
|
|
460
469
|
}
|
|
461
470
|
} catch {
|
|
462
471
|
truncated = true;
|
|
463
472
|
const cmdMatch = inputPreview.match(/"command"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
464
473
|
const contentMatch = inputPreview.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
465
|
-
if (cmdMatch)
|
|
466
|
-
|
|
474
|
+
if (cmdMatch) {
|
|
475
|
+
preview = `$ ${cmdMatch[1]}`;
|
|
476
|
+
lang = "bash";
|
|
477
|
+
} else if (contentMatch) preview = contentMatch[1].slice(0, 3e3);
|
|
467
478
|
}
|
|
468
479
|
const truncNote = truncated ? "\n\n<i>...truncated</i>" : "";
|
|
469
480
|
const previewSlice = preview.slice(0, 3e3);
|
|
470
481
|
const isShort = !previewSlice.includes("\n") && previewSlice.length < 100;
|
|
471
|
-
const
|
|
482
|
+
const langAttr = lang ? ` class="language-${lang}"` : "";
|
|
483
|
+
const previewHtml = kind === "prose" ? this.mdToHtml(previewSlice) : isShort ? `<code>${this.escHtml(previewSlice)}</code>` : `<pre><code${langAttr}>${this.escHtml(previewSlice)}</code></pre>`;
|
|
472
484
|
let displayTool = toolName;
|
|
473
485
|
if ((toolName.endsWith("__notify") || toolName === "notify") && inputPreview) {
|
|
474
486
|
try {
|
|
@@ -568,6 +580,40 @@ ${label}`);
|
|
|
568
580
|
escHtml(s) {
|
|
569
581
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
570
582
|
}
|
|
583
|
+
static LANG_BY_EXT = {
|
|
584
|
+
js: "javascript",
|
|
585
|
+
mjs: "javascript",
|
|
586
|
+
cjs: "javascript",
|
|
587
|
+
jsx: "javascript",
|
|
588
|
+
ts: "typescript",
|
|
589
|
+
tsx: "typescript",
|
|
590
|
+
py: "python",
|
|
591
|
+
rb: "ruby",
|
|
592
|
+
go: "go",
|
|
593
|
+
java: "java",
|
|
594
|
+
kt: "kotlin",
|
|
595
|
+
rs: "rust",
|
|
596
|
+
php: "php",
|
|
597
|
+
cs: "csharp",
|
|
598
|
+
sh: "bash",
|
|
599
|
+
bash: "bash",
|
|
600
|
+
zsh: "bash",
|
|
601
|
+
ps1: "powershell",
|
|
602
|
+
json: "json",
|
|
603
|
+
yml: "yaml",
|
|
604
|
+
yaml: "yaml",
|
|
605
|
+
xml: "xml",
|
|
606
|
+
sql: "sql",
|
|
607
|
+
md: "markdown",
|
|
608
|
+
html: "html",
|
|
609
|
+
vue: "html",
|
|
610
|
+
css: "css",
|
|
611
|
+
scss: "scss"
|
|
612
|
+
};
|
|
613
|
+
langOf(path5) {
|
|
614
|
+
const m = /\.([a-z0-9]+)$/i.exec(path5 || "");
|
|
615
|
+
return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
|
|
616
|
+
}
|
|
571
617
|
/** Convert markdown to Telegram HTML (escape first, then apply formatting) */
|
|
572
618
|
mdToHtml(s) {
|
|
573
619
|
let text = s.replace(
|
|
@@ -677,6 +723,54 @@ function setupMcpConfig(targetDir) {
|
|
|
677
723
|
return mcpPath;
|
|
678
724
|
}
|
|
679
725
|
|
|
726
|
+
// src/shared/crash-guard.ts
|
|
727
|
+
import fs3 from "fs";
|
|
728
|
+
var BURST_WINDOW_MS = 6e4;
|
|
729
|
+
var BURST_LIMIT = 10;
|
|
730
|
+
function appendToLog(text) {
|
|
731
|
+
try {
|
|
732
|
+
fs3.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
733
|
+
fs3.appendFileSync(LOG_FILE, text, "utf-8");
|
|
734
|
+
} catch {
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function describe(err) {
|
|
738
|
+
if (err instanceof Error) return err.stack ?? `${err.name}: ${err.message}`;
|
|
739
|
+
try {
|
|
740
|
+
return JSON.stringify(err);
|
|
741
|
+
} catch {
|
|
742
|
+
return String(err);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function installCrashGuard(context) {
|
|
746
|
+
let recent = [];
|
|
747
|
+
const handle = (kind, err) => {
|
|
748
|
+
const stack = describe(err);
|
|
749
|
+
appendToLog(`
|
|
750
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} (${context})
|
|
751
|
+
${stack}
|
|
752
|
+
`);
|
|
753
|
+
logger.error(`${kind} caught by crash guard: ${stack}`);
|
|
754
|
+
const now = Date.now();
|
|
755
|
+
recent = recent.filter((t) => now - t < BURST_WINDOW_MS);
|
|
756
|
+
recent.push(now);
|
|
757
|
+
if (recent.length > BURST_LIMIT) {
|
|
758
|
+
const msg = `${recent.length} unhandled errors within ${BURST_WINDOW_MS / 1e3}s \u2014 exiting`;
|
|
759
|
+
appendToLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
760
|
+
`);
|
|
761
|
+
logger.error(msg);
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
process.on("uncaughtException", (err) => handle("uncaughtException", err));
|
|
766
|
+
process.on("unhandledRejection", (reason) => handle("unhandledRejection", reason));
|
|
767
|
+
}
|
|
768
|
+
function logStartup(context) {
|
|
769
|
+
appendToLog(`
|
|
770
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] ${context} started (pid ${process.pid})
|
|
771
|
+
`);
|
|
772
|
+
}
|
|
773
|
+
|
|
680
774
|
// src/hub/server.ts
|
|
681
775
|
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
682
776
|
var HubServer = class {
|
|
@@ -720,9 +814,12 @@ var HubServer = class {
|
|
|
720
814
|
this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
|
|
721
815
|
this.wssChannel = new WebSocketServer({ noServer: true });
|
|
722
816
|
this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
|
|
817
|
+
this.wssChannel.on("error", (err) => logger.warn(`Channel WebSocket server error: ${err.message}`));
|
|
723
818
|
this.wssDashboard = new WebSocketServer({ noServer: true });
|
|
724
819
|
this.wssDashboard.on("connection", (ws) => this.handleDashboardConnection(ws));
|
|
820
|
+
this.wssDashboard.on("error", (err) => logger.warn(`Dashboard WebSocket server error: ${err.message}`));
|
|
725
821
|
this.httpServer.on("upgrade", (req, socket, head) => {
|
|
822
|
+
socket.on("error", (err) => logger.debug(`Upgrade socket error: ${err.message}`));
|
|
726
823
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
727
824
|
const pathname = url.pathname;
|
|
728
825
|
if (this.token && !this.isLocalRequest(req)) {
|
|
@@ -749,8 +846,11 @@ var HubServer = class {
|
|
|
749
846
|
this.cleanupUploads();
|
|
750
847
|
this.startHeartbeat();
|
|
751
848
|
return new Promise((resolve, reject) => {
|
|
752
|
-
|
|
849
|
+
const onStartupError = (err) => reject(err);
|
|
850
|
+
this.httpServer.once("error", onStartupError);
|
|
753
851
|
this.httpServer.listen(this.port, this.host, () => {
|
|
852
|
+
this.httpServer.removeListener("error", onStartupError);
|
|
853
|
+
this.httpServer.on("error", (err) => logger.error(`HTTP server error: ${err.message}`));
|
|
754
854
|
const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
|
|
755
855
|
logger.info(`Hub server listening on http://${displayHost}:${this.port}`);
|
|
756
856
|
resolve();
|
|
@@ -871,8 +971,8 @@ var HubServer = class {
|
|
|
871
971
|
];
|
|
872
972
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
873
973
|
for (const candidate of candidates) {
|
|
874
|
-
if (
|
|
875
|
-
const html =
|
|
974
|
+
if (fs4.existsSync(candidate)) {
|
|
975
|
+
const html = fs4.readFileSync(candidate, "utf-8");
|
|
876
976
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
877
977
|
res.end(html);
|
|
878
978
|
return;
|
|
@@ -919,6 +1019,10 @@ var HubServer = class {
|
|
|
919
1019
|
handleChannelConnection(ws, req) {
|
|
920
1020
|
const isLocal = this.isLocalRequest(req);
|
|
921
1021
|
logger.info(`Channel server connected (local: ${isLocal})`);
|
|
1022
|
+
ws.on("error", (err) => {
|
|
1023
|
+
logger.warn(`Channel WebSocket error: ${err.message}`);
|
|
1024
|
+
ws.terminate();
|
|
1025
|
+
});
|
|
922
1026
|
ws.on("pong", () => {
|
|
923
1027
|
for (const [sessionId, sock] of this.channelSockets) {
|
|
924
1028
|
if (sock === ws) {
|
|
@@ -1028,6 +1132,10 @@ var HubServer = class {
|
|
|
1028
1132
|
handleDashboardConnection(ws) {
|
|
1029
1133
|
this.dashboardSockets.add(ws);
|
|
1030
1134
|
logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);
|
|
1135
|
+
ws.on("error", (err) => {
|
|
1136
|
+
logger.warn(`Dashboard WebSocket error: ${err.message}`);
|
|
1137
|
+
ws.terminate();
|
|
1138
|
+
});
|
|
1031
1139
|
const sessionsMsg = {
|
|
1032
1140
|
type: "sessions_list",
|
|
1033
1141
|
sessions: this.sessions.getAll()
|
|
@@ -1084,11 +1192,11 @@ var HubServer = class {
|
|
|
1084
1192
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
1085
1193
|
return;
|
|
1086
1194
|
}
|
|
1087
|
-
|
|
1195
|
+
fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
1088
1196
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
1089
1197
|
const filename = `${randomUUID3()}.${ext}`;
|
|
1090
1198
|
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
1091
|
-
|
|
1199
|
+
fs4.writeFileSync(filePath, buffer);
|
|
1092
1200
|
const forwardMsg = {
|
|
1093
1201
|
type: "image_to_session",
|
|
1094
1202
|
sessionId,
|
|
@@ -1101,7 +1209,7 @@ var HubServer = class {
|
|
|
1101
1209
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
1102
1210
|
setTimeout(() => {
|
|
1103
1211
|
try {
|
|
1104
|
-
|
|
1212
|
+
fs4.unlinkSync(filePath);
|
|
1105
1213
|
} catch {
|
|
1106
1214
|
}
|
|
1107
1215
|
}, 5 * 60 * 1e3);
|
|
@@ -1271,11 +1379,11 @@ var HubServer = class {
|
|
|
1271
1379
|
}
|
|
1272
1380
|
cleanupUploads() {
|
|
1273
1381
|
try {
|
|
1274
|
-
if (!
|
|
1275
|
-
const files =
|
|
1382
|
+
if (!fs4.existsSync(UPLOADS_DIR)) return;
|
|
1383
|
+
const files = fs4.readdirSync(UPLOADS_DIR);
|
|
1276
1384
|
for (const file of files) {
|
|
1277
1385
|
try {
|
|
1278
|
-
|
|
1386
|
+
fs4.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
1279
1387
|
} catch {
|
|
1280
1388
|
}
|
|
1281
1389
|
}
|
|
@@ -1319,6 +1427,8 @@ var HubServer = class {
|
|
|
1319
1427
|
}
|
|
1320
1428
|
};
|
|
1321
1429
|
if (process.argv[1] && (process.argv[1].endsWith("hub/server.js") || process.argv[1].endsWith("hub/server.ts"))) {
|
|
1430
|
+
installCrashGuard("hub daemon");
|
|
1431
|
+
logStartup("Hub daemon");
|
|
1322
1432
|
const config = loadConfig();
|
|
1323
1433
|
const hub = new HubServer(config);
|
|
1324
1434
|
hub.start().catch((err) => {
|