@delt/claude-alarm 0.7.1 → 0.8.1

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/hub/server.ts
2
2
  import http from "http";
3
- import fs3 from "fs";
3
+ import fs4 from "fs";
4
4
  import path4 from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { WebSocketServer, WebSocket } from "ws";
@@ -677,6 +677,54 @@ function setupMcpConfig(targetDir) {
677
677
  return mcpPath;
678
678
  }
679
679
 
680
+ // src/shared/crash-guard.ts
681
+ import fs3 from "fs";
682
+ var BURST_WINDOW_MS = 6e4;
683
+ var BURST_LIMIT = 10;
684
+ function appendToLog(text) {
685
+ try {
686
+ fs3.mkdirSync(CONFIG_DIR, { recursive: true });
687
+ fs3.appendFileSync(LOG_FILE, text, "utf-8");
688
+ } catch {
689
+ }
690
+ }
691
+ function describe(err) {
692
+ if (err instanceof Error) return err.stack ?? `${err.name}: ${err.message}`;
693
+ try {
694
+ return JSON.stringify(err);
695
+ } catch {
696
+ return String(err);
697
+ }
698
+ }
699
+ function installCrashGuard(context) {
700
+ let recent = [];
701
+ const handle = (kind, err) => {
702
+ const stack = describe(err);
703
+ appendToLog(`
704
+ [${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} (${context})
705
+ ${stack}
706
+ `);
707
+ logger.error(`${kind} caught by crash guard: ${stack}`);
708
+ const now = Date.now();
709
+ recent = recent.filter((t) => now - t < BURST_WINDOW_MS);
710
+ recent.push(now);
711
+ if (recent.length > BURST_LIMIT) {
712
+ const msg = `${recent.length} unhandled errors within ${BURST_WINDOW_MS / 1e3}s \u2014 exiting`;
713
+ appendToLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
714
+ `);
715
+ logger.error(msg);
716
+ process.exit(1);
717
+ }
718
+ };
719
+ process.on("uncaughtException", (err) => handle("uncaughtException", err));
720
+ process.on("unhandledRejection", (reason) => handle("unhandledRejection", reason));
721
+ }
722
+ function logStartup(context) {
723
+ appendToLog(`
724
+ [${(/* @__PURE__ */ new Date()).toISOString()}] ${context} started (pid ${process.pid})
725
+ `);
726
+ }
727
+
680
728
  // src/hub/server.ts
681
729
  var __dirname = path4.dirname(fileURLToPath(import.meta.url));
682
730
  var HubServer = class {
@@ -720,9 +768,12 @@ var HubServer = class {
720
768
  this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
721
769
  this.wssChannel = new WebSocketServer({ noServer: true });
722
770
  this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
771
+ this.wssChannel.on("error", (err) => logger.warn(`Channel WebSocket server error: ${err.message}`));
723
772
  this.wssDashboard = new WebSocketServer({ noServer: true });
724
773
  this.wssDashboard.on("connection", (ws) => this.handleDashboardConnection(ws));
774
+ this.wssDashboard.on("error", (err) => logger.warn(`Dashboard WebSocket server error: ${err.message}`));
725
775
  this.httpServer.on("upgrade", (req, socket, head) => {
776
+ socket.on("error", (err) => logger.debug(`Upgrade socket error: ${err.message}`));
726
777
  const url = new URL(req.url, `http://${req.headers.host}`);
727
778
  const pathname = url.pathname;
728
779
  if (this.token && !this.isLocalRequest(req)) {
@@ -749,8 +800,11 @@ var HubServer = class {
749
800
  this.cleanupUploads();
750
801
  this.startHeartbeat();
751
802
  return new Promise((resolve, reject) => {
752
- this.httpServer.on("error", reject);
803
+ const onStartupError = (err) => reject(err);
804
+ this.httpServer.once("error", onStartupError);
753
805
  this.httpServer.listen(this.port, this.host, () => {
806
+ this.httpServer.removeListener("error", onStartupError);
807
+ this.httpServer.on("error", (err) => logger.error(`HTTP server error: ${err.message}`));
754
808
  const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
755
809
  logger.info(`Hub server listening on http://${displayHost}:${this.port}`);
756
810
  resolve();
@@ -871,8 +925,8 @@ var HubServer = class {
871
925
  ];
872
926
  logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
873
927
  for (const candidate of candidates) {
874
- if (fs3.existsSync(candidate)) {
875
- const html = fs3.readFileSync(candidate, "utf-8");
928
+ if (fs4.existsSync(candidate)) {
929
+ const html = fs4.readFileSync(candidate, "utf-8");
876
930
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
877
931
  res.end(html);
878
932
  return;
@@ -919,6 +973,10 @@ var HubServer = class {
919
973
  handleChannelConnection(ws, req) {
920
974
  const isLocal = this.isLocalRequest(req);
921
975
  logger.info(`Channel server connected (local: ${isLocal})`);
976
+ ws.on("error", (err) => {
977
+ logger.warn(`Channel WebSocket error: ${err.message}`);
978
+ ws.terminate();
979
+ });
922
980
  ws.on("pong", () => {
923
981
  for (const [sessionId, sock] of this.channelSockets) {
924
982
  if (sock === ws) {
@@ -1028,6 +1086,10 @@ var HubServer = class {
1028
1086
  handleDashboardConnection(ws) {
1029
1087
  this.dashboardSockets.add(ws);
1030
1088
  logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);
1089
+ ws.on("error", (err) => {
1090
+ logger.warn(`Dashboard WebSocket error: ${err.message}`);
1091
+ ws.terminate();
1092
+ });
1031
1093
  const sessionsMsg = {
1032
1094
  type: "sessions_list",
1033
1095
  sessions: this.sessions.getAll()
@@ -1084,11 +1146,11 @@ var HubServer = class {
1084
1146
  logger.warn("Image upload rejected: exceeds 10MB");
1085
1147
  return;
1086
1148
  }
1087
- fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
1149
+ fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
1088
1150
  const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
1089
1151
  const filename = `${randomUUID3()}.${ext}`;
1090
1152
  const filePath = path4.join(UPLOADS_DIR, filename);
1091
- fs3.writeFileSync(filePath, buffer);
1153
+ fs4.writeFileSync(filePath, buffer);
1092
1154
  const forwardMsg = {
1093
1155
  type: "image_to_session",
1094
1156
  sessionId,
@@ -1101,7 +1163,7 @@ var HubServer = class {
1101
1163
  logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
1102
1164
  setTimeout(() => {
1103
1165
  try {
1104
- fs3.unlinkSync(filePath);
1166
+ fs4.unlinkSync(filePath);
1105
1167
  } catch {
1106
1168
  }
1107
1169
  }, 5 * 60 * 1e3);
@@ -1271,11 +1333,11 @@ var HubServer = class {
1271
1333
  }
1272
1334
  cleanupUploads() {
1273
1335
  try {
1274
- if (!fs3.existsSync(UPLOADS_DIR)) return;
1275
- const files = fs3.readdirSync(UPLOADS_DIR);
1336
+ if (!fs4.existsSync(UPLOADS_DIR)) return;
1337
+ const files = fs4.readdirSync(UPLOADS_DIR);
1276
1338
  for (const file of files) {
1277
1339
  try {
1278
- fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
1340
+ fs4.unlinkSync(path4.join(UPLOADS_DIR, file));
1279
1341
  } catch {
1280
1342
  }
1281
1343
  }
@@ -1319,6 +1381,8 @@ var HubServer = class {
1319
1381
  }
1320
1382
  };
1321
1383
  if (process.argv[1] && (process.argv[1].endsWith("hub/server.js") || process.argv[1].endsWith("hub/server.ts"))) {
1384
+ installCrashGuard("hub daemon");
1385
+ logStartup("Hub daemon");
1322
1386
  const config = loadConfig();
1323
1387
  const hub = new HubServer(config);
1324
1388
  hub.start().catch((err) => {