@delt/claude-alarm 0.9.1 → 1.0.0
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/README.md +18 -0
- package/dist/channel/server.js +41 -22
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +304 -108
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +774 -0
- package/dist/codex/main.js.map +1 -0
- package/dist/dashboard/index.html +18 -4
- package/dist/hub/server.js +155 -61
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +24 -2
- package/dist/index.js +201 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +18 -4
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/shared/constants.ts
|
|
13
13
|
import path from "path";
|
|
14
14
|
import os from "os";
|
|
15
|
-
var DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, CONFIG_DIR, CONFIG_FILE, PID_FILE, LOG_FILE, UPLOADS_DIR, WS_PATH_CHANNEL, WS_PATH_DASHBOARD, CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION;
|
|
15
|
+
var DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, CONFIG_DIR, CONFIG_FILE, PID_FILE, LOG_FILE, UPLOADS_DIR, CODEX_PID_FILE, CODEX_LOG_FILE, WS_PATH_CHANNEL, WS_PATH_DASHBOARD, CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION;
|
|
16
16
|
var init_constants = __esm({
|
|
17
17
|
"src/shared/constants.ts"() {
|
|
18
18
|
"use strict";
|
|
@@ -23,6 +23,8 @@ var init_constants = __esm({
|
|
|
23
23
|
PID_FILE = path.join(CONFIG_DIR, "hub.pid");
|
|
24
24
|
LOG_FILE = path.join(CONFIG_DIR, "hub.log");
|
|
25
25
|
UPLOADS_DIR = path.join(CONFIG_DIR, "uploads");
|
|
26
|
+
CODEX_PID_FILE = path.join(CONFIG_DIR, "codex.pid");
|
|
27
|
+
CODEX_LOG_FILE = path.join(CONFIG_DIR, "codex.log");
|
|
26
28
|
WS_PATH_CHANNEL = "/ws/channel";
|
|
27
29
|
WS_PATH_DASHBOARD = "/ws/dashboard";
|
|
28
30
|
CHANNEL_SERVER_NAME = "claude-alarm";
|
|
@@ -67,6 +69,12 @@ function saveConfig(config2) {
|
|
|
67
69
|
ensureConfigDir();
|
|
68
70
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), { encoding: "utf-8", mode: 384 });
|
|
69
71
|
}
|
|
72
|
+
function setCodexEnabled(enabled) {
|
|
73
|
+
const config2 = loadConfig();
|
|
74
|
+
config2.codex = { ...config2.codex, enabled };
|
|
75
|
+
saveConfig(config2);
|
|
76
|
+
return config2;
|
|
77
|
+
}
|
|
70
78
|
function setupMcpConfig(targetDir) {
|
|
71
79
|
const dir = targetDir ?? process.cwd();
|
|
72
80
|
const mcpPath = path2.join(dir, ".mcp.json");
|
|
@@ -211,9 +219,9 @@ var init_session_manager = __esm({
|
|
|
211
219
|
SessionManager = class {
|
|
212
220
|
sessions = /* @__PURE__ */ new Map();
|
|
213
221
|
register(session) {
|
|
214
|
-
const baseName =
|
|
222
|
+
const baseName = this.baseName(session);
|
|
215
223
|
const existing = Array.from(this.sessions.values()).filter(
|
|
216
|
-
(s) => s.id !== session.id &&
|
|
224
|
+
(s) => s.id !== session.id && this.baseName(s) === baseName
|
|
217
225
|
);
|
|
218
226
|
if (existing.length > 0) {
|
|
219
227
|
const usedNums = existing.map((s) => {
|
|
@@ -267,6 +275,9 @@ var init_session_manager = __esm({
|
|
|
267
275
|
count() {
|
|
268
276
|
return this.sessions.size;
|
|
269
277
|
}
|
|
278
|
+
baseName(session) {
|
|
279
|
+
return session.title || session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
280
|
+
}
|
|
270
281
|
};
|
|
271
282
|
}
|
|
272
283
|
});
|
|
@@ -294,7 +305,7 @@ var init_notifier = __esm({
|
|
|
294
305
|
async notify(title, message, level = "info") {
|
|
295
306
|
await this.notifyWithSession(void 0, void 0, title, message, level);
|
|
296
307
|
}
|
|
297
|
-
async notifyWithSession(sessionId2,
|
|
308
|
+
async notifyWithSession(sessionId2, sessionLabel2, title, message, level = "info") {
|
|
298
309
|
const promises = [];
|
|
299
310
|
if (this.desktopEnabled) {
|
|
300
311
|
promises.push(this.sendDesktop(title, message, level));
|
|
@@ -302,8 +313,8 @@ var init_notifier = __esm({
|
|
|
302
313
|
for (const webhook of this.webhooks) {
|
|
303
314
|
promises.push(this.sendWebhook(webhook, title, message, level));
|
|
304
315
|
}
|
|
305
|
-
if (this.telegramBot && sessionId2 &&
|
|
306
|
-
promises.push(this.telegramBot.sendNotification(sessionId2,
|
|
316
|
+
if (this.telegramBot && sessionId2 && sessionLabel2) {
|
|
317
|
+
promises.push(this.telegramBot.sendNotification(sessionId2, sessionLabel2, title, message));
|
|
307
318
|
}
|
|
308
319
|
await Promise.allSettled(promises);
|
|
309
320
|
}
|
|
@@ -387,6 +398,17 @@ var init_notifier = __esm({
|
|
|
387
398
|
}
|
|
388
399
|
});
|
|
389
400
|
|
|
401
|
+
// src/shared/session-label.ts
|
|
402
|
+
function sessionLabel(session) {
|
|
403
|
+
const base = session.displayName || session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
404
|
+
return session.agentKind === "codex" ? `Codex \xB7 ${base}` : base;
|
|
405
|
+
}
|
|
406
|
+
var init_session_label = __esm({
|
|
407
|
+
"src/shared/session-label.ts"() {
|
|
408
|
+
"use strict";
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
390
412
|
// src/hub/telegram.ts
|
|
391
413
|
import fs3 from "fs";
|
|
392
414
|
import path3 from "path";
|
|
@@ -397,6 +419,7 @@ var init_telegram = __esm({
|
|
|
397
419
|
"use strict";
|
|
398
420
|
init_logger();
|
|
399
421
|
init_constants();
|
|
422
|
+
init_session_label();
|
|
400
423
|
TELEGRAM_API = "https://api.telegram.org/bot";
|
|
401
424
|
TelegramBot = class _TelegramBot {
|
|
402
425
|
config;
|
|
@@ -535,15 +558,14 @@ ${this.mdToHtml(message)}`;
|
|
|
535
558
|
const pending = this.pendingMessages.get(msg.chat.id);
|
|
536
559
|
if (pending) {
|
|
537
560
|
this.pendingMessages.delete(msg.chat.id);
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
if (idx >= 0 && idx < sessions2.length) {
|
|
561
|
+
const session = this.pendingSession(pending, parseInt(selectMatch[1], 10) - 1);
|
|
562
|
+
if (session) {
|
|
541
563
|
if (pending.photoFileId) {
|
|
542
|
-
await this.deliverPhotoToSessionByFileId(
|
|
564
|
+
await this.deliverPhotoToSessionByFileId(session.id, pending.photoFileId, pending.caption);
|
|
543
565
|
} else if (pending.text) {
|
|
544
|
-
this.deliverToSession(
|
|
566
|
+
this.deliverToSession(session.id, pending.text);
|
|
545
567
|
}
|
|
546
|
-
this.sendMessage(`Sent to [${this.getLabel(
|
|
568
|
+
this.sendMessage(`Sent to [${this.getLabel(session)}]`);
|
|
547
569
|
} else {
|
|
548
570
|
this.sendMessage("Invalid session number.");
|
|
549
571
|
}
|
|
@@ -564,11 +586,12 @@ ${this.mdToHtml(message)}`;
|
|
|
564
586
|
}
|
|
565
587
|
return;
|
|
566
588
|
}
|
|
589
|
+
const sessionIds = sessions.map((s) => s.id);
|
|
567
590
|
if (hasPhoto) {
|
|
568
591
|
const largest = msg.photo[msg.photo.length - 1];
|
|
569
|
-
this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });
|
|
592
|
+
this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text, sessionIds });
|
|
570
593
|
} else {
|
|
571
|
-
this.pendingMessages.set(msg.chat.id, { text });
|
|
594
|
+
this.pendingMessages.set(msg.chat.id, { text, sessionIds });
|
|
572
595
|
}
|
|
573
596
|
const buttons = sessions.map((s, i) => ({
|
|
574
597
|
text: this.getLabel(s),
|
|
@@ -625,11 +648,16 @@ ${this.mdToHtml(message)}`;
|
|
|
625
648
|
logger.warn(`Telegram photo download failed: ${err.message}`);
|
|
626
649
|
}
|
|
627
650
|
}
|
|
651
|
+
// Buttons are numbered against the list shown when they were sent; Codex sessions come and go, so the live list may have shifted.
|
|
652
|
+
pendingSession(pending, idx) {
|
|
653
|
+
const id = pending.sessionIds[idx];
|
|
654
|
+
return id ? this.getSessions?.().find((s) => s.id === id) : void 0;
|
|
655
|
+
}
|
|
628
656
|
getLabel(session) {
|
|
629
|
-
return session
|
|
657
|
+
return sessionLabel(session);
|
|
630
658
|
}
|
|
631
659
|
/** Send a permission request with inline buttons */
|
|
632
|
-
async sendPermissionRequest(sessionId2,
|
|
660
|
+
async sendPermissionRequest(sessionId2, sessionLabel2, requestId, toolName, description, inputPreview) {
|
|
633
661
|
const isNotify = toolName.endsWith("__notify") || toolName === "notify";
|
|
634
662
|
let preview = inputPreview;
|
|
635
663
|
let kind = "code";
|
|
@@ -682,7 +710,7 @@ ${this.mdToHtml(message)}`;
|
|
|
682
710
|
const mcpMatch = toolName.match(/__([^_]+)$/);
|
|
683
711
|
if (mcpMatch) displayTool = mcpMatch[1].charAt(0).toUpperCase() + mcpMatch[1].slice(1);
|
|
684
712
|
}
|
|
685
|
-
const text = `\u26A0\uFE0F <b>Permission Request</b> \u2014 ${this.escHtml(
|
|
713
|
+
const text = `\u26A0\uFE0F <b>Permission Request</b> \u2014 ${this.escHtml(sessionLabel2)}
|
|
686
714
|
|
|
687
715
|
\u{1F527} <b>${this.escHtml(displayTool)}</b>
|
|
688
716
|
${previewHtml}${truncNote}`;
|
|
@@ -722,22 +750,18 @@ ${label}`);
|
|
|
722
750
|
const parts = query.data.split(":");
|
|
723
751
|
if (parts.length < 3) return;
|
|
724
752
|
const [, idxStr, chatIdStr] = parts;
|
|
725
|
-
const idx = parseInt(idxStr, 10);
|
|
726
753
|
const chatId = parseInt(chatIdStr, 10);
|
|
727
|
-
const
|
|
728
|
-
|
|
754
|
+
const pending = this.pendingMessages.get(chatId);
|
|
755
|
+
const session = pending ? this.pendingSession(pending, parseInt(idxStr, 10)) : void 0;
|
|
756
|
+
if (!pending || !session) {
|
|
729
757
|
await this.answerCallbackQuery(query.id, "Session not found");
|
|
730
758
|
return;
|
|
731
759
|
}
|
|
732
|
-
const session = sessions[idx];
|
|
733
|
-
const pending = this.pendingMessages.get(chatId);
|
|
734
760
|
this.pendingMessages.delete(chatId);
|
|
735
|
-
if (pending) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
this.deliverToSession(session.id, pending.text);
|
|
740
|
-
}
|
|
761
|
+
if (pending.photoFileId) {
|
|
762
|
+
await this.deliverPhotoToSessionByFileId(session.id, pending.photoFileId, pending.caption);
|
|
763
|
+
} else if (pending.text) {
|
|
764
|
+
this.deliverToSession(session.id, pending.text);
|
|
741
765
|
}
|
|
742
766
|
await this.answerCallbackQuery(query.id, `Sent to ${this.getLabel(session)}`);
|
|
743
767
|
if (query.message) {
|
|
@@ -799,8 +823,8 @@ ${label}`);
|
|
|
799
823
|
css: "css",
|
|
800
824
|
scss: "scss"
|
|
801
825
|
};
|
|
802
|
-
langOf(
|
|
803
|
-
const m = /\.([a-z0-9]+)$/i.exec(
|
|
826
|
+
langOf(path9) {
|
|
827
|
+
const m = /\.([a-z0-9]+)$/i.exec(path9 || "");
|
|
804
828
|
return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
|
|
805
829
|
}
|
|
806
830
|
/** Convert markdown to Telegram HTML (escape first, then apply formatting) */
|
|
@@ -845,6 +869,77 @@ ${bodyLines.join("\n")}`;
|
|
|
845
869
|
}
|
|
846
870
|
});
|
|
847
871
|
|
|
872
|
+
// src/hub/codex-supervisor.ts
|
|
873
|
+
import { spawn } from "child_process";
|
|
874
|
+
import fs4 from "fs";
|
|
875
|
+
import path4 from "path";
|
|
876
|
+
function resolveAdapterScript(baseDir) {
|
|
877
|
+
return [path4.join(baseDir, "..", "codex", "main.js"), path4.join(baseDir, "codex", "main.js")].find((p) => fs4.existsSync(p));
|
|
878
|
+
}
|
|
879
|
+
var HEALTHY_RUN_MS, CodexSupervisor;
|
|
880
|
+
var init_codex_supervisor = __esm({
|
|
881
|
+
"src/hub/codex-supervisor.ts"() {
|
|
882
|
+
"use strict";
|
|
883
|
+
init_logger();
|
|
884
|
+
HEALTHY_RUN_MS = 6e4;
|
|
885
|
+
CodexSupervisor = class {
|
|
886
|
+
constructor(script, spawnFn = spawn, minDelayMs = 2e3, maxDelayMs = 6e4, stopGraceMs = 3e3) {
|
|
887
|
+
this.script = script;
|
|
888
|
+
this.spawnFn = spawnFn;
|
|
889
|
+
this.minDelayMs = minDelayMs;
|
|
890
|
+
this.maxDelayMs = maxDelayMs;
|
|
891
|
+
this.stopGraceMs = stopGraceMs;
|
|
892
|
+
this.delay = minDelayMs;
|
|
893
|
+
}
|
|
894
|
+
child;
|
|
895
|
+
timer;
|
|
896
|
+
stopped = true;
|
|
897
|
+
delay;
|
|
898
|
+
start() {
|
|
899
|
+
this.stopped = false;
|
|
900
|
+
this.launch();
|
|
901
|
+
}
|
|
902
|
+
stop() {
|
|
903
|
+
this.stopped = true;
|
|
904
|
+
if (this.timer) clearTimeout(this.timer);
|
|
905
|
+
this.timer = void 0;
|
|
906
|
+
const child = this.child;
|
|
907
|
+
this.child = void 0;
|
|
908
|
+
if (!child) return;
|
|
909
|
+
child.stdin?.end();
|
|
910
|
+
const force = setTimeout(() => child.kill(), this.stopGraceMs);
|
|
911
|
+
force.unref();
|
|
912
|
+
child.once("exit", () => clearTimeout(force));
|
|
913
|
+
}
|
|
914
|
+
launch() {
|
|
915
|
+
const startedAt = Date.now();
|
|
916
|
+
const child = this.spawnFn(process.execPath, [this.script, "--watch-stdin"], {
|
|
917
|
+
detached: true,
|
|
918
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
919
|
+
windowsHide: true
|
|
920
|
+
});
|
|
921
|
+
this.child = child;
|
|
922
|
+
child.stdout?.on("data", (d) => process.stdout.write(d));
|
|
923
|
+
child.stderr?.on("data", (d) => process.stderr.write(d));
|
|
924
|
+
child.stdin?.on("error", () => {
|
|
925
|
+
});
|
|
926
|
+
child.on("error", (err) => logger.warn(`Codex adapter failed to start: ${err.message}`));
|
|
927
|
+
child.on("exit", (code, signal) => {
|
|
928
|
+
if (this.child === child) this.child = void 0;
|
|
929
|
+
if (this.stopped || code === 0) return;
|
|
930
|
+
if (Date.now() - startedAt > HEALTHY_RUN_MS) this.delay = this.minDelayMs;
|
|
931
|
+
logger.warn(`Codex adapter exited (${signal ?? code}); restarting in ${this.delay}ms`);
|
|
932
|
+
this.timer = setTimeout(() => {
|
|
933
|
+
this.timer = void 0;
|
|
934
|
+
if (!this.stopped) this.launch();
|
|
935
|
+
}, this.delay);
|
|
936
|
+
this.delay = Math.min(this.delay * 2, this.maxDelayMs);
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
|
|
848
943
|
// src/hub/auth.ts
|
|
849
944
|
import crypto from "crypto";
|
|
850
945
|
function safeEqual(a, b) {
|
|
@@ -914,8 +1009,8 @@ __export(server_exports, {
|
|
|
914
1009
|
HubServer: () => HubServer
|
|
915
1010
|
});
|
|
916
1011
|
import http from "http";
|
|
917
|
-
import
|
|
918
|
-
import
|
|
1012
|
+
import fs5 from "fs";
|
|
1013
|
+
import path5 from "path";
|
|
919
1014
|
import { fileURLToPath } from "url";
|
|
920
1015
|
import { WebSocketServer, WebSocket } from "ws";
|
|
921
1016
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -928,10 +1023,12 @@ var init_server = __esm({
|
|
|
928
1023
|
init_session_manager();
|
|
929
1024
|
init_notifier();
|
|
930
1025
|
init_telegram();
|
|
1026
|
+
init_codex_supervisor();
|
|
931
1027
|
init_config();
|
|
1028
|
+
init_session_label();
|
|
932
1029
|
init_crash_guard();
|
|
933
1030
|
init_auth();
|
|
934
|
-
__dirname =
|
|
1031
|
+
__dirname = path5.dirname(fileURLToPath(import.meta.url));
|
|
935
1032
|
MAX_WS_PAYLOAD = 16 * 1024 * 1024;
|
|
936
1033
|
HubServer = class {
|
|
937
1034
|
httpServer;
|
|
@@ -954,10 +1051,13 @@ var init_server = __esm({
|
|
|
954
1051
|
host;
|
|
955
1052
|
port;
|
|
956
1053
|
token;
|
|
1054
|
+
codexEnabled;
|
|
1055
|
+
codexSupervisor;
|
|
957
1056
|
constructor(config2) {
|
|
958
1057
|
this.host = config2?.hub?.host ?? DEFAULT_HUB_HOST;
|
|
959
1058
|
this.port = config2?.hub?.port ?? DEFAULT_HUB_PORT;
|
|
960
1059
|
this.token = config2?.hub?.token;
|
|
1060
|
+
this.codexEnabled = config2?.codex?.enabled === true;
|
|
961
1061
|
if (config2?.notifications) {
|
|
962
1062
|
this.notifier.configure({
|
|
963
1063
|
desktop: config2.notifications.desktop
|
|
@@ -1015,12 +1115,15 @@ var init_server = __esm({
|
|
|
1015
1115
|
this.httpServer.on("error", (err) => logger.error(`HTTP server error: ${err.message}`));
|
|
1016
1116
|
const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
|
|
1017
1117
|
logger.info(`Hub server listening on http://${displayHost}:${this.port}`);
|
|
1118
|
+
this.startCodexAdapter();
|
|
1018
1119
|
resolve();
|
|
1019
1120
|
});
|
|
1020
1121
|
});
|
|
1021
1122
|
}
|
|
1022
1123
|
stop() {
|
|
1023
1124
|
return new Promise((resolve) => {
|
|
1125
|
+
this.codexSupervisor?.stop();
|
|
1126
|
+
this.codexSupervisor = void 0;
|
|
1024
1127
|
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
1025
1128
|
if (this.telegramBot) this.telegramBot.stopPolling();
|
|
1026
1129
|
for (const ws of this.channelSockets.values()) ws.terminate();
|
|
@@ -1121,23 +1224,23 @@ var init_server = __esm({
|
|
|
1121
1224
|
return;
|
|
1122
1225
|
}
|
|
1123
1226
|
const candidates = [
|
|
1124
|
-
|
|
1227
|
+
path5.join(__dirname, "..", "dashboard", "index.html"),
|
|
1125
1228
|
// from dist/hub/
|
|
1126
|
-
|
|
1229
|
+
path5.join(__dirname, "dashboard", "index.html"),
|
|
1127
1230
|
// from dist/ (bundled index.js)
|
|
1128
|
-
|
|
1231
|
+
path5.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
1129
1232
|
// from dist/hub/ -> src/
|
|
1130
|
-
|
|
1233
|
+
path5.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
1131
1234
|
// from dist/ -> src/
|
|
1132
|
-
|
|
1235
|
+
path5.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
1133
1236
|
// from cwd
|
|
1134
|
-
|
|
1237
|
+
path5.join(process.cwd(), "src", "dashboard", "index.html")
|
|
1135
1238
|
// from cwd/src
|
|
1136
1239
|
];
|
|
1137
1240
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
1138
1241
|
for (const candidate of candidates) {
|
|
1139
|
-
if (
|
|
1140
|
-
const html =
|
|
1242
|
+
if (fs5.existsSync(candidate)) {
|
|
1243
|
+
const html = fs5.readFileSync(candidate, "utf-8");
|
|
1141
1244
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1142
1245
|
res.end(html);
|
|
1143
1246
|
return;
|
|
@@ -1176,7 +1279,7 @@ var init_server = __esm({
|
|
|
1176
1279
|
this.jsonResponse(res, 404, { error: "Session not connected" });
|
|
1177
1280
|
return;
|
|
1178
1281
|
}
|
|
1179
|
-
const msg = { type: "message_to_session", sessionId: sessionId2, content };
|
|
1282
|
+
const msg = { type: "message_to_session", sessionId: sessionId2, content, source: "api" };
|
|
1180
1283
|
ws.send(JSON.stringify(msg));
|
|
1181
1284
|
this.jsonResponse(res, 200, { ok: true });
|
|
1182
1285
|
}
|
|
@@ -1350,7 +1453,7 @@ var init_server = __esm({
|
|
|
1350
1453
|
if (msg.type === "message_to_session") {
|
|
1351
1454
|
const channelWs = this.channelSockets.get(msg.sessionId);
|
|
1352
1455
|
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
1353
|
-
channelWs.send(JSON.stringify(msg));
|
|
1456
|
+
channelWs.send(JSON.stringify({ ...msg, source: "dashboard" }));
|
|
1354
1457
|
}
|
|
1355
1458
|
} else if (msg.type === "image_upload") {
|
|
1356
1459
|
this.handleImageUpload(msg);
|
|
@@ -1395,11 +1498,11 @@ var init_server = __esm({
|
|
|
1395
1498
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
1396
1499
|
return;
|
|
1397
1500
|
}
|
|
1398
|
-
|
|
1501
|
+
fs5.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
1399
1502
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
1400
1503
|
const filename = `${randomUUID3()}.${ext}`;
|
|
1401
|
-
const filePath =
|
|
1402
|
-
|
|
1504
|
+
const filePath = path5.join(UPLOADS_DIR, filename);
|
|
1505
|
+
fs5.writeFileSync(filePath, buffer);
|
|
1403
1506
|
const forwardMsg = {
|
|
1404
1507
|
type: "image_to_session",
|
|
1405
1508
|
sessionId: sessionId2,
|
|
@@ -1412,7 +1515,7 @@ var init_server = __esm({
|
|
|
1412
1515
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
1413
1516
|
setTimeout(() => {
|
|
1414
1517
|
try {
|
|
1415
|
-
|
|
1518
|
+
fs5.unlinkSync(filePath);
|
|
1416
1519
|
} catch {
|
|
1417
1520
|
}
|
|
1418
1521
|
}, 5 * 60 * 1e3);
|
|
@@ -1440,7 +1543,7 @@ var init_server = __esm({
|
|
|
1440
1543
|
this.telegramBot.onMessageToSession = (sessionId2, content) => {
|
|
1441
1544
|
const channelWs = this.channelSockets.get(sessionId2);
|
|
1442
1545
|
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
1443
|
-
const msg = { type: "message_to_session", sessionId: sessionId2, content };
|
|
1546
|
+
const msg = { type: "message_to_session", sessionId: sessionId2, content, source: "telegram" };
|
|
1444
1547
|
channelWs.send(JSON.stringify(msg));
|
|
1445
1548
|
logger.info(`Telegram message forwarded to session: ${sessionId2}`);
|
|
1446
1549
|
}
|
|
@@ -1567,6 +1670,17 @@ var init_server = __esm({
|
|
|
1567
1670
|
this.jsonResponse(res, 500, { error: err.message });
|
|
1568
1671
|
}
|
|
1569
1672
|
}
|
|
1673
|
+
startCodexAdapter() {
|
|
1674
|
+
if (!this.codexEnabled) return;
|
|
1675
|
+
const script = resolveAdapterScript(__dirname);
|
|
1676
|
+
if (!script) {
|
|
1677
|
+
logger.warn("Codex adapter is enabled but codex/main.js was not found next to the hub");
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
this.codexSupervisor = new CodexSupervisor(script);
|
|
1681
|
+
this.codexSupervisor.start();
|
|
1682
|
+
logger.info("Codex adapter started");
|
|
1683
|
+
}
|
|
1570
1684
|
startHeartbeat() {
|
|
1571
1685
|
this.heartbeatInterval = setInterval(() => {
|
|
1572
1686
|
for (const [sessionId2, ws] of this.channelSockets) {
|
|
@@ -1582,11 +1696,11 @@ var init_server = __esm({
|
|
|
1582
1696
|
}
|
|
1583
1697
|
cleanupUploads() {
|
|
1584
1698
|
try {
|
|
1585
|
-
if (!
|
|
1586
|
-
const files =
|
|
1699
|
+
if (!fs5.existsSync(UPLOADS_DIR)) return;
|
|
1700
|
+
const files = fs5.readdirSync(UPLOADS_DIR);
|
|
1587
1701
|
for (const file of files) {
|
|
1588
1702
|
try {
|
|
1589
|
-
|
|
1703
|
+
fs5.unlinkSync(path5.join(UPLOADS_DIR, file));
|
|
1590
1704
|
} catch {
|
|
1591
1705
|
}
|
|
1592
1706
|
}
|
|
@@ -1595,8 +1709,7 @@ var init_server = __esm({
|
|
|
1595
1709
|
}
|
|
1596
1710
|
}
|
|
1597
1711
|
getSessionLabel(session) {
|
|
1598
|
-
|
|
1599
|
-
return session.displayName || session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
1712
|
+
return session ? sessionLabel(session) : "unknown";
|
|
1600
1713
|
}
|
|
1601
1714
|
jsonResponse(res, status, body) {
|
|
1602
1715
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
@@ -1668,48 +1781,41 @@ var init_hub_client = __esm({
|
|
|
1668
1781
|
init_logger();
|
|
1669
1782
|
init_constants();
|
|
1670
1783
|
HubClient = class {
|
|
1671
|
-
constructor(sessionId2, sessionName2, hubHost2 = DEFAULT_HUB_HOST, hubPort2 = DEFAULT_HUB_PORT, token, getPeerName = () => void 0) {
|
|
1784
|
+
constructor(sessionId2, sessionName2, hubHost2 = DEFAULT_HUB_HOST, hubPort2 = DEFAULT_HUB_PORT, token, getPeerName = () => void 0, getRegistration = () => ({})) {
|
|
1672
1785
|
this.sessionId = sessionId2;
|
|
1673
1786
|
this.sessionName = sessionName2;
|
|
1674
1787
|
this.hubHost = hubHost2;
|
|
1675
1788
|
this.hubPort = hubPort2;
|
|
1676
1789
|
this.token = token;
|
|
1677
1790
|
this.getPeerName = getPeerName;
|
|
1791
|
+
this.getRegistration = getRegistration;
|
|
1678
1792
|
}
|
|
1679
1793
|
ws = null;
|
|
1680
1794
|
reconnectTimer = null;
|
|
1681
1795
|
messageHandlers = [];
|
|
1682
1796
|
queue = [];
|
|
1683
1797
|
connected = false;
|
|
1798
|
+
closed = false;
|
|
1684
1799
|
connect() {
|
|
1800
|
+
this.closed = false;
|
|
1685
1801
|
const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : "";
|
|
1686
1802
|
const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;
|
|
1687
1803
|
logger.debug(`Connecting to hub at ${url}`);
|
|
1688
1804
|
try {
|
|
1689
|
-
|
|
1690
|
-
this.ws
|
|
1805
|
+
const ws = new WebSocket2(url);
|
|
1806
|
+
this.ws = ws;
|
|
1807
|
+
ws.on("open", () => {
|
|
1808
|
+
if (this.ws !== ws) return;
|
|
1691
1809
|
logger.info("Connected to hub");
|
|
1692
1810
|
this.connected = true;
|
|
1693
|
-
|
|
1694
|
-
type: "register",
|
|
1695
|
-
session: {
|
|
1696
|
-
id: this.sessionId,
|
|
1697
|
-
name: this.sessionName,
|
|
1698
|
-
status: "idle",
|
|
1699
|
-
connectedAt: Date.now(),
|
|
1700
|
-
lastActivity: Date.now(),
|
|
1701
|
-
cwd: process.cwd(),
|
|
1702
|
-
channelEnabled: true,
|
|
1703
|
-
peerName: this.getPeerName()
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1706
|
-
this.ws.send(JSON.stringify(registration));
|
|
1811
|
+
this.ws.send(JSON.stringify(this.registration()));
|
|
1707
1812
|
for (const msg of this.queue) {
|
|
1708
1813
|
this.ws.send(JSON.stringify(msg));
|
|
1709
1814
|
}
|
|
1710
1815
|
this.queue = [];
|
|
1711
1816
|
});
|
|
1712
|
-
|
|
1817
|
+
ws.on("message", (data) => {
|
|
1818
|
+
if (this.ws !== ws) return;
|
|
1713
1819
|
try {
|
|
1714
1820
|
const msg = JSON.parse(data.toString());
|
|
1715
1821
|
for (const handler of this.messageHandlers) {
|
|
@@ -1719,12 +1825,14 @@ var init_hub_client = __esm({
|
|
|
1719
1825
|
logger.warn("Failed to parse hub message:", err);
|
|
1720
1826
|
}
|
|
1721
1827
|
});
|
|
1722
|
-
|
|
1828
|
+
ws.on("close", () => {
|
|
1829
|
+
if (this.ws !== ws) return;
|
|
1723
1830
|
logger.info("Disconnected from hub");
|
|
1724
1831
|
this.connected = false;
|
|
1725
|
-
this.scheduleReconnect();
|
|
1832
|
+
if (!this.closed) this.scheduleReconnect();
|
|
1726
1833
|
});
|
|
1727
|
-
|
|
1834
|
+
ws.on("error", (err) => {
|
|
1835
|
+
if (this.ws !== ws) return;
|
|
1728
1836
|
logger.debug(`Hub connection error: ${err.message}`);
|
|
1729
1837
|
this.connected = false;
|
|
1730
1838
|
});
|
|
@@ -1747,6 +1855,7 @@ var init_hub_client = __esm({
|
|
|
1747
1855
|
this.messageHandlers.push(handler);
|
|
1748
1856
|
}
|
|
1749
1857
|
disconnect() {
|
|
1858
|
+
this.closed = true;
|
|
1750
1859
|
if (this.reconnectTimer) {
|
|
1751
1860
|
clearTimeout(this.reconnectTimer);
|
|
1752
1861
|
this.reconnectTimer = null;
|
|
@@ -1757,8 +1866,29 @@ var init_hub_client = __esm({
|
|
|
1757
1866
|
}
|
|
1758
1867
|
this.connected = false;
|
|
1759
1868
|
}
|
|
1869
|
+
reregister() {
|
|
1870
|
+
if (this.connected && this.ws?.readyState === WebSocket2.OPEN) {
|
|
1871
|
+
this.ws.send(JSON.stringify(this.registration()));
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
registration() {
|
|
1875
|
+
return {
|
|
1876
|
+
type: "register",
|
|
1877
|
+
session: {
|
|
1878
|
+
id: this.sessionId,
|
|
1879
|
+
name: this.sessionName,
|
|
1880
|
+
status: "idle",
|
|
1881
|
+
connectedAt: Date.now(),
|
|
1882
|
+
lastActivity: Date.now(),
|
|
1883
|
+
cwd: process.cwd(),
|
|
1884
|
+
channelEnabled: true,
|
|
1885
|
+
peerName: this.getPeerName(),
|
|
1886
|
+
...this.getRegistration()
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1760
1890
|
scheduleReconnect() {
|
|
1761
|
-
if (this.reconnectTimer) return;
|
|
1891
|
+
if (this.reconnectTimer || this.closed) return;
|
|
1762
1892
|
this.reconnectTimer = setTimeout(() => {
|
|
1763
1893
|
this.reconnectTimer = null;
|
|
1764
1894
|
this.connect();
|
|
@@ -1769,9 +1899,9 @@ var init_hub_client = __esm({
|
|
|
1769
1899
|
});
|
|
1770
1900
|
|
|
1771
1901
|
// src/channel/peer-name.ts
|
|
1772
|
-
import
|
|
1902
|
+
import fs6 from "fs";
|
|
1773
1903
|
import os2 from "os";
|
|
1774
|
-
import
|
|
1904
|
+
import path6 from "path";
|
|
1775
1905
|
function findPeerName(records, env) {
|
|
1776
1906
|
const valid = records.filter((r) => typeof r === "object" && r !== null);
|
|
1777
1907
|
const nameOf = (match) => {
|
|
@@ -1791,18 +1921,18 @@ function readPeerName(env = process.env) {
|
|
|
1791
1921
|
sessionId: env.CLAUDE_CODE_SESSION_ID
|
|
1792
1922
|
};
|
|
1793
1923
|
if (!lookup.messagingSocket && !lookup.sessionId) return void 0;
|
|
1794
|
-
const configDir = env.CLAUDE_CONFIG_DIR ||
|
|
1795
|
-
const dir =
|
|
1924
|
+
const configDir = env.CLAUDE_CONFIG_DIR || path6.join(os2.homedir(), ".claude");
|
|
1925
|
+
const dir = path6.join(configDir, "sessions");
|
|
1796
1926
|
let files;
|
|
1797
1927
|
try {
|
|
1798
|
-
files =
|
|
1928
|
+
files = fs6.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
1799
1929
|
} catch {
|
|
1800
1930
|
return void 0;
|
|
1801
1931
|
}
|
|
1802
1932
|
const records = [];
|
|
1803
1933
|
for (const f of files) {
|
|
1804
1934
|
try {
|
|
1805
|
-
records.push(JSON.parse(
|
|
1935
|
+
records.push(JSON.parse(fs6.readFileSync(path6.join(dir, f), "utf8")));
|
|
1806
1936
|
} catch {
|
|
1807
1937
|
}
|
|
1808
1938
|
}
|
|
@@ -1825,7 +1955,7 @@ import {
|
|
|
1825
1955
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1826
1956
|
import { z } from "zod";
|
|
1827
1957
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1828
|
-
import
|
|
1958
|
+
import path7 from "path";
|
|
1829
1959
|
async function main() {
|
|
1830
1960
|
logger.info(`Starting MCP channel server (session: ${sessionId})`);
|
|
1831
1961
|
hubClient.connect();
|
|
@@ -1894,7 +2024,7 @@ var init_server2 = __esm({
|
|
|
1894
2024
|
init_hub_client();
|
|
1895
2025
|
init_peer_name2();
|
|
1896
2026
|
sessionId = randomUUID4();
|
|
1897
|
-
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ??
|
|
2027
|
+
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path7.basename(process.cwd());
|
|
1898
2028
|
peerName = readPeerName();
|
|
1899
2029
|
server = new Server(
|
|
1900
2030
|
{
|
|
@@ -2073,12 +2203,12 @@ init_config();
|
|
|
2073
2203
|
init_constants();
|
|
2074
2204
|
init_logger();
|
|
2075
2205
|
init_crash_guard();
|
|
2076
|
-
import { spawn } from "child_process";
|
|
2077
|
-
import
|
|
2078
|
-
import
|
|
2206
|
+
import { spawn as spawn2 } from "child_process";
|
|
2207
|
+
import fs7 from "fs";
|
|
2208
|
+
import path8 from "path";
|
|
2079
2209
|
import readline from "readline";
|
|
2080
2210
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2081
|
-
var __dirname2 =
|
|
2211
|
+
var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
2082
2212
|
function authHeaders(token) {
|
|
2083
2213
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
2084
2214
|
}
|
|
@@ -2097,6 +2227,11 @@ Usage:
|
|
|
2097
2227
|
claude-alarm setup [dir] Add claude-alarm to .mcp.json
|
|
2098
2228
|
claude-alarm test Send a test notification
|
|
2099
2229
|
claude-alarm token Show current auth token
|
|
2230
|
+
claude-alarm codex enable Start the Codex adapter together with the hub
|
|
2231
|
+
claude-alarm codex disable Stop starting the Codex adapter with the hub
|
|
2232
|
+
claude-alarm codex start Run the Codex adapter on its own (e.g. Codex on another PC)
|
|
2233
|
+
claude-alarm codex stop Stop a running Codex adapter
|
|
2234
|
+
claude-alarm codex status Show Codex adapter status
|
|
2100
2235
|
claude-alarm help Show this help
|
|
2101
2236
|
|
|
2102
2237
|
Quick start:
|
|
@@ -2105,8 +2240,8 @@ Quick start:
|
|
|
2105
2240
|
}
|
|
2106
2241
|
async function checkForUpdates() {
|
|
2107
2242
|
try {
|
|
2108
|
-
const pkgPath =
|
|
2109
|
-
const pkg = JSON.parse(
|
|
2243
|
+
const pkgPath = path8.join(__dirname2, "..", "package.json");
|
|
2244
|
+
const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
|
|
2110
2245
|
const currentVersion = pkg.version;
|
|
2111
2246
|
const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
|
|
2112
2247
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -2129,25 +2264,25 @@ async function hubStart(daemon) {
|
|
|
2129
2264
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
2130
2265
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
2131
2266
|
checkForUpdates();
|
|
2132
|
-
if (
|
|
2133
|
-
const pid = parseInt(
|
|
2267
|
+
if (fs7.existsSync(PID_FILE)) {
|
|
2268
|
+
const pid = parseInt(fs7.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
2134
2269
|
if (isProcessRunning(pid)) {
|
|
2135
2270
|
console.log(`Hub is already running (PID: ${pid}). Dashboard: ${loginLink(displayHost, port, config2.hub.token)}`);
|
|
2136
2271
|
return;
|
|
2137
2272
|
}
|
|
2138
|
-
|
|
2273
|
+
fs7.unlinkSync(PID_FILE);
|
|
2139
2274
|
}
|
|
2140
2275
|
if (daemon) {
|
|
2141
2276
|
ensureConfigDir();
|
|
2142
|
-
const logFd =
|
|
2143
|
-
const hubScript =
|
|
2144
|
-
const child =
|
|
2277
|
+
const logFd = fs7.openSync(LOG_FILE, "a");
|
|
2278
|
+
const hubScript = path8.join(__dirname2, "hub", "server.js");
|
|
2279
|
+
const child = spawn2(process.execPath, [hubScript], {
|
|
2145
2280
|
detached: true,
|
|
2146
2281
|
stdio: ["ignore", logFd, logFd],
|
|
2147
2282
|
env: { ...process.env }
|
|
2148
2283
|
});
|
|
2149
2284
|
if (child.pid) {
|
|
2150
|
-
|
|
2285
|
+
fs7.writeFileSync(PID_FILE, String(child.pid), "utf-8");
|
|
2151
2286
|
child.unref();
|
|
2152
2287
|
console.log(`Hub started as daemon (PID: ${child.pid})`);
|
|
2153
2288
|
console.log(`Dashboard: ${loginLink(displayHost, port, config2.hub.token)}`);
|
|
@@ -2168,11 +2303,11 @@ async function hubStart(daemon) {
|
|
|
2168
2303
|
const hub = new HubServer2(config2);
|
|
2169
2304
|
await hub.start();
|
|
2170
2305
|
ensureConfigDir();
|
|
2171
|
-
|
|
2306
|
+
fs7.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
2172
2307
|
const shutdown = async () => {
|
|
2173
2308
|
console.log("\nShutting down...");
|
|
2174
2309
|
await hub.stop();
|
|
2175
|
-
if (
|
|
2310
|
+
if (fs7.existsSync(PID_FILE)) fs7.unlinkSync(PID_FILE);
|
|
2176
2311
|
process.exit(0);
|
|
2177
2312
|
};
|
|
2178
2313
|
process.on("SIGINT", shutdown);
|
|
@@ -2180,18 +2315,18 @@ async function hubStart(daemon) {
|
|
|
2180
2315
|
}
|
|
2181
2316
|
}
|
|
2182
2317
|
function hubStop() {
|
|
2183
|
-
if (!
|
|
2318
|
+
if (!fs7.existsSync(PID_FILE)) {
|
|
2184
2319
|
console.log("Hub is not running (no PID file found)");
|
|
2185
2320
|
return;
|
|
2186
2321
|
}
|
|
2187
|
-
const pid = parseInt(
|
|
2322
|
+
const pid = parseInt(fs7.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
2188
2323
|
try {
|
|
2189
2324
|
process.kill(pid, "SIGTERM");
|
|
2190
2325
|
console.log(`Hub stopped (PID: ${pid})`);
|
|
2191
2326
|
} catch {
|
|
2192
2327
|
console.log("Hub process not found (may have already stopped)");
|
|
2193
2328
|
}
|
|
2194
|
-
|
|
2329
|
+
fs7.unlinkSync(PID_FILE);
|
|
2195
2330
|
}
|
|
2196
2331
|
async function hubStatus() {
|
|
2197
2332
|
const config2 = loadConfig();
|
|
@@ -2199,8 +2334,8 @@ async function hubStatus() {
|
|
|
2199
2334
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
2200
2335
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
2201
2336
|
let pidInfo = "not running";
|
|
2202
|
-
if (
|
|
2203
|
-
const pid = parseInt(
|
|
2337
|
+
if (fs7.existsSync(PID_FILE)) {
|
|
2338
|
+
const pid = parseInt(fs7.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
2204
2339
|
if (isProcessRunning(pid)) {
|
|
2205
2340
|
pidInfo = `running (PID: ${pid})`;
|
|
2206
2341
|
} else {
|
|
@@ -2272,7 +2407,7 @@ function ask(question) {
|
|
|
2272
2407
|
}
|
|
2273
2408
|
async function init() {
|
|
2274
2409
|
const dir = process.cwd();
|
|
2275
|
-
const projectName =
|
|
2410
|
+
const projectName = path8.basename(dir);
|
|
2276
2411
|
console.log(`
|
|
2277
2412
|
claude-alarm init for "${projectName}"
|
|
2278
2413
|
`);
|
|
@@ -2292,11 +2427,11 @@ claude-alarm init for "${projectName}"
|
|
|
2292
2427
|
if (port) env.CLAUDE_ALARM_HUB_PORT = port;
|
|
2293
2428
|
if (token) env.CLAUDE_ALARM_HUB_TOKEN = token;
|
|
2294
2429
|
}
|
|
2295
|
-
const mcpPath =
|
|
2430
|
+
const mcpPath = path8.join(dir, ".mcp.json");
|
|
2296
2431
|
let mcpConfig = {};
|
|
2297
|
-
if (
|
|
2432
|
+
if (fs7.existsSync(mcpPath)) {
|
|
2298
2433
|
try {
|
|
2299
|
-
mcpConfig = JSON.parse(
|
|
2434
|
+
mcpConfig = JSON.parse(fs7.readFileSync(mcpPath, "utf-8"));
|
|
2300
2435
|
} catch {
|
|
2301
2436
|
mcpConfig = {};
|
|
2302
2437
|
}
|
|
@@ -2307,7 +2442,7 @@ claude-alarm init for "${projectName}"
|
|
|
2307
2442
|
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
2308
2443
|
env
|
|
2309
2444
|
};
|
|
2310
|
-
|
|
2445
|
+
fs7.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
2311
2446
|
console.log(`
|
|
2312
2447
|
\u2713 Created ${mcpPath}`);
|
|
2313
2448
|
if (remote.toLowerCase() !== "y") {
|
|
@@ -2352,6 +2487,54 @@ function isProcessRunning(pid) {
|
|
|
2352
2487
|
return false;
|
|
2353
2488
|
}
|
|
2354
2489
|
}
|
|
2490
|
+
function readCodexPid() {
|
|
2491
|
+
if (!fs7.existsSync(CODEX_PID_FILE)) return void 0;
|
|
2492
|
+
const pid = parseInt(fs7.readFileSync(CODEX_PID_FILE, "utf-8").trim(), 10);
|
|
2493
|
+
return Number.isNaN(pid) ? void 0 : pid;
|
|
2494
|
+
}
|
|
2495
|
+
function codexEnable(enabled) {
|
|
2496
|
+
setCodexEnabled(enabled);
|
|
2497
|
+
console.log(enabled ? "Codex adapter enabled. Restart the hub to apply: claude-alarm hub stop, then claude-alarm hub start" : "Codex adapter disabled. Restart the hub to apply.");
|
|
2498
|
+
}
|
|
2499
|
+
function codexStart() {
|
|
2500
|
+
const pid = readCodexPid();
|
|
2501
|
+
if (pid !== void 0 && isProcessRunning(pid)) {
|
|
2502
|
+
console.log(`Codex adapter is already running (PID: ${pid})`);
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
ensureConfigDir();
|
|
2506
|
+
const logFd = fs7.openSync(CODEX_LOG_FILE, "a");
|
|
2507
|
+
const child = spawn2(process.execPath, [path8.join(__dirname2, "codex", "main.js")], {
|
|
2508
|
+
detached: true,
|
|
2509
|
+
stdio: ["ignore", logFd, logFd],
|
|
2510
|
+
env: { ...process.env }
|
|
2511
|
+
});
|
|
2512
|
+
child.unref();
|
|
2513
|
+
console.log(`Codex adapter started (PID: ${child.pid}). Logs: ${CODEX_LOG_FILE}`);
|
|
2514
|
+
}
|
|
2515
|
+
function codexStop() {
|
|
2516
|
+
const pid = readCodexPid();
|
|
2517
|
+
if (pid === void 0) {
|
|
2518
|
+
console.log("Codex adapter is not running");
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
try {
|
|
2522
|
+
process.kill(pid, "SIGTERM");
|
|
2523
|
+
console.log(`Codex adapter stopped (PID: ${pid})`);
|
|
2524
|
+
} catch {
|
|
2525
|
+
console.log("Codex adapter process not found (may have already stopped)");
|
|
2526
|
+
}
|
|
2527
|
+
try {
|
|
2528
|
+
fs7.unlinkSync(CODEX_PID_FILE);
|
|
2529
|
+
} catch {
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
function codexStatus() {
|
|
2533
|
+
const pid = readCodexPid();
|
|
2534
|
+
const state = pid === void 0 ? "not running" : isProcessRunning(pid) ? `running (PID: ${pid})` : "not running (stale PID file)";
|
|
2535
|
+
console.log(`Codex adapter: ${state}`);
|
|
2536
|
+
console.log(`Start with hub: ${loadConfig().codex?.enabled ? "enabled" : "disabled"}`);
|
|
2537
|
+
}
|
|
2355
2538
|
async function main2() {
|
|
2356
2539
|
const args = process.argv.slice(2);
|
|
2357
2540
|
const cmd = args[0];
|
|
@@ -2383,6 +2566,19 @@ async function main2() {
|
|
|
2383
2566
|
}
|
|
2384
2567
|
return;
|
|
2385
2568
|
}
|
|
2569
|
+
if (cmd === "codex") {
|
|
2570
|
+
if (sub === "enable") codexEnable(true);
|
|
2571
|
+
else if (sub === "disable") codexEnable(false);
|
|
2572
|
+
else if (sub === "start") codexStart();
|
|
2573
|
+
else if (sub === "stop") codexStop();
|
|
2574
|
+
else if (sub === "status") codexStatus();
|
|
2575
|
+
else {
|
|
2576
|
+
console.error(`Unknown codex command: ${sub}`);
|
|
2577
|
+
printUsage();
|
|
2578
|
+
process.exit(1);
|
|
2579
|
+
}
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2386
2582
|
if (cmd === "setup") {
|
|
2387
2583
|
setup(args[1]);
|
|
2388
2584
|
return;
|