@delt/claude-alarm 0.8.0 → 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/cli.js CHANGED
@@ -134,6 +134,62 @@ var init_logger = __esm({
134
134
  }
135
135
  });
136
136
 
137
+ // src/shared/crash-guard.ts
138
+ import fs2 from "fs";
139
+ function appendToLog(text) {
140
+ try {
141
+ fs2.mkdirSync(CONFIG_DIR, { recursive: true });
142
+ fs2.appendFileSync(LOG_FILE, text, "utf-8");
143
+ } catch {
144
+ }
145
+ }
146
+ function describe(err) {
147
+ if (err instanceof Error) return err.stack ?? `${err.name}: ${err.message}`;
148
+ try {
149
+ return JSON.stringify(err);
150
+ } catch {
151
+ return String(err);
152
+ }
153
+ }
154
+ function installCrashGuard(context) {
155
+ let recent = [];
156
+ const handle = (kind, err) => {
157
+ const stack = describe(err);
158
+ appendToLog(`
159
+ [${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} (${context})
160
+ ${stack}
161
+ `);
162
+ logger.error(`${kind} caught by crash guard: ${stack}`);
163
+ const now = Date.now();
164
+ recent = recent.filter((t) => now - t < BURST_WINDOW_MS);
165
+ recent.push(now);
166
+ if (recent.length > BURST_LIMIT) {
167
+ const msg = `${recent.length} unhandled errors within ${BURST_WINDOW_MS / 1e3}s \u2014 exiting`;
168
+ appendToLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
169
+ `);
170
+ logger.error(msg);
171
+ process.exit(1);
172
+ }
173
+ };
174
+ process.on("uncaughtException", (err) => handle("uncaughtException", err));
175
+ process.on("unhandledRejection", (reason) => handle("unhandledRejection", reason));
176
+ }
177
+ function logStartup(context) {
178
+ appendToLog(`
179
+ [${(/* @__PURE__ */ new Date()).toISOString()}] ${context} started (pid ${process.pid})
180
+ `);
181
+ }
182
+ var BURST_WINDOW_MS, BURST_LIMIT;
183
+ var init_crash_guard = __esm({
184
+ "src/shared/crash-guard.ts"() {
185
+ "use strict";
186
+ init_constants();
187
+ init_logger();
188
+ BURST_WINDOW_MS = 6e4;
189
+ BURST_LIMIT = 10;
190
+ }
191
+ });
192
+
137
193
  // src/hub/session-manager.ts
138
194
  var SessionManager;
139
195
  var init_session_manager = __esm({
@@ -312,7 +368,7 @@ var init_notifier = __esm({
312
368
  });
313
369
 
314
370
  // src/hub/telegram.ts
315
- import fs2 from "fs";
371
+ import fs3 from "fs";
316
372
  import path3 from "path";
317
373
  import { randomUUID as randomUUID2 } from "crypto";
318
374
  var TELEGRAM_API, TelegramBot;
@@ -531,17 +587,17 @@ ${this.mdToHtml(message)}`;
531
587
  const buffer = Buffer.from(await imgRes.arrayBuffer());
532
588
  const ext = fileData.result.file_path.split(".").pop() || "jpg";
533
589
  const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
534
- fs2.mkdirSync(UPLOADS_DIR, { recursive: true });
590
+ fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
535
591
  const filename = `${randomUUID2()}.${ext}`;
536
592
  const filePath = path3.join(UPLOADS_DIR, filename);
537
- fs2.writeFileSync(filePath, buffer);
593
+ fs3.writeFileSync(filePath, buffer);
538
594
  logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
539
595
  if (this.onImageToSession) {
540
596
  this.onImageToSession(sessionId2, filePath, mimeType, caption);
541
597
  }
542
598
  setTimeout(() => {
543
599
  try {
544
- fs2.unlinkSync(filePath);
600
+ fs3.unlinkSync(filePath);
545
601
  } catch {
546
602
  }
547
603
  }, 5 * 60 * 1e3);
@@ -728,7 +784,7 @@ __export(server_exports, {
728
784
  HubServer: () => HubServer
729
785
  });
730
786
  import http from "http";
731
- import fs3 from "fs";
787
+ import fs4 from "fs";
732
788
  import path4 from "path";
733
789
  import { fileURLToPath } from "url";
734
790
  import { WebSocketServer, WebSocket } from "ws";
@@ -743,6 +799,7 @@ var init_server = __esm({
743
799
  init_notifier();
744
800
  init_telegram();
745
801
  init_config();
802
+ init_crash_guard();
746
803
  __dirname = path4.dirname(fileURLToPath(import.meta.url));
747
804
  HubServer = class {
748
805
  httpServer;
@@ -785,9 +842,12 @@ var init_server = __esm({
785
842
  this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
786
843
  this.wssChannel = new WebSocketServer({ noServer: true });
787
844
  this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
845
+ this.wssChannel.on("error", (err) => logger.warn(`Channel WebSocket server error: ${err.message}`));
788
846
  this.wssDashboard = new WebSocketServer({ noServer: true });
789
847
  this.wssDashboard.on("connection", (ws) => this.handleDashboardConnection(ws));
848
+ this.wssDashboard.on("error", (err) => logger.warn(`Dashboard WebSocket server error: ${err.message}`));
790
849
  this.httpServer.on("upgrade", (req, socket, head) => {
850
+ socket.on("error", (err) => logger.debug(`Upgrade socket error: ${err.message}`));
791
851
  const url = new URL(req.url, `http://${req.headers.host}`);
792
852
  const pathname = url.pathname;
793
853
  if (this.token && !this.isLocalRequest(req)) {
@@ -814,8 +874,11 @@ var init_server = __esm({
814
874
  this.cleanupUploads();
815
875
  this.startHeartbeat();
816
876
  return new Promise((resolve, reject) => {
817
- this.httpServer.on("error", reject);
877
+ const onStartupError = (err) => reject(err);
878
+ this.httpServer.once("error", onStartupError);
818
879
  this.httpServer.listen(this.port, this.host, () => {
880
+ this.httpServer.removeListener("error", onStartupError);
881
+ this.httpServer.on("error", (err) => logger.error(`HTTP server error: ${err.message}`));
819
882
  const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
820
883
  logger.info(`Hub server listening on http://${displayHost}:${this.port}`);
821
884
  resolve();
@@ -936,8 +999,8 @@ var init_server = __esm({
936
999
  ];
937
1000
  logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
938
1001
  for (const candidate of candidates) {
939
- if (fs3.existsSync(candidate)) {
940
- const html = fs3.readFileSync(candidate, "utf-8");
1002
+ if (fs4.existsSync(candidate)) {
1003
+ const html = fs4.readFileSync(candidate, "utf-8");
941
1004
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
942
1005
  res.end(html);
943
1006
  return;
@@ -984,6 +1047,10 @@ var init_server = __esm({
984
1047
  handleChannelConnection(ws, req) {
985
1048
  const isLocal = this.isLocalRequest(req);
986
1049
  logger.info(`Channel server connected (local: ${isLocal})`);
1050
+ ws.on("error", (err) => {
1051
+ logger.warn(`Channel WebSocket error: ${err.message}`);
1052
+ ws.terminate();
1053
+ });
987
1054
  ws.on("pong", () => {
988
1055
  for (const [sessionId2, sock] of this.channelSockets) {
989
1056
  if (sock === ws) {
@@ -1093,6 +1160,10 @@ var init_server = __esm({
1093
1160
  handleDashboardConnection(ws) {
1094
1161
  this.dashboardSockets.add(ws);
1095
1162
  logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);
1163
+ ws.on("error", (err) => {
1164
+ logger.warn(`Dashboard WebSocket error: ${err.message}`);
1165
+ ws.terminate();
1166
+ });
1096
1167
  const sessionsMsg = {
1097
1168
  type: "sessions_list",
1098
1169
  sessions: this.sessions.getAll()
@@ -1149,11 +1220,11 @@ var init_server = __esm({
1149
1220
  logger.warn("Image upload rejected: exceeds 10MB");
1150
1221
  return;
1151
1222
  }
1152
- fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
1223
+ fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
1153
1224
  const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
1154
1225
  const filename = `${randomUUID3()}.${ext}`;
1155
1226
  const filePath = path4.join(UPLOADS_DIR, filename);
1156
- fs3.writeFileSync(filePath, buffer);
1227
+ fs4.writeFileSync(filePath, buffer);
1157
1228
  const forwardMsg = {
1158
1229
  type: "image_to_session",
1159
1230
  sessionId: sessionId2,
@@ -1166,7 +1237,7 @@ var init_server = __esm({
1166
1237
  logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
1167
1238
  setTimeout(() => {
1168
1239
  try {
1169
- fs3.unlinkSync(filePath);
1240
+ fs4.unlinkSync(filePath);
1170
1241
  } catch {
1171
1242
  }
1172
1243
  }, 5 * 60 * 1e3);
@@ -1336,11 +1407,11 @@ var init_server = __esm({
1336
1407
  }
1337
1408
  cleanupUploads() {
1338
1409
  try {
1339
- if (!fs3.existsSync(UPLOADS_DIR)) return;
1340
- const files = fs3.readdirSync(UPLOADS_DIR);
1410
+ if (!fs4.existsSync(UPLOADS_DIR)) return;
1411
+ const files = fs4.readdirSync(UPLOADS_DIR);
1341
1412
  for (const file of files) {
1342
1413
  try {
1343
- fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
1414
+ fs4.unlinkSync(path4.join(UPLOADS_DIR, file));
1344
1415
  } catch {
1345
1416
  }
1346
1417
  }
@@ -1384,6 +1455,8 @@ var init_server = __esm({
1384
1455
  }
1385
1456
  };
1386
1457
  if (process.argv[1] && (process.argv[1].endsWith("hub/server.js") || process.argv[1].endsWith("hub/server.ts"))) {
1458
+ installCrashGuard("hub daemon");
1459
+ logStartup("Hub daemon");
1387
1460
  const config2 = loadConfig();
1388
1461
  const hub = new HubServer(config2);
1389
1462
  hub.start().catch((err) => {
@@ -1749,8 +1822,9 @@ NOTIFICATIONS:
1749
1822
  init_config();
1750
1823
  init_constants();
1751
1824
  init_logger();
1825
+ init_crash_guard();
1752
1826
  import { spawn } from "child_process";
1753
- import fs4 from "fs";
1827
+ import fs5 from "fs";
1754
1828
  import path6 from "path";
1755
1829
  import readline from "readline";
1756
1830
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1776,7 +1850,7 @@ Quick start:
1776
1850
  async function checkForUpdates() {
1777
1851
  try {
1778
1852
  const pkgPath = path6.join(__dirname2, "..", "package.json");
1779
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
1853
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
1780
1854
  const currentVersion = pkg.version;
1781
1855
  const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
1782
1856
  signal: AbortSignal.timeout(3e3)
@@ -1799,17 +1873,17 @@ async function hubStart(daemon) {
1799
1873
  const port = config2.hub.port ?? DEFAULT_HUB_PORT;
1800
1874
  const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
1801
1875
  checkForUpdates();
1802
- if (fs4.existsSync(PID_FILE)) {
1803
- const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
1876
+ if (fs5.existsSync(PID_FILE)) {
1877
+ const pid = parseInt(fs5.readFileSync(PID_FILE, "utf-8").trim(), 10);
1804
1878
  if (isProcessRunning(pid)) {
1805
1879
  console.log(`Hub is already running (PID: ${pid}) on http://${displayHost}:${port}`);
1806
1880
  return;
1807
1881
  }
1808
- fs4.unlinkSync(PID_FILE);
1882
+ fs5.unlinkSync(PID_FILE);
1809
1883
  }
1810
1884
  if (daemon) {
1811
1885
  ensureConfigDir();
1812
- const logFd = fs4.openSync(LOG_FILE, "a");
1886
+ const logFd = fs5.openSync(LOG_FILE, "a");
1813
1887
  const hubScript = path6.join(__dirname2, "hub", "server.js");
1814
1888
  const child = spawn(process.execPath, [hubScript], {
1815
1889
  detached: true,
@@ -1817,7 +1891,7 @@ async function hubStart(daemon) {
1817
1891
  env: { ...process.env }
1818
1892
  });
1819
1893
  if (child.pid) {
1820
- fs4.writeFileSync(PID_FILE, String(child.pid), "utf-8");
1894
+ fs5.writeFileSync(PID_FILE, String(child.pid), "utf-8");
1821
1895
  child.unref();
1822
1896
  console.log(`Hub started as daemon (PID: ${child.pid})`);
1823
1897
  console.log(`Dashboard: http://${displayHost}:${port}`);
@@ -1830,15 +1904,18 @@ async function hubStart(daemon) {
1830
1904
  } else {
1831
1905
  console.log(`Starting hub on http://${displayHost}:${port} (press Ctrl+C to stop)`);
1832
1906
  console.log(`Token: ${config2.hub.token}`);
1907
+ console.log(`Logs: ${LOG_FILE}`);
1908
+ installCrashGuard("hub foreground");
1909
+ logStartup("Hub foreground");
1833
1910
  const { HubServer: HubServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
1834
1911
  const hub = new HubServer2(config2);
1835
1912
  await hub.start();
1836
1913
  ensureConfigDir();
1837
- fs4.writeFileSync(PID_FILE, String(process.pid), "utf-8");
1914
+ fs5.writeFileSync(PID_FILE, String(process.pid), "utf-8");
1838
1915
  const shutdown = async () => {
1839
1916
  console.log("\nShutting down...");
1840
1917
  await hub.stop();
1841
- if (fs4.existsSync(PID_FILE)) fs4.unlinkSync(PID_FILE);
1918
+ if (fs5.existsSync(PID_FILE)) fs5.unlinkSync(PID_FILE);
1842
1919
  process.exit(0);
1843
1920
  };
1844
1921
  process.on("SIGINT", shutdown);
@@ -1846,18 +1923,18 @@ async function hubStart(daemon) {
1846
1923
  }
1847
1924
  }
1848
1925
  function hubStop() {
1849
- if (!fs4.existsSync(PID_FILE)) {
1926
+ if (!fs5.existsSync(PID_FILE)) {
1850
1927
  console.log("Hub is not running (no PID file found)");
1851
1928
  return;
1852
1929
  }
1853
- const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
1930
+ const pid = parseInt(fs5.readFileSync(PID_FILE, "utf-8").trim(), 10);
1854
1931
  try {
1855
1932
  process.kill(pid, "SIGTERM");
1856
1933
  console.log(`Hub stopped (PID: ${pid})`);
1857
1934
  } catch {
1858
1935
  console.log("Hub process not found (may have already stopped)");
1859
1936
  }
1860
- fs4.unlinkSync(PID_FILE);
1937
+ fs5.unlinkSync(PID_FILE);
1861
1938
  }
1862
1939
  async function hubStatus() {
1863
1940
  const config2 = loadConfig();
@@ -1865,8 +1942,8 @@ async function hubStatus() {
1865
1942
  const port = config2.hub.port ?? DEFAULT_HUB_PORT;
1866
1943
  const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
1867
1944
  let pidInfo = "not running";
1868
- if (fs4.existsSync(PID_FILE)) {
1869
- const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
1945
+ if (fs5.existsSync(PID_FILE)) {
1946
+ const pid = parseInt(fs5.readFileSync(PID_FILE, "utf-8").trim(), 10);
1870
1947
  if (isProcessRunning(pid)) {
1871
1948
  pidInfo = `running (PID: ${pid})`;
1872
1949
  } else {
@@ -1960,9 +2037,9 @@ claude-alarm init for "${projectName}"
1960
2037
  }
1961
2038
  const mcpPath = path6.join(dir, ".mcp.json");
1962
2039
  let mcpConfig = {};
1963
- if (fs4.existsSync(mcpPath)) {
2040
+ if (fs5.existsSync(mcpPath)) {
1964
2041
  try {
1965
- mcpConfig = JSON.parse(fs4.readFileSync(mcpPath, "utf-8"));
2042
+ mcpConfig = JSON.parse(fs5.readFileSync(mcpPath, "utf-8"));
1966
2043
  } catch {
1967
2044
  mcpConfig = {};
1968
2045
  }
@@ -1973,7 +2050,7 @@ claude-alarm init for "${projectName}"
1973
2050
  args: ["-y", "@delt/claude-alarm", "serve"],
1974
2051
  env
1975
2052
  };
1976
- fs4.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
2053
+ fs5.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
1977
2054
  console.log(`
1978
2055
  \u2713 Created ${mcpPath}`);
1979
2056
  if (remote.toLowerCase() !== "y") {