@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/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;
|
|
@@ -322,7 +378,7 @@ var init_telegram = __esm({
|
|
|
322
378
|
init_logger();
|
|
323
379
|
init_constants();
|
|
324
380
|
TELEGRAM_API = "https://api.telegram.org/bot";
|
|
325
|
-
TelegramBot = class {
|
|
381
|
+
TelegramBot = class _TelegramBot {
|
|
326
382
|
config;
|
|
327
383
|
offset = 0;
|
|
328
384
|
polling = false;
|
|
@@ -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);
|
|
@@ -554,33 +610,45 @@ ${this.mdToHtml(message)}`;
|
|
|
554
610
|
}
|
|
555
611
|
/** Send a permission request with inline buttons */
|
|
556
612
|
async sendPermissionRequest(sessionId2, sessionLabel, requestId, toolName, description, inputPreview) {
|
|
613
|
+
const isNotify = toolName.endsWith("__notify") || toolName === "notify";
|
|
557
614
|
let preview = inputPreview;
|
|
615
|
+
let kind = "code";
|
|
616
|
+
let lang = "";
|
|
558
617
|
let truncated = false;
|
|
559
618
|
try {
|
|
560
619
|
const p = JSON.parse(inputPreview);
|
|
561
|
-
if (p.command)
|
|
562
|
-
|
|
563
|
-
|
|
620
|
+
if (p.command) {
|
|
621
|
+
preview = `$ ${p.command}`;
|
|
622
|
+
lang = "bash";
|
|
623
|
+
} else if (p.title && p.message) {
|
|
624
|
+
preview = p.message;
|
|
625
|
+
kind = "prose";
|
|
626
|
+
} else if (p.file_path) {
|
|
564
627
|
preview = p.file_path;
|
|
628
|
+
lang = this.langOf(p.file_path);
|
|
565
629
|
if (p.content) {
|
|
566
630
|
preview += "\n" + p.content.slice(0, 3e3);
|
|
567
631
|
if (p.content.length > 500) truncated = true;
|
|
568
632
|
}
|
|
569
633
|
} else if (p.content && typeof p.content === "string") {
|
|
570
634
|
preview = p.content.slice(0, 3e3);
|
|
635
|
+
if (isNotify) kind = "prose";
|
|
571
636
|
if (p.content.length > 500) truncated = true;
|
|
572
637
|
}
|
|
573
638
|
} catch {
|
|
574
639
|
truncated = true;
|
|
575
640
|
const cmdMatch = inputPreview.match(/"command"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
576
641
|
const contentMatch = inputPreview.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
577
|
-
if (cmdMatch)
|
|
578
|
-
|
|
642
|
+
if (cmdMatch) {
|
|
643
|
+
preview = `$ ${cmdMatch[1]}`;
|
|
644
|
+
lang = "bash";
|
|
645
|
+
} else if (contentMatch) preview = contentMatch[1].slice(0, 3e3);
|
|
579
646
|
}
|
|
580
647
|
const truncNote = truncated ? "\n\n<i>...truncated</i>" : "";
|
|
581
648
|
const previewSlice = preview.slice(0, 3e3);
|
|
582
649
|
const isShort = !previewSlice.includes("\n") && previewSlice.length < 100;
|
|
583
|
-
const
|
|
650
|
+
const langAttr = lang ? ` class="language-${lang}"` : "";
|
|
651
|
+
const previewHtml = kind === "prose" ? this.mdToHtml(previewSlice) : isShort ? `<code>${this.escHtml(previewSlice)}</code>` : `<pre><code${langAttr}>${this.escHtml(previewSlice)}</code></pre>`;
|
|
584
652
|
let displayTool = toolName;
|
|
585
653
|
if ((toolName.endsWith("__notify") || toolName === "notify") && inputPreview) {
|
|
586
654
|
try {
|
|
@@ -680,6 +748,40 @@ ${label}`);
|
|
|
680
748
|
escHtml(s) {
|
|
681
749
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
682
750
|
}
|
|
751
|
+
static LANG_BY_EXT = {
|
|
752
|
+
js: "javascript",
|
|
753
|
+
mjs: "javascript",
|
|
754
|
+
cjs: "javascript",
|
|
755
|
+
jsx: "javascript",
|
|
756
|
+
ts: "typescript",
|
|
757
|
+
tsx: "typescript",
|
|
758
|
+
py: "python",
|
|
759
|
+
rb: "ruby",
|
|
760
|
+
go: "go",
|
|
761
|
+
java: "java",
|
|
762
|
+
kt: "kotlin",
|
|
763
|
+
rs: "rust",
|
|
764
|
+
php: "php",
|
|
765
|
+
cs: "csharp",
|
|
766
|
+
sh: "bash",
|
|
767
|
+
bash: "bash",
|
|
768
|
+
zsh: "bash",
|
|
769
|
+
ps1: "powershell",
|
|
770
|
+
json: "json",
|
|
771
|
+
yml: "yaml",
|
|
772
|
+
yaml: "yaml",
|
|
773
|
+
xml: "xml",
|
|
774
|
+
sql: "sql",
|
|
775
|
+
md: "markdown",
|
|
776
|
+
html: "html",
|
|
777
|
+
vue: "html",
|
|
778
|
+
css: "css",
|
|
779
|
+
scss: "scss"
|
|
780
|
+
};
|
|
781
|
+
langOf(path7) {
|
|
782
|
+
const m = /\.([a-z0-9]+)$/i.exec(path7 || "");
|
|
783
|
+
return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
|
|
784
|
+
}
|
|
683
785
|
/** Convert markdown to Telegram HTML (escape first, then apply formatting) */
|
|
684
786
|
mdToHtml(s) {
|
|
685
787
|
let text = s.replace(
|
|
@@ -728,7 +830,7 @@ __export(server_exports, {
|
|
|
728
830
|
HubServer: () => HubServer
|
|
729
831
|
});
|
|
730
832
|
import http from "http";
|
|
731
|
-
import
|
|
833
|
+
import fs4 from "fs";
|
|
732
834
|
import path4 from "path";
|
|
733
835
|
import { fileURLToPath } from "url";
|
|
734
836
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -743,6 +845,7 @@ var init_server = __esm({
|
|
|
743
845
|
init_notifier();
|
|
744
846
|
init_telegram();
|
|
745
847
|
init_config();
|
|
848
|
+
init_crash_guard();
|
|
746
849
|
__dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
747
850
|
HubServer = class {
|
|
748
851
|
httpServer;
|
|
@@ -785,9 +888,12 @@ var init_server = __esm({
|
|
|
785
888
|
this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
|
|
786
889
|
this.wssChannel = new WebSocketServer({ noServer: true });
|
|
787
890
|
this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
|
|
891
|
+
this.wssChannel.on("error", (err) => logger.warn(`Channel WebSocket server error: ${err.message}`));
|
|
788
892
|
this.wssDashboard = new WebSocketServer({ noServer: true });
|
|
789
893
|
this.wssDashboard.on("connection", (ws) => this.handleDashboardConnection(ws));
|
|
894
|
+
this.wssDashboard.on("error", (err) => logger.warn(`Dashboard WebSocket server error: ${err.message}`));
|
|
790
895
|
this.httpServer.on("upgrade", (req, socket, head) => {
|
|
896
|
+
socket.on("error", (err) => logger.debug(`Upgrade socket error: ${err.message}`));
|
|
791
897
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
792
898
|
const pathname = url.pathname;
|
|
793
899
|
if (this.token && !this.isLocalRequest(req)) {
|
|
@@ -814,8 +920,11 @@ var init_server = __esm({
|
|
|
814
920
|
this.cleanupUploads();
|
|
815
921
|
this.startHeartbeat();
|
|
816
922
|
return new Promise((resolve, reject) => {
|
|
817
|
-
|
|
923
|
+
const onStartupError = (err) => reject(err);
|
|
924
|
+
this.httpServer.once("error", onStartupError);
|
|
818
925
|
this.httpServer.listen(this.port, this.host, () => {
|
|
926
|
+
this.httpServer.removeListener("error", onStartupError);
|
|
927
|
+
this.httpServer.on("error", (err) => logger.error(`HTTP server error: ${err.message}`));
|
|
819
928
|
const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
|
|
820
929
|
logger.info(`Hub server listening on http://${displayHost}:${this.port}`);
|
|
821
930
|
resolve();
|
|
@@ -936,8 +1045,8 @@ var init_server = __esm({
|
|
|
936
1045
|
];
|
|
937
1046
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
938
1047
|
for (const candidate of candidates) {
|
|
939
|
-
if (
|
|
940
|
-
const html =
|
|
1048
|
+
if (fs4.existsSync(candidate)) {
|
|
1049
|
+
const html = fs4.readFileSync(candidate, "utf-8");
|
|
941
1050
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
942
1051
|
res.end(html);
|
|
943
1052
|
return;
|
|
@@ -984,6 +1093,10 @@ var init_server = __esm({
|
|
|
984
1093
|
handleChannelConnection(ws, req) {
|
|
985
1094
|
const isLocal = this.isLocalRequest(req);
|
|
986
1095
|
logger.info(`Channel server connected (local: ${isLocal})`);
|
|
1096
|
+
ws.on("error", (err) => {
|
|
1097
|
+
logger.warn(`Channel WebSocket error: ${err.message}`);
|
|
1098
|
+
ws.terminate();
|
|
1099
|
+
});
|
|
987
1100
|
ws.on("pong", () => {
|
|
988
1101
|
for (const [sessionId2, sock] of this.channelSockets) {
|
|
989
1102
|
if (sock === ws) {
|
|
@@ -1093,6 +1206,10 @@ var init_server = __esm({
|
|
|
1093
1206
|
handleDashboardConnection(ws) {
|
|
1094
1207
|
this.dashboardSockets.add(ws);
|
|
1095
1208
|
logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);
|
|
1209
|
+
ws.on("error", (err) => {
|
|
1210
|
+
logger.warn(`Dashboard WebSocket error: ${err.message}`);
|
|
1211
|
+
ws.terminate();
|
|
1212
|
+
});
|
|
1096
1213
|
const sessionsMsg = {
|
|
1097
1214
|
type: "sessions_list",
|
|
1098
1215
|
sessions: this.sessions.getAll()
|
|
@@ -1149,11 +1266,11 @@ var init_server = __esm({
|
|
|
1149
1266
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
1150
1267
|
return;
|
|
1151
1268
|
}
|
|
1152
|
-
|
|
1269
|
+
fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
1153
1270
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
1154
1271
|
const filename = `${randomUUID3()}.${ext}`;
|
|
1155
1272
|
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
1156
|
-
|
|
1273
|
+
fs4.writeFileSync(filePath, buffer);
|
|
1157
1274
|
const forwardMsg = {
|
|
1158
1275
|
type: "image_to_session",
|
|
1159
1276
|
sessionId: sessionId2,
|
|
@@ -1166,7 +1283,7 @@ var init_server = __esm({
|
|
|
1166
1283
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
1167
1284
|
setTimeout(() => {
|
|
1168
1285
|
try {
|
|
1169
|
-
|
|
1286
|
+
fs4.unlinkSync(filePath);
|
|
1170
1287
|
} catch {
|
|
1171
1288
|
}
|
|
1172
1289
|
}, 5 * 60 * 1e3);
|
|
@@ -1336,11 +1453,11 @@ var init_server = __esm({
|
|
|
1336
1453
|
}
|
|
1337
1454
|
cleanupUploads() {
|
|
1338
1455
|
try {
|
|
1339
|
-
if (!
|
|
1340
|
-
const files =
|
|
1456
|
+
if (!fs4.existsSync(UPLOADS_DIR)) return;
|
|
1457
|
+
const files = fs4.readdirSync(UPLOADS_DIR);
|
|
1341
1458
|
for (const file of files) {
|
|
1342
1459
|
try {
|
|
1343
|
-
|
|
1460
|
+
fs4.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
1344
1461
|
} catch {
|
|
1345
1462
|
}
|
|
1346
1463
|
}
|
|
@@ -1384,6 +1501,8 @@ var init_server = __esm({
|
|
|
1384
1501
|
}
|
|
1385
1502
|
};
|
|
1386
1503
|
if (process.argv[1] && (process.argv[1].endsWith("hub/server.js") || process.argv[1].endsWith("hub/server.ts"))) {
|
|
1504
|
+
installCrashGuard("hub daemon");
|
|
1505
|
+
logStartup("Hub daemon");
|
|
1387
1506
|
const config2 = loadConfig();
|
|
1388
1507
|
const hub = new HubServer(config2);
|
|
1389
1508
|
hub.start().catch((err) => {
|
|
@@ -1749,8 +1868,9 @@ NOTIFICATIONS:
|
|
|
1749
1868
|
init_config();
|
|
1750
1869
|
init_constants();
|
|
1751
1870
|
init_logger();
|
|
1871
|
+
init_crash_guard();
|
|
1752
1872
|
import { spawn } from "child_process";
|
|
1753
|
-
import
|
|
1873
|
+
import fs5 from "fs";
|
|
1754
1874
|
import path6 from "path";
|
|
1755
1875
|
import readline from "readline";
|
|
1756
1876
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -1776,7 +1896,7 @@ Quick start:
|
|
|
1776
1896
|
async function checkForUpdates() {
|
|
1777
1897
|
try {
|
|
1778
1898
|
const pkgPath = path6.join(__dirname2, "..", "package.json");
|
|
1779
|
-
const pkg = JSON.parse(
|
|
1899
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
1780
1900
|
const currentVersion = pkg.version;
|
|
1781
1901
|
const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
|
|
1782
1902
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -1799,17 +1919,17 @@ async function hubStart(daemon) {
|
|
|
1799
1919
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
1800
1920
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1801
1921
|
checkForUpdates();
|
|
1802
|
-
if (
|
|
1803
|
-
const pid = parseInt(
|
|
1922
|
+
if (fs5.existsSync(PID_FILE)) {
|
|
1923
|
+
const pid = parseInt(fs5.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1804
1924
|
if (isProcessRunning(pid)) {
|
|
1805
1925
|
console.log(`Hub is already running (PID: ${pid}) on http://${displayHost}:${port}`);
|
|
1806
1926
|
return;
|
|
1807
1927
|
}
|
|
1808
|
-
|
|
1928
|
+
fs5.unlinkSync(PID_FILE);
|
|
1809
1929
|
}
|
|
1810
1930
|
if (daemon) {
|
|
1811
1931
|
ensureConfigDir();
|
|
1812
|
-
const logFd =
|
|
1932
|
+
const logFd = fs5.openSync(LOG_FILE, "a");
|
|
1813
1933
|
const hubScript = path6.join(__dirname2, "hub", "server.js");
|
|
1814
1934
|
const child = spawn(process.execPath, [hubScript], {
|
|
1815
1935
|
detached: true,
|
|
@@ -1817,7 +1937,7 @@ async function hubStart(daemon) {
|
|
|
1817
1937
|
env: { ...process.env }
|
|
1818
1938
|
});
|
|
1819
1939
|
if (child.pid) {
|
|
1820
|
-
|
|
1940
|
+
fs5.writeFileSync(PID_FILE, String(child.pid), "utf-8");
|
|
1821
1941
|
child.unref();
|
|
1822
1942
|
console.log(`Hub started as daemon (PID: ${child.pid})`);
|
|
1823
1943
|
console.log(`Dashboard: http://${displayHost}:${port}`);
|
|
@@ -1830,15 +1950,18 @@ async function hubStart(daemon) {
|
|
|
1830
1950
|
} else {
|
|
1831
1951
|
console.log(`Starting hub on http://${displayHost}:${port} (press Ctrl+C to stop)`);
|
|
1832
1952
|
console.log(`Token: ${config2.hub.token}`);
|
|
1953
|
+
console.log(`Logs: ${LOG_FILE}`);
|
|
1954
|
+
installCrashGuard("hub foreground");
|
|
1955
|
+
logStartup("Hub foreground");
|
|
1833
1956
|
const { HubServer: HubServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
1834
1957
|
const hub = new HubServer2(config2);
|
|
1835
1958
|
await hub.start();
|
|
1836
1959
|
ensureConfigDir();
|
|
1837
|
-
|
|
1960
|
+
fs5.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
1838
1961
|
const shutdown = async () => {
|
|
1839
1962
|
console.log("\nShutting down...");
|
|
1840
1963
|
await hub.stop();
|
|
1841
|
-
if (
|
|
1964
|
+
if (fs5.existsSync(PID_FILE)) fs5.unlinkSync(PID_FILE);
|
|
1842
1965
|
process.exit(0);
|
|
1843
1966
|
};
|
|
1844
1967
|
process.on("SIGINT", shutdown);
|
|
@@ -1846,18 +1969,18 @@ async function hubStart(daemon) {
|
|
|
1846
1969
|
}
|
|
1847
1970
|
}
|
|
1848
1971
|
function hubStop() {
|
|
1849
|
-
if (!
|
|
1972
|
+
if (!fs5.existsSync(PID_FILE)) {
|
|
1850
1973
|
console.log("Hub is not running (no PID file found)");
|
|
1851
1974
|
return;
|
|
1852
1975
|
}
|
|
1853
|
-
const pid = parseInt(
|
|
1976
|
+
const pid = parseInt(fs5.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1854
1977
|
try {
|
|
1855
1978
|
process.kill(pid, "SIGTERM");
|
|
1856
1979
|
console.log(`Hub stopped (PID: ${pid})`);
|
|
1857
1980
|
} catch {
|
|
1858
1981
|
console.log("Hub process not found (may have already stopped)");
|
|
1859
1982
|
}
|
|
1860
|
-
|
|
1983
|
+
fs5.unlinkSync(PID_FILE);
|
|
1861
1984
|
}
|
|
1862
1985
|
async function hubStatus() {
|
|
1863
1986
|
const config2 = loadConfig();
|
|
@@ -1865,8 +1988,8 @@ async function hubStatus() {
|
|
|
1865
1988
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
1866
1989
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1867
1990
|
let pidInfo = "not running";
|
|
1868
|
-
if (
|
|
1869
|
-
const pid = parseInt(
|
|
1991
|
+
if (fs5.existsSync(PID_FILE)) {
|
|
1992
|
+
const pid = parseInt(fs5.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1870
1993
|
if (isProcessRunning(pid)) {
|
|
1871
1994
|
pidInfo = `running (PID: ${pid})`;
|
|
1872
1995
|
} else {
|
|
@@ -1960,9 +2083,9 @@ claude-alarm init for "${projectName}"
|
|
|
1960
2083
|
}
|
|
1961
2084
|
const mcpPath = path6.join(dir, ".mcp.json");
|
|
1962
2085
|
let mcpConfig = {};
|
|
1963
|
-
if (
|
|
2086
|
+
if (fs5.existsSync(mcpPath)) {
|
|
1964
2087
|
try {
|
|
1965
|
-
mcpConfig = JSON.parse(
|
|
2088
|
+
mcpConfig = JSON.parse(fs5.readFileSync(mcpPath, "utf-8"));
|
|
1966
2089
|
} catch {
|
|
1967
2090
|
mcpConfig = {};
|
|
1968
2091
|
}
|
|
@@ -1973,7 +2096,7 @@ claude-alarm init for "${projectName}"
|
|
|
1973
2096
|
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
1974
2097
|
env
|
|
1975
2098
|
};
|
|
1976
|
-
|
|
2099
|
+
fs5.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
1977
2100
|
console.log(`
|
|
1978
2101
|
\u2713 Created ${mcpPath}`);
|
|
1979
2102
|
if (remote.toLowerCase() !== "y") {
|