@delt/claude-alarm 1.0.0 → 1.1.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 +12 -4
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +278 -98
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +154 -15
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +84 -6
- package/dist/hub/server.js +157 -12
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +37 -2
- package/dist/index.js +157 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +84 -6
package/dist/cli.js
CHANGED
|
@@ -75,6 +75,9 @@ function setCodexEnabled(enabled) {
|
|
|
75
75
|
saveConfig(config2);
|
|
76
76
|
return config2;
|
|
77
77
|
}
|
|
78
|
+
function shouldOfferCodex(config2, installed) {
|
|
79
|
+
return installed && config2.codex === void 0;
|
|
80
|
+
}
|
|
78
81
|
function setupMcpConfig(targetDir) {
|
|
79
82
|
const dir = targetDir ?? process.cwd();
|
|
80
83
|
const mcpPath = path2.join(dir, ".mcp.json");
|
|
@@ -143,11 +146,11 @@ var init_logger = __esm({
|
|
|
143
146
|
});
|
|
144
147
|
|
|
145
148
|
// src/shared/crash-guard.ts
|
|
146
|
-
import
|
|
149
|
+
import fs3 from "fs";
|
|
147
150
|
function appendToLog(text) {
|
|
148
151
|
try {
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
fs3.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
153
|
+
fs3.appendFileSync(LOG_FILE, text, "utf-8");
|
|
151
154
|
} catch {
|
|
152
155
|
}
|
|
153
156
|
}
|
|
@@ -409,18 +412,31 @@ var init_session_label = __esm({
|
|
|
409
412
|
}
|
|
410
413
|
});
|
|
411
414
|
|
|
415
|
+
// src/shared/permission-key.ts
|
|
416
|
+
function permissionKey(sessionId2, requestId) {
|
|
417
|
+
return `${sessionId2}
|
|
418
|
+
${requestId}`;
|
|
419
|
+
}
|
|
420
|
+
var init_permission_key = __esm({
|
|
421
|
+
"src/shared/permission-key.ts"() {
|
|
422
|
+
"use strict";
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
412
426
|
// src/hub/telegram.ts
|
|
413
|
-
import
|
|
414
|
-
import
|
|
427
|
+
import fs4 from "fs";
|
|
428
|
+
import path4 from "path";
|
|
415
429
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
416
|
-
var TELEGRAM_API, TelegramBot;
|
|
430
|
+
var TELEGRAM_API, MAX_CHOICE_MESSAGES, TelegramBot;
|
|
417
431
|
var init_telegram = __esm({
|
|
418
432
|
"src/hub/telegram.ts"() {
|
|
419
433
|
"use strict";
|
|
420
434
|
init_logger();
|
|
421
435
|
init_constants();
|
|
422
436
|
init_session_label();
|
|
437
|
+
init_permission_key();
|
|
423
438
|
TELEGRAM_API = "https://api.telegram.org/bot";
|
|
439
|
+
MAX_CHOICE_MESSAGES = 200;
|
|
424
440
|
TelegramBot = class _TelegramBot {
|
|
425
441
|
config;
|
|
426
442
|
offset = 0;
|
|
@@ -434,6 +450,11 @@ var init_telegram = __esm({
|
|
|
434
450
|
onImageToSession;
|
|
435
451
|
// Callback: when a permission verdict arrives from Telegram
|
|
436
452
|
onPermissionVerdict;
|
|
453
|
+
// Callback: when a Codex approval choice arrives from Telegram
|
|
454
|
+
onChoiceVerdict;
|
|
455
|
+
// Telegram caps callback_data at 64 bytes, so buttons carry a short token instead of the ids.
|
|
456
|
+
choiceTokens = /* @__PURE__ */ new Map();
|
|
457
|
+
choiceMessages = /* @__PURE__ */ new Map();
|
|
437
458
|
// Callback: get current sessions list
|
|
438
459
|
getSessions;
|
|
439
460
|
// Pending messages for session selection
|
|
@@ -630,17 +651,17 @@ ${this.mdToHtml(message)}`;
|
|
|
630
651
|
const buffer = Buffer.from(await imgRes.arrayBuffer());
|
|
631
652
|
const ext = fileData.result.file_path.split(".").pop() || "jpg";
|
|
632
653
|
const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
633
|
-
|
|
654
|
+
fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
634
655
|
const filename = `${randomUUID2()}.${ext}`;
|
|
635
|
-
const filePath =
|
|
636
|
-
|
|
656
|
+
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
657
|
+
fs4.writeFileSync(filePath, buffer);
|
|
637
658
|
logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
|
|
638
659
|
if (this.onImageToSession) {
|
|
639
660
|
this.onImageToSession(sessionId2, filePath, mimeType, caption);
|
|
640
661
|
}
|
|
641
662
|
setTimeout(() => {
|
|
642
663
|
try {
|
|
643
|
-
|
|
664
|
+
fs4.unlinkSync(filePath);
|
|
644
665
|
} catch {
|
|
645
666
|
}
|
|
646
667
|
}, 5 * 60 * 1e3);
|
|
@@ -722,6 +743,71 @@ ${previewHtml}${truncNote}`;
|
|
|
722
743
|
};
|
|
723
744
|
await this.sendMessage(text, void 0, replyMarkup);
|
|
724
745
|
}
|
|
746
|
+
async sendChoiceRequest(sessionId2, sessionLabel2, requestId, toolName, description, inputPreview, choices) {
|
|
747
|
+
let preview = inputPreview;
|
|
748
|
+
try {
|
|
749
|
+
const p = JSON.parse(inputPreview);
|
|
750
|
+
if (typeof p.command === "string") preview = `$ ${p.command}`;
|
|
751
|
+
else if (typeof p.content === "string") preview = p.content;
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
const slice = preview.slice(0, 3e3);
|
|
755
|
+
const html = `\u26A0\uFE0F <b>Permission Request</b> \u2014 ${this.escHtml(sessionLabel2)}
|
|
756
|
+
|
|
757
|
+
\u{1F527} <b>${this.escHtml(toolName)}</b>` + (description ? `
|
|
758
|
+
${this.escHtml(description)}` : "") + (slice ? `
|
|
759
|
+
<pre>${this.escHtml(slice)}</pre>` : "") + (preview.length > slice.length ? "\n<i>...truncated</i>" : "");
|
|
760
|
+
const entry = { html };
|
|
761
|
+
this.choiceMessages.set(permissionKey(sessionId2, requestId), entry);
|
|
762
|
+
const rows = choices.map((c) => {
|
|
763
|
+
const token = randomUUID2().replace(/-/g, "").slice(0, 16);
|
|
764
|
+
this.choiceTokens.set(token, { sessionId: sessionId2, requestId, choiceId: c.id, label: c.label });
|
|
765
|
+
return [{ text: c.label, callback_data: `pc:${token}` }];
|
|
766
|
+
});
|
|
767
|
+
this.trimChoiceMessages();
|
|
768
|
+
const sent = await this.sendMessage(html, void 0, { inline_keyboard: rows });
|
|
769
|
+
if (!sent) return;
|
|
770
|
+
entry.messageId = sent.message_id;
|
|
771
|
+
if (entry.outcome) await this.editMessageText(this.config.chatId, sent.message_id, html + entry.outcome);
|
|
772
|
+
}
|
|
773
|
+
async resolveChoiceRequest(sessionId2, requestId, state) {
|
|
774
|
+
this.dropChoiceTokens(sessionId2, requestId);
|
|
775
|
+
const key = permissionKey(sessionId2, requestId);
|
|
776
|
+
const entry = this.choiceMessages.get(key);
|
|
777
|
+
if (!entry) return;
|
|
778
|
+
this.choiceMessages.delete(key);
|
|
779
|
+
entry.outcome = state === "resolved" ? "\n\n\u2705 <b>Resolved</b>" : "\n\n\u231B <b>Expired</b>";
|
|
780
|
+
if (entry.messageId !== void 0) await this.editMessageText(this.config.chatId, entry.messageId, entry.html + entry.outcome);
|
|
781
|
+
}
|
|
782
|
+
async handleChoiceCallback(query) {
|
|
783
|
+
const choice = this.choiceTokens.get(query.data.slice("pc:".length));
|
|
784
|
+
if (!choice) {
|
|
785
|
+
await this.answerCallbackQuery(query.id, "Expired");
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
this.dropChoiceTokens(choice.sessionId, choice.requestId);
|
|
789
|
+
this.onChoiceVerdict?.(choice.sessionId, choice.requestId, choice.choiceId);
|
|
790
|
+
await this.answerCallbackQuery(query.id, `Sent: ${choice.label}`);
|
|
791
|
+
const entry = this.choiceMessages.get(permissionKey(choice.sessionId, choice.requestId));
|
|
792
|
+
if (entry?.messageId !== void 0 && !entry.outcome) {
|
|
793
|
+
await this.editMessageText(this.config.chatId, entry.messageId, `${entry.html}
|
|
794
|
+
|
|
795
|
+
\u23F3 <b>Sent: ${this.escHtml(choice.label)}</b>`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
dropChoiceTokens(sessionId2, requestId) {
|
|
799
|
+
for (const [token, c] of this.choiceTokens) {
|
|
800
|
+
if (c.sessionId === sessionId2 && c.requestId === requestId) this.choiceTokens.delete(token);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
trimChoiceMessages() {
|
|
804
|
+
while (this.choiceMessages.size > MAX_CHOICE_MESSAGES) {
|
|
805
|
+
const oldest = this.choiceMessages.keys().next().value;
|
|
806
|
+
this.choiceMessages.delete(oldest);
|
|
807
|
+
const [sessionId2, requestId] = oldest.split("\n");
|
|
808
|
+
this.dropChoiceTokens(sessionId2, requestId);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
725
811
|
async handleCallbackQuery(query) {
|
|
726
812
|
if (!query.data) return;
|
|
727
813
|
if (String(query.message?.chat.id) !== String(this.config.chatId)) return;
|
|
@@ -729,6 +815,10 @@ ${previewHtml}${truncNote}`;
|
|
|
729
815
|
await this.handleSessionSelectCallback(query);
|
|
730
816
|
return;
|
|
731
817
|
}
|
|
818
|
+
if (query.data.startsWith("pc:")) {
|
|
819
|
+
await this.handleChoiceCallback(query);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
732
822
|
if (!query.data.startsWith("perm:")) return;
|
|
733
823
|
const parts = query.data.split(":");
|
|
734
824
|
if (parts.length < 4) return;
|
|
@@ -823,8 +913,8 @@ ${label}`);
|
|
|
823
913
|
css: "css",
|
|
824
914
|
scss: "scss"
|
|
825
915
|
};
|
|
826
|
-
langOf(
|
|
827
|
-
const m = /\.([a-z0-9]+)$/i.exec(
|
|
916
|
+
langOf(path10) {
|
|
917
|
+
const m = /\.([a-z0-9]+)$/i.exec(path10 || "");
|
|
828
918
|
return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
|
|
829
919
|
}
|
|
830
920
|
/** Convert markdown to Telegram HTML (escape first, then apply formatting) */
|
|
@@ -870,11 +960,11 @@ ${bodyLines.join("\n")}`;
|
|
|
870
960
|
});
|
|
871
961
|
|
|
872
962
|
// src/hub/codex-supervisor.ts
|
|
873
|
-
import { spawn } from "child_process";
|
|
874
|
-
import
|
|
875
|
-
import
|
|
963
|
+
import { spawn as spawn2 } from "child_process";
|
|
964
|
+
import fs5 from "fs";
|
|
965
|
+
import path5 from "path";
|
|
876
966
|
function resolveAdapterScript(baseDir) {
|
|
877
|
-
return [
|
|
967
|
+
return [path5.join(baseDir, "..", "codex", "main.js"), path5.join(baseDir, "codex", "main.js")].find((p) => fs5.existsSync(p));
|
|
878
968
|
}
|
|
879
969
|
var HEALTHY_RUN_MS, CodexSupervisor;
|
|
880
970
|
var init_codex_supervisor = __esm({
|
|
@@ -883,7 +973,7 @@ var init_codex_supervisor = __esm({
|
|
|
883
973
|
init_logger();
|
|
884
974
|
HEALTHY_RUN_MS = 6e4;
|
|
885
975
|
CodexSupervisor = class {
|
|
886
|
-
constructor(script, spawnFn =
|
|
976
|
+
constructor(script, spawnFn = spawn2, minDelayMs = 2e3, maxDelayMs = 6e4, stopGraceMs = 3e3) {
|
|
887
977
|
this.script = script;
|
|
888
978
|
this.spawnFn = spawnFn;
|
|
889
979
|
this.minDelayMs = minDelayMs;
|
|
@@ -1009,11 +1099,16 @@ __export(server_exports, {
|
|
|
1009
1099
|
HubServer: () => HubServer
|
|
1010
1100
|
});
|
|
1011
1101
|
import http from "http";
|
|
1012
|
-
import
|
|
1013
|
-
import
|
|
1102
|
+
import fs6 from "fs";
|
|
1103
|
+
import path6 from "path";
|
|
1014
1104
|
import { fileURLToPath } from "url";
|
|
1015
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
1105
|
+
import { WebSocketServer, WebSocket as WebSocket2 } from "ws";
|
|
1016
1106
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1107
|
+
function validChoices(choices) {
|
|
1108
|
+
if (!Array.isArray(choices)) return void 0;
|
|
1109
|
+
const valid = choices.filter((c) => typeof c?.id === "string" && typeof c?.label === "string");
|
|
1110
|
+
return valid.length ? valid : void 0;
|
|
1111
|
+
}
|
|
1017
1112
|
var __dirname, MAX_WS_PAYLOAD, HubServer;
|
|
1018
1113
|
var init_server = __esm({
|
|
1019
1114
|
"src/hub/server.ts"() {
|
|
@@ -1027,8 +1122,9 @@ var init_server = __esm({
|
|
|
1027
1122
|
init_config();
|
|
1028
1123
|
init_session_label();
|
|
1029
1124
|
init_crash_guard();
|
|
1125
|
+
init_permission_key();
|
|
1030
1126
|
init_auth();
|
|
1031
|
-
__dirname =
|
|
1127
|
+
__dirname = path6.dirname(fileURLToPath(import.meta.url));
|
|
1032
1128
|
MAX_WS_PAYLOAD = 16 * 1024 * 1024;
|
|
1033
1129
|
HubServer = class {
|
|
1034
1130
|
httpServer;
|
|
@@ -1048,6 +1144,8 @@ var init_server = __esm({
|
|
|
1048
1144
|
channelAlive = /* @__PURE__ */ new Map();
|
|
1049
1145
|
// sessionId -> alive flag
|
|
1050
1146
|
socketOwners = /* @__PURE__ */ new WeakMap();
|
|
1147
|
+
// Codex approvals carry their own choices; a response is forwarded only in the mode of the request it answers.
|
|
1148
|
+
choiceRequests = /* @__PURE__ */ new Map();
|
|
1051
1149
|
host;
|
|
1052
1150
|
port;
|
|
1053
1151
|
token;
|
|
@@ -1185,6 +1283,7 @@ var init_server = __esm({
|
|
|
1185
1283
|
this.channelSockets.delete(sessionId2);
|
|
1186
1284
|
this.localChannels.delete(sessionId2);
|
|
1187
1285
|
this.channelAlive.delete(sessionId2);
|
|
1286
|
+
this.expireChoices(sessionId2);
|
|
1188
1287
|
if (session) {
|
|
1189
1288
|
this.broadcastToDashboards({ type: "session_disconnected", sessionId: sessionId2 });
|
|
1190
1289
|
this.jsonResponse(res, 200, { ok: true });
|
|
@@ -1224,23 +1323,23 @@ var init_server = __esm({
|
|
|
1224
1323
|
return;
|
|
1225
1324
|
}
|
|
1226
1325
|
const candidates = [
|
|
1227
|
-
|
|
1326
|
+
path6.join(__dirname, "..", "dashboard", "index.html"),
|
|
1228
1327
|
// from dist/hub/
|
|
1229
|
-
|
|
1328
|
+
path6.join(__dirname, "dashboard", "index.html"),
|
|
1230
1329
|
// from dist/ (bundled index.js)
|
|
1231
|
-
|
|
1330
|
+
path6.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
1232
1331
|
// from dist/hub/ -> src/
|
|
1233
|
-
|
|
1332
|
+
path6.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
1234
1333
|
// from dist/ -> src/
|
|
1235
|
-
|
|
1334
|
+
path6.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
1236
1335
|
// from cwd
|
|
1237
|
-
|
|
1336
|
+
path6.join(process.cwd(), "src", "dashboard", "index.html")
|
|
1238
1337
|
// from cwd/src
|
|
1239
1338
|
];
|
|
1240
1339
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
1241
1340
|
for (const candidate of candidates) {
|
|
1242
|
-
if (
|
|
1243
|
-
const html =
|
|
1341
|
+
if (fs6.existsSync(candidate)) {
|
|
1342
|
+
const html = fs6.readFileSync(candidate, "utf-8");
|
|
1244
1343
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1245
1344
|
res.end(html);
|
|
1246
1345
|
return;
|
|
@@ -1275,7 +1374,7 @@ var init_server = __esm({
|
|
|
1275
1374
|
return;
|
|
1276
1375
|
}
|
|
1277
1376
|
const ws = this.channelSockets.get(sessionId2);
|
|
1278
|
-
if (!ws || ws.readyState !==
|
|
1377
|
+
if (!ws || ws.readyState !== WebSocket2.OPEN) {
|
|
1279
1378
|
this.jsonResponse(res, 404, { error: "Session not connected" });
|
|
1280
1379
|
return;
|
|
1281
1380
|
}
|
|
@@ -1328,6 +1427,7 @@ var init_server = __esm({
|
|
|
1328
1427
|
this.channelSockets.delete(sessionId2);
|
|
1329
1428
|
this.localChannels.delete(sessionId2);
|
|
1330
1429
|
this.channelAlive.delete(sessionId2);
|
|
1430
|
+
this.expireChoices(sessionId2);
|
|
1331
1431
|
logger.info(`Channel disconnected: ${sessionId2}`);
|
|
1332
1432
|
this.broadcastToDashboards({
|
|
1333
1433
|
type: "session_disconnected",
|
|
@@ -1415,7 +1515,22 @@ var init_server = __esm({
|
|
|
1415
1515
|
}
|
|
1416
1516
|
case "permission_request": {
|
|
1417
1517
|
this.sessions.updateActivity(msg.sessionId);
|
|
1518
|
+
const choices = validChoices(msg.choices);
|
|
1418
1519
|
logger.info(`Permission request [${msg.requestId}] from ${msg.sessionId}: ${msg.toolName}`);
|
|
1520
|
+
if (choices) {
|
|
1521
|
+
this.choiceRequests.set(permissionKey(msg.sessionId, msg.requestId), {
|
|
1522
|
+
request: {
|
|
1523
|
+
sessionId: msg.sessionId,
|
|
1524
|
+
requestId: msg.requestId,
|
|
1525
|
+
toolName: msg.toolName,
|
|
1526
|
+
description: msg.description,
|
|
1527
|
+
inputPreview: msg.inputPreview,
|
|
1528
|
+
timestamp: msg.timestamp,
|
|
1529
|
+
choices
|
|
1530
|
+
},
|
|
1531
|
+
choiceIds: new Set(choices.map((c) => c.id))
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1419
1534
|
this.broadcastToDashboards({
|
|
1420
1535
|
type: "permission_request",
|
|
1421
1536
|
sessionId: msg.sessionId,
|
|
@@ -1423,15 +1538,23 @@ var init_server = __esm({
|
|
|
1423
1538
|
toolName: msg.toolName,
|
|
1424
1539
|
description: msg.description,
|
|
1425
1540
|
inputPreview: msg.inputPreview,
|
|
1426
|
-
timestamp: msg.timestamp
|
|
1541
|
+
timestamp: msg.timestamp,
|
|
1542
|
+
...choices ? { choices } : {}
|
|
1427
1543
|
});
|
|
1428
1544
|
if (this.telegramBot) {
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1545
|
+
const label = this.getSessionLabel(this.sessions.get(msg.sessionId));
|
|
1546
|
+
if (choices) {
|
|
1547
|
+
void this.telegramBot.sendChoiceRequest(msg.sessionId, label, msg.requestId, msg.toolName, msg.description, msg.inputPreview, choices);
|
|
1548
|
+
} else {
|
|
1549
|
+
this.telegramBot.sendPermissionRequest(msg.sessionId, label, msg.requestId, msg.toolName, msg.description, msg.inputPreview);
|
|
1550
|
+
}
|
|
1432
1551
|
}
|
|
1433
1552
|
break;
|
|
1434
1553
|
}
|
|
1554
|
+
case "permission_resolved": {
|
|
1555
|
+
this.resolveChoice(msg.sessionId, msg.requestId, msg.state === "expired" ? "expired" : "resolved");
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1435
1558
|
}
|
|
1436
1559
|
}
|
|
1437
1560
|
// --- Dashboard WebSocket ---
|
|
@@ -1447,21 +1570,25 @@ var init_server = __esm({
|
|
|
1447
1570
|
sessions: this.sessions.getAll()
|
|
1448
1571
|
};
|
|
1449
1572
|
ws.send(JSON.stringify(sessionsMsg));
|
|
1573
|
+
const pendingMsg = {
|
|
1574
|
+
type: "permission_pending",
|
|
1575
|
+
requests: [...this.choiceRequests.values()].map((c) => c.request)
|
|
1576
|
+
};
|
|
1577
|
+
ws.send(JSON.stringify(pendingMsg));
|
|
1450
1578
|
ws.on("message", (data) => {
|
|
1451
1579
|
try {
|
|
1452
1580
|
const msg = JSON.parse(data.toString());
|
|
1453
1581
|
if (msg.type === "message_to_session") {
|
|
1454
1582
|
const channelWs = this.channelSockets.get(msg.sessionId);
|
|
1455
|
-
if (channelWs?.readyState ===
|
|
1583
|
+
if (channelWs?.readyState === WebSocket2.OPEN) {
|
|
1456
1584
|
channelWs.send(JSON.stringify({ ...msg, source: "dashboard" }));
|
|
1457
1585
|
}
|
|
1458
1586
|
} else if (msg.type === "image_upload") {
|
|
1459
1587
|
this.handleImageUpload(msg);
|
|
1460
1588
|
} else if (msg.type === "permission_response") {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
logger.info(`Permission verdict [${msg.requestId}]: ${msg.behavior} -> session ${msg.sessionId}`);
|
|
1589
|
+
if (this.forwardPermissionResponse(msg)) {
|
|
1590
|
+
const verdict = msg.choiceId !== void 0 ? `choice ${msg.choiceId}` : msg.behavior;
|
|
1591
|
+
logger.info(`Permission verdict [${msg.requestId}]: ${verdict} -> session ${msg.sessionId}`);
|
|
1465
1592
|
}
|
|
1466
1593
|
}
|
|
1467
1594
|
} catch {
|
|
@@ -1474,10 +1601,36 @@ var init_server = __esm({
|
|
|
1474
1601
|
});
|
|
1475
1602
|
}
|
|
1476
1603
|
// --- Helpers ---
|
|
1604
|
+
forwardPermissionResponse(msg) {
|
|
1605
|
+
const pending = this.choiceRequests.get(permissionKey(msg.sessionId, msg.requestId));
|
|
1606
|
+
let out;
|
|
1607
|
+
if (pending) {
|
|
1608
|
+
if (typeof msg.choiceId !== "string" || !pending.choiceIds.has(msg.choiceId)) return false;
|
|
1609
|
+
out = { type: "permission_response", sessionId: msg.sessionId, requestId: msg.requestId, choiceId: msg.choiceId };
|
|
1610
|
+
} else {
|
|
1611
|
+
if (msg.behavior !== "allow" && msg.behavior !== "deny" || msg.choiceId !== void 0) return false;
|
|
1612
|
+
if (this.sessions.get(msg.sessionId)?.agentKind === "codex") return false;
|
|
1613
|
+
out = { type: "permission_response", sessionId: msg.sessionId, requestId: msg.requestId, behavior: msg.behavior };
|
|
1614
|
+
}
|
|
1615
|
+
const channelWs = this.channelSockets.get(msg.sessionId);
|
|
1616
|
+
if (channelWs?.readyState !== WebSocket2.OPEN) return false;
|
|
1617
|
+
channelWs.send(JSON.stringify(out));
|
|
1618
|
+
return true;
|
|
1619
|
+
}
|
|
1620
|
+
resolveChoice(sessionId2, requestId, state) {
|
|
1621
|
+
if (!this.choiceRequests.delete(permissionKey(sessionId2, requestId))) return;
|
|
1622
|
+
this.broadcastToDashboards({ type: "permission_resolved", sessionId: sessionId2, requestId, state });
|
|
1623
|
+
void this.telegramBot?.resolveChoiceRequest(sessionId2, requestId, state);
|
|
1624
|
+
}
|
|
1625
|
+
expireChoices(sessionId2) {
|
|
1626
|
+
for (const pending of [...this.choiceRequests.values()]) {
|
|
1627
|
+
if (pending.request.sessionId === sessionId2) this.resolveChoice(sessionId2, pending.request.requestId, "expired");
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1477
1630
|
broadcastToDashboards(msg) {
|
|
1478
1631
|
const payload = JSON.stringify(msg);
|
|
1479
1632
|
for (const ws of this.dashboardSockets) {
|
|
1480
|
-
if (ws.readyState ===
|
|
1633
|
+
if (ws.readyState === WebSocket2.OPEN) {
|
|
1481
1634
|
ws.send(payload);
|
|
1482
1635
|
}
|
|
1483
1636
|
}
|
|
@@ -1489,7 +1642,7 @@ var init_server = __esm({
|
|
|
1489
1642
|
return;
|
|
1490
1643
|
}
|
|
1491
1644
|
const channelWs = this.channelSockets.get(sessionId2);
|
|
1492
|
-
if (!channelWs || channelWs.readyState !==
|
|
1645
|
+
if (!channelWs || channelWs.readyState !== WebSocket2.OPEN) return;
|
|
1493
1646
|
const allowedTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
1494
1647
|
if (!allowedTypes.includes(mimeType)) return;
|
|
1495
1648
|
const base64Data = imageData.replace(/^data:image\/\w+;base64,/, "");
|
|
@@ -1498,11 +1651,11 @@ var init_server = __esm({
|
|
|
1498
1651
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
1499
1652
|
return;
|
|
1500
1653
|
}
|
|
1501
|
-
|
|
1654
|
+
fs6.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
1502
1655
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
1503
1656
|
const filename = `${randomUUID3()}.${ext}`;
|
|
1504
|
-
const filePath =
|
|
1505
|
-
|
|
1657
|
+
const filePath = path6.join(UPLOADS_DIR, filename);
|
|
1658
|
+
fs6.writeFileSync(filePath, buffer);
|
|
1506
1659
|
const forwardMsg = {
|
|
1507
1660
|
type: "image_to_session",
|
|
1508
1661
|
sessionId: sessionId2,
|
|
@@ -1515,7 +1668,7 @@ var init_server = __esm({
|
|
|
1515
1668
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
1516
1669
|
setTimeout(() => {
|
|
1517
1670
|
try {
|
|
1518
|
-
|
|
1671
|
+
fs6.unlinkSync(filePath);
|
|
1519
1672
|
} catch {
|
|
1520
1673
|
}
|
|
1521
1674
|
}, 5 * 60 * 1e3);
|
|
@@ -1542,7 +1695,7 @@ var init_server = __esm({
|
|
|
1542
1695
|
this.telegramBot.getSessions = () => this.sessions.getAll();
|
|
1543
1696
|
this.telegramBot.onMessageToSession = (sessionId2, content) => {
|
|
1544
1697
|
const channelWs = this.channelSockets.get(sessionId2);
|
|
1545
|
-
if (channelWs?.readyState ===
|
|
1698
|
+
if (channelWs?.readyState === WebSocket2.OPEN) {
|
|
1546
1699
|
const msg = { type: "message_to_session", sessionId: sessionId2, content, source: "telegram" };
|
|
1547
1700
|
channelWs.send(JSON.stringify(msg));
|
|
1548
1701
|
logger.info(`Telegram message forwarded to session: ${sessionId2}`);
|
|
@@ -1550,21 +1703,23 @@ var init_server = __esm({
|
|
|
1550
1703
|
};
|
|
1551
1704
|
this.telegramBot.onImageToSession = (sessionId2, imagePath, mimeType, caption) => {
|
|
1552
1705
|
const channelWs = this.channelSockets.get(sessionId2);
|
|
1553
|
-
if (channelWs?.readyState ===
|
|
1706
|
+
if (channelWs?.readyState === WebSocket2.OPEN) {
|
|
1554
1707
|
const msg = { type: "image_to_session", sessionId: sessionId2, imagePath, mimeType, content: caption };
|
|
1555
1708
|
channelWs.send(JSON.stringify(msg));
|
|
1556
1709
|
logger.info(`Telegram photo forwarded to session: ${sessionId2}`);
|
|
1557
1710
|
}
|
|
1558
1711
|
};
|
|
1559
1712
|
this.telegramBot.onPermissionVerdict = (sessionId2, requestId, behavior) => {
|
|
1560
|
-
|
|
1561
|
-
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
1562
|
-
const msg = { type: "permission_response", sessionId: sessionId2, requestId, behavior };
|
|
1563
|
-
channelWs.send(JSON.stringify(msg));
|
|
1713
|
+
if (this.forwardPermissionResponse({ sessionId: sessionId2, requestId, behavior })) {
|
|
1564
1714
|
logger.info(`Telegram permission verdict [${requestId}]: ${behavior} -> session ${sessionId2}`);
|
|
1565
1715
|
}
|
|
1566
1716
|
this.broadcastToDashboards({ type: "permission_response", sessionId: sessionId2, requestId, behavior });
|
|
1567
1717
|
};
|
|
1718
|
+
this.telegramBot.onChoiceVerdict = (sessionId2, requestId, choiceId) => {
|
|
1719
|
+
if (this.forwardPermissionResponse({ sessionId: sessionId2, requestId, choiceId })) {
|
|
1720
|
+
logger.info(`Telegram choice [${requestId}]: ${choiceId} -> session ${sessionId2}`);
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1568
1723
|
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
1569
1724
|
this.telegramBot.startPolling();
|
|
1570
1725
|
logger.info("Telegram bot initialized");
|
|
@@ -1696,11 +1851,11 @@ var init_server = __esm({
|
|
|
1696
1851
|
}
|
|
1697
1852
|
cleanupUploads() {
|
|
1698
1853
|
try {
|
|
1699
|
-
if (!
|
|
1700
|
-
const files =
|
|
1854
|
+
if (!fs6.existsSync(UPLOADS_DIR)) return;
|
|
1855
|
+
const files = fs6.readdirSync(UPLOADS_DIR);
|
|
1701
1856
|
for (const file of files) {
|
|
1702
1857
|
try {
|
|
1703
|
-
|
|
1858
|
+
fs6.unlinkSync(path6.join(UPLOADS_DIR, file));
|
|
1704
1859
|
} catch {
|
|
1705
1860
|
}
|
|
1706
1861
|
}
|
|
@@ -1773,7 +1928,7 @@ Content-Length: 0\r
|
|
|
1773
1928
|
});
|
|
1774
1929
|
|
|
1775
1930
|
// src/channel/hub-client.ts
|
|
1776
|
-
import
|
|
1931
|
+
import WebSocket3 from "ws";
|
|
1777
1932
|
var HubClient;
|
|
1778
1933
|
var init_hub_client = __esm({
|
|
1779
1934
|
"src/channel/hub-client.ts"() {
|
|
@@ -1802,7 +1957,7 @@ var init_hub_client = __esm({
|
|
|
1802
1957
|
const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;
|
|
1803
1958
|
logger.debug(`Connecting to hub at ${url}`);
|
|
1804
1959
|
try {
|
|
1805
|
-
const ws = new
|
|
1960
|
+
const ws = new WebSocket3(url);
|
|
1806
1961
|
this.ws = ws;
|
|
1807
1962
|
ws.on("open", () => {
|
|
1808
1963
|
if (this.ws !== ws) return;
|
|
@@ -1842,7 +1997,7 @@ var init_hub_client = __esm({
|
|
|
1842
1997
|
}
|
|
1843
1998
|
}
|
|
1844
1999
|
send(msg) {
|
|
1845
|
-
if (this.connected && this.ws?.readyState ===
|
|
2000
|
+
if (this.connected && this.ws?.readyState === WebSocket3.OPEN) {
|
|
1846
2001
|
this.ws.send(JSON.stringify(msg));
|
|
1847
2002
|
} else {
|
|
1848
2003
|
if (this.queue.length < 100) {
|
|
@@ -1867,7 +2022,7 @@ var init_hub_client = __esm({
|
|
|
1867
2022
|
this.connected = false;
|
|
1868
2023
|
}
|
|
1869
2024
|
reregister() {
|
|
1870
|
-
if (this.connected && this.ws?.readyState ===
|
|
2025
|
+
if (this.connected && this.ws?.readyState === WebSocket3.OPEN) {
|
|
1871
2026
|
this.ws.send(JSON.stringify(this.registration()));
|
|
1872
2027
|
}
|
|
1873
2028
|
}
|
|
@@ -1899,9 +2054,9 @@ var init_hub_client = __esm({
|
|
|
1899
2054
|
});
|
|
1900
2055
|
|
|
1901
2056
|
// src/channel/peer-name.ts
|
|
1902
|
-
import
|
|
2057
|
+
import fs7 from "fs";
|
|
1903
2058
|
import os2 from "os";
|
|
1904
|
-
import
|
|
2059
|
+
import path7 from "path";
|
|
1905
2060
|
function findPeerName(records, env) {
|
|
1906
2061
|
const valid = records.filter((r) => typeof r === "object" && r !== null);
|
|
1907
2062
|
const nameOf = (match) => {
|
|
@@ -1921,18 +2076,18 @@ function readPeerName(env = process.env) {
|
|
|
1921
2076
|
sessionId: env.CLAUDE_CODE_SESSION_ID
|
|
1922
2077
|
};
|
|
1923
2078
|
if (!lookup.messagingSocket && !lookup.sessionId) return void 0;
|
|
1924
|
-
const configDir = env.CLAUDE_CONFIG_DIR ||
|
|
1925
|
-
const dir =
|
|
2079
|
+
const configDir = env.CLAUDE_CONFIG_DIR || path7.join(os2.homedir(), ".claude");
|
|
2080
|
+
const dir = path7.join(configDir, "sessions");
|
|
1926
2081
|
let files;
|
|
1927
2082
|
try {
|
|
1928
|
-
files =
|
|
2083
|
+
files = fs7.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
1929
2084
|
} catch {
|
|
1930
2085
|
return void 0;
|
|
1931
2086
|
}
|
|
1932
2087
|
const records = [];
|
|
1933
2088
|
for (const f of files) {
|
|
1934
2089
|
try {
|
|
1935
|
-
records.push(JSON.parse(
|
|
2090
|
+
records.push(JSON.parse(fs7.readFileSync(path7.join(dir, f), "utf8")));
|
|
1936
2091
|
} catch {
|
|
1937
2092
|
}
|
|
1938
2093
|
}
|
|
@@ -1955,7 +2110,7 @@ import {
|
|
|
1955
2110
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1956
2111
|
import { z } from "zod";
|
|
1957
2112
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1958
|
-
import
|
|
2113
|
+
import path8 from "path";
|
|
1959
2114
|
async function main() {
|
|
1960
2115
|
logger.info(`Starting MCP channel server (session: ${sessionId})`);
|
|
1961
2116
|
hubClient.connect();
|
|
@@ -2024,7 +2179,7 @@ var init_server2 = __esm({
|
|
|
2024
2179
|
init_hub_client();
|
|
2025
2180
|
init_peer_name2();
|
|
2026
2181
|
sessionId = randomUUID4();
|
|
2027
|
-
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ??
|
|
2182
|
+
sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path8.basename(process.cwd());
|
|
2028
2183
|
peerName = readPeerName();
|
|
2029
2184
|
server = new Server(
|
|
2030
2185
|
{
|
|
@@ -2200,15 +2355,35 @@ NOTIFICATIONS:
|
|
|
2200
2355
|
|
|
2201
2356
|
// src/cli.ts
|
|
2202
2357
|
init_config();
|
|
2358
|
+
import { spawn as spawn3 } from "child_process";
|
|
2359
|
+
import fs8 from "fs";
|
|
2360
|
+
import path9 from "path";
|
|
2361
|
+
import readline from "readline";
|
|
2362
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2363
|
+
|
|
2364
|
+
// src/codex/transport.ts
|
|
2365
|
+
init_logger();
|
|
2366
|
+
import { spawn } from "child_process";
|
|
2367
|
+
import fs2 from "fs";
|
|
2368
|
+
import path3 from "path";
|
|
2369
|
+
import { Duplex } from "stream";
|
|
2370
|
+
import WebSocket from "ws";
|
|
2371
|
+
function findOnPath(command, platform = process.platform, env = process.env) {
|
|
2372
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path3.delimiter).filter(Boolean);
|
|
2373
|
+
for (const ext of platform === "win32" ? [".exe", ".cmd"] : [""]) {
|
|
2374
|
+
for (const dir of dirs) {
|
|
2375
|
+
const file = path3.join(dir, command + ext);
|
|
2376
|
+
if (fs2.existsSync(file)) return file;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
return void 0;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// src/cli.ts
|
|
2203
2383
|
init_constants();
|
|
2204
2384
|
init_logger();
|
|
2205
2385
|
init_crash_guard();
|
|
2206
|
-
|
|
2207
|
-
import fs7 from "fs";
|
|
2208
|
-
import path8 from "path";
|
|
2209
|
-
import readline from "readline";
|
|
2210
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2211
|
-
var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
2386
|
+
var __dirname2 = path9.dirname(fileURLToPath2(import.meta.url));
|
|
2212
2387
|
function authHeaders(token) {
|
|
2213
2388
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
2214
2389
|
}
|
|
@@ -2240,8 +2415,8 @@ Quick start:
|
|
|
2240
2415
|
}
|
|
2241
2416
|
async function checkForUpdates() {
|
|
2242
2417
|
try {
|
|
2243
|
-
const pkgPath =
|
|
2244
|
-
const pkg = JSON.parse(
|
|
2418
|
+
const pkgPath = path9.join(__dirname2, "..", "package.json");
|
|
2419
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
2245
2420
|
const currentVersion = pkg.version;
|
|
2246
2421
|
const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
|
|
2247
2422
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -2264,25 +2439,25 @@ async function hubStart(daemon) {
|
|
|
2264
2439
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
2265
2440
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
2266
2441
|
checkForUpdates();
|
|
2267
|
-
if (
|
|
2268
|
-
const pid = parseInt(
|
|
2442
|
+
if (fs8.existsSync(PID_FILE)) {
|
|
2443
|
+
const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
2269
2444
|
if (isProcessRunning(pid)) {
|
|
2270
2445
|
console.log(`Hub is already running (PID: ${pid}). Dashboard: ${loginLink(displayHost, port, config2.hub.token)}`);
|
|
2271
2446
|
return;
|
|
2272
2447
|
}
|
|
2273
|
-
|
|
2448
|
+
fs8.unlinkSync(PID_FILE);
|
|
2274
2449
|
}
|
|
2275
2450
|
if (daemon) {
|
|
2276
2451
|
ensureConfigDir();
|
|
2277
|
-
const logFd =
|
|
2278
|
-
const hubScript =
|
|
2279
|
-
const child =
|
|
2452
|
+
const logFd = fs8.openSync(LOG_FILE, "a");
|
|
2453
|
+
const hubScript = path9.join(__dirname2, "hub", "server.js");
|
|
2454
|
+
const child = spawn3(process.execPath, [hubScript], {
|
|
2280
2455
|
detached: true,
|
|
2281
2456
|
stdio: ["ignore", logFd, logFd],
|
|
2282
2457
|
env: { ...process.env }
|
|
2283
2458
|
});
|
|
2284
2459
|
if (child.pid) {
|
|
2285
|
-
|
|
2460
|
+
fs8.writeFileSync(PID_FILE, String(child.pid), "utf-8");
|
|
2286
2461
|
child.unref();
|
|
2287
2462
|
console.log(`Hub started as daemon (PID: ${child.pid})`);
|
|
2288
2463
|
console.log(`Dashboard: ${loginLink(displayHost, port, config2.hub.token)}`);
|
|
@@ -2303,11 +2478,11 @@ async function hubStart(daemon) {
|
|
|
2303
2478
|
const hub = new HubServer2(config2);
|
|
2304
2479
|
await hub.start();
|
|
2305
2480
|
ensureConfigDir();
|
|
2306
|
-
|
|
2481
|
+
fs8.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
2307
2482
|
const shutdown = async () => {
|
|
2308
2483
|
console.log("\nShutting down...");
|
|
2309
2484
|
await hub.stop();
|
|
2310
|
-
if (
|
|
2485
|
+
if (fs8.existsSync(PID_FILE)) fs8.unlinkSync(PID_FILE);
|
|
2311
2486
|
process.exit(0);
|
|
2312
2487
|
};
|
|
2313
2488
|
process.on("SIGINT", shutdown);
|
|
@@ -2315,18 +2490,18 @@ async function hubStart(daemon) {
|
|
|
2315
2490
|
}
|
|
2316
2491
|
}
|
|
2317
2492
|
function hubStop() {
|
|
2318
|
-
if (!
|
|
2493
|
+
if (!fs8.existsSync(PID_FILE)) {
|
|
2319
2494
|
console.log("Hub is not running (no PID file found)");
|
|
2320
2495
|
return;
|
|
2321
2496
|
}
|
|
2322
|
-
const pid = parseInt(
|
|
2497
|
+
const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
2323
2498
|
try {
|
|
2324
2499
|
process.kill(pid, "SIGTERM");
|
|
2325
2500
|
console.log(`Hub stopped (PID: ${pid})`);
|
|
2326
2501
|
} catch {
|
|
2327
2502
|
console.log("Hub process not found (may have already stopped)");
|
|
2328
2503
|
}
|
|
2329
|
-
|
|
2504
|
+
fs8.unlinkSync(PID_FILE);
|
|
2330
2505
|
}
|
|
2331
2506
|
async function hubStatus() {
|
|
2332
2507
|
const config2 = loadConfig();
|
|
@@ -2334,8 +2509,8 @@ async function hubStatus() {
|
|
|
2334
2509
|
const port = config2.hub.port ?? DEFAULT_HUB_PORT;
|
|
2335
2510
|
const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
2336
2511
|
let pidInfo = "not running";
|
|
2337
|
-
if (
|
|
2338
|
-
const pid = parseInt(
|
|
2512
|
+
if (fs8.existsSync(PID_FILE)) {
|
|
2513
|
+
const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
2339
2514
|
if (isProcessRunning(pid)) {
|
|
2340
2515
|
pidInfo = `running (PID: ${pid})`;
|
|
2341
2516
|
} else {
|
|
@@ -2407,7 +2582,7 @@ function ask(question) {
|
|
|
2407
2582
|
}
|
|
2408
2583
|
async function init() {
|
|
2409
2584
|
const dir = process.cwd();
|
|
2410
|
-
const projectName =
|
|
2585
|
+
const projectName = path9.basename(dir);
|
|
2411
2586
|
console.log(`
|
|
2412
2587
|
claude-alarm init for "${projectName}"
|
|
2413
2588
|
`);
|
|
@@ -2427,11 +2602,11 @@ claude-alarm init for "${projectName}"
|
|
|
2427
2602
|
if (port) env.CLAUDE_ALARM_HUB_PORT = port;
|
|
2428
2603
|
if (token) env.CLAUDE_ALARM_HUB_TOKEN = token;
|
|
2429
2604
|
}
|
|
2430
|
-
const mcpPath =
|
|
2605
|
+
const mcpPath = path9.join(dir, ".mcp.json");
|
|
2431
2606
|
let mcpConfig = {};
|
|
2432
|
-
if (
|
|
2607
|
+
if (fs8.existsSync(mcpPath)) {
|
|
2433
2608
|
try {
|
|
2434
|
-
mcpConfig = JSON.parse(
|
|
2609
|
+
mcpConfig = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
|
|
2435
2610
|
} catch {
|
|
2436
2611
|
mcpConfig = {};
|
|
2437
2612
|
}
|
|
@@ -2442,7 +2617,7 @@ claude-alarm init for "${projectName}"
|
|
|
2442
2617
|
args: ["-y", "@delt/claude-alarm", "serve"],
|
|
2443
2618
|
env
|
|
2444
2619
|
};
|
|
2445
|
-
|
|
2620
|
+
fs8.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
2446
2621
|
console.log(`
|
|
2447
2622
|
\u2713 Created ${mcpPath}`);
|
|
2448
2623
|
if (remote.toLowerCase() !== "y") {
|
|
@@ -2463,6 +2638,11 @@ claude-alarm init for "${projectName}"
|
|
|
2463
2638
|
console.log(` claude-alarm hub start`);
|
|
2464
2639
|
}
|
|
2465
2640
|
console.log(` Dashboard: http://${displayHost}:${port}`);
|
|
2641
|
+
if (shouldOfferCodex(config2, findOnPath("codex") !== void 0)) {
|
|
2642
|
+
const answer = await ask("\nCodex is installed. Show Codex conversations on the dashboard too? (y/N): ");
|
|
2643
|
+
if (answer.toLowerCase() === "y") codexEnable(true);
|
|
2644
|
+
else setCodexEnabled(false);
|
|
2645
|
+
}
|
|
2466
2646
|
}
|
|
2467
2647
|
console.log(`
|
|
2468
2648
|
Next step:`);
|
|
@@ -2488,8 +2668,8 @@ function isProcessRunning(pid) {
|
|
|
2488
2668
|
}
|
|
2489
2669
|
}
|
|
2490
2670
|
function readCodexPid() {
|
|
2491
|
-
if (!
|
|
2492
|
-
const pid = parseInt(
|
|
2671
|
+
if (!fs8.existsSync(CODEX_PID_FILE)) return void 0;
|
|
2672
|
+
const pid = parseInt(fs8.readFileSync(CODEX_PID_FILE, "utf-8").trim(), 10);
|
|
2493
2673
|
return Number.isNaN(pid) ? void 0 : pid;
|
|
2494
2674
|
}
|
|
2495
2675
|
function codexEnable(enabled) {
|
|
@@ -2503,8 +2683,8 @@ function codexStart() {
|
|
|
2503
2683
|
return;
|
|
2504
2684
|
}
|
|
2505
2685
|
ensureConfigDir();
|
|
2506
|
-
const logFd =
|
|
2507
|
-
const child =
|
|
2686
|
+
const logFd = fs8.openSync(CODEX_LOG_FILE, "a");
|
|
2687
|
+
const child = spawn3(process.execPath, [path9.join(__dirname2, "codex", "main.js")], {
|
|
2508
2688
|
detached: true,
|
|
2509
2689
|
stdio: ["ignore", logFd, logFd],
|
|
2510
2690
|
env: { ...process.env }
|
|
@@ -2525,7 +2705,7 @@ function codexStop() {
|
|
|
2525
2705
|
console.log("Codex adapter process not found (may have already stopped)");
|
|
2526
2706
|
}
|
|
2527
2707
|
try {
|
|
2528
|
-
|
|
2708
|
+
fs8.unlinkSync(CODEX_PID_FILE);
|
|
2529
2709
|
} catch {
|
|
2530
2710
|
}
|
|
2531
2711
|
}
|