@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 +108 -31
- package/dist/cli.js.map +1 -1
- package/dist/hub/server.js +74 -10
- package/dist/hub/server.js.map +1 -1
- package/dist/index.js +74 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
590
|
+
fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
535
591
|
const filename = `${randomUUID2()}.${ext}`;
|
|
536
592
|
const filePath = path3.join(UPLOADS_DIR, filename);
|
|
537
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
940
|
-
const html =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
1340
|
-
const files =
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 (
|
|
1803
|
-
const pid = parseInt(
|
|
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
|
-
|
|
1882
|
+
fs5.unlinkSync(PID_FILE);
|
|
1809
1883
|
}
|
|
1810
1884
|
if (daemon) {
|
|
1811
1885
|
ensureConfigDir();
|
|
1812
|
-
const logFd =
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
1869
|
-
const pid = parseInt(
|
|
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 (
|
|
2040
|
+
if (fs5.existsSync(mcpPath)) {
|
|
1964
2041
|
try {
|
|
1965
|
-
mcpConfig = JSON.parse(
|
|
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
|
-
|
|
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") {
|