@delt/claude-alarm 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -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 fs2 from "fs";
149
+ import fs3 from "fs";
147
150
  function appendToLog(text) {
148
151
  try {
149
- fs2.mkdirSync(CONFIG_DIR, { recursive: true });
150
- fs2.appendFileSync(LOG_FILE, text, "utf-8");
152
+ fs3.mkdirSync(CONFIG_DIR, { recursive: true });
153
+ fs3.appendFileSync(LOG_FILE, text, "utf-8");
151
154
  } catch {
152
155
  }
153
156
  }
@@ -410,8 +413,8 @@ var init_session_label = __esm({
410
413
  });
411
414
 
412
415
  // src/hub/telegram.ts
413
- import fs3 from "fs";
414
- import path3 from "path";
416
+ import fs4 from "fs";
417
+ import path4 from "path";
415
418
  import { randomUUID as randomUUID2 } from "crypto";
416
419
  var TELEGRAM_API, TelegramBot;
417
420
  var init_telegram = __esm({
@@ -630,17 +633,17 @@ ${this.mdToHtml(message)}`;
630
633
  const buffer = Buffer.from(await imgRes.arrayBuffer());
631
634
  const ext = fileData.result.file_path.split(".").pop() || "jpg";
632
635
  const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
633
- fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
636
+ fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
634
637
  const filename = `${randomUUID2()}.${ext}`;
635
- const filePath = path3.join(UPLOADS_DIR, filename);
636
- fs3.writeFileSync(filePath, buffer);
638
+ const filePath = path4.join(UPLOADS_DIR, filename);
639
+ fs4.writeFileSync(filePath, buffer);
637
640
  logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
638
641
  if (this.onImageToSession) {
639
642
  this.onImageToSession(sessionId2, filePath, mimeType, caption);
640
643
  }
641
644
  setTimeout(() => {
642
645
  try {
643
- fs3.unlinkSync(filePath);
646
+ fs4.unlinkSync(filePath);
644
647
  } catch {
645
648
  }
646
649
  }, 5 * 60 * 1e3);
@@ -823,8 +826,8 @@ ${label}`);
823
826
  css: "css",
824
827
  scss: "scss"
825
828
  };
826
- langOf(path9) {
827
- const m = /\.([a-z0-9]+)$/i.exec(path9 || "");
829
+ langOf(path10) {
830
+ const m = /\.([a-z0-9]+)$/i.exec(path10 || "");
828
831
  return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
829
832
  }
830
833
  /** Convert markdown to Telegram HTML (escape first, then apply formatting) */
@@ -870,11 +873,11 @@ ${bodyLines.join("\n")}`;
870
873
  });
871
874
 
872
875
  // src/hub/codex-supervisor.ts
873
- import { spawn } from "child_process";
874
- import fs4 from "fs";
875
- import path4 from "path";
876
+ import { spawn as spawn2 } from "child_process";
877
+ import fs5 from "fs";
878
+ import path5 from "path";
876
879
  function resolveAdapterScript(baseDir) {
877
- return [path4.join(baseDir, "..", "codex", "main.js"), path4.join(baseDir, "codex", "main.js")].find((p) => fs4.existsSync(p));
880
+ return [path5.join(baseDir, "..", "codex", "main.js"), path5.join(baseDir, "codex", "main.js")].find((p) => fs5.existsSync(p));
878
881
  }
879
882
  var HEALTHY_RUN_MS, CodexSupervisor;
880
883
  var init_codex_supervisor = __esm({
@@ -883,7 +886,7 @@ var init_codex_supervisor = __esm({
883
886
  init_logger();
884
887
  HEALTHY_RUN_MS = 6e4;
885
888
  CodexSupervisor = class {
886
- constructor(script, spawnFn = spawn, minDelayMs = 2e3, maxDelayMs = 6e4, stopGraceMs = 3e3) {
889
+ constructor(script, spawnFn = spawn2, minDelayMs = 2e3, maxDelayMs = 6e4, stopGraceMs = 3e3) {
887
890
  this.script = script;
888
891
  this.spawnFn = spawnFn;
889
892
  this.minDelayMs = minDelayMs;
@@ -1009,10 +1012,10 @@ __export(server_exports, {
1009
1012
  HubServer: () => HubServer
1010
1013
  });
1011
1014
  import http from "http";
1012
- import fs5 from "fs";
1013
- import path5 from "path";
1015
+ import fs6 from "fs";
1016
+ import path6 from "path";
1014
1017
  import { fileURLToPath } from "url";
1015
- import { WebSocketServer, WebSocket } from "ws";
1018
+ import { WebSocketServer, WebSocket as WebSocket2 } from "ws";
1016
1019
  import { randomUUID as randomUUID3 } from "crypto";
1017
1020
  var __dirname, MAX_WS_PAYLOAD, HubServer;
1018
1021
  var init_server = __esm({
@@ -1028,7 +1031,7 @@ var init_server = __esm({
1028
1031
  init_session_label();
1029
1032
  init_crash_guard();
1030
1033
  init_auth();
1031
- __dirname = path5.dirname(fileURLToPath(import.meta.url));
1034
+ __dirname = path6.dirname(fileURLToPath(import.meta.url));
1032
1035
  MAX_WS_PAYLOAD = 16 * 1024 * 1024;
1033
1036
  HubServer = class {
1034
1037
  httpServer;
@@ -1224,23 +1227,23 @@ var init_server = __esm({
1224
1227
  return;
1225
1228
  }
1226
1229
  const candidates = [
1227
- path5.join(__dirname, "..", "dashboard", "index.html"),
1230
+ path6.join(__dirname, "..", "dashboard", "index.html"),
1228
1231
  // from dist/hub/
1229
- path5.join(__dirname, "dashboard", "index.html"),
1232
+ path6.join(__dirname, "dashboard", "index.html"),
1230
1233
  // from dist/ (bundled index.js)
1231
- path5.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
1234
+ path6.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
1232
1235
  // from dist/hub/ -> src/
1233
- path5.join(__dirname, "..", "src", "dashboard", "index.html"),
1236
+ path6.join(__dirname, "..", "src", "dashboard", "index.html"),
1234
1237
  // from dist/ -> src/
1235
- path5.join(process.cwd(), "dist", "dashboard", "index.html"),
1238
+ path6.join(process.cwd(), "dist", "dashboard", "index.html"),
1236
1239
  // from cwd
1237
- path5.join(process.cwd(), "src", "dashboard", "index.html")
1240
+ path6.join(process.cwd(), "src", "dashboard", "index.html")
1238
1241
  // from cwd/src
1239
1242
  ];
1240
1243
  logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
1241
1244
  for (const candidate of candidates) {
1242
- if (fs5.existsSync(candidate)) {
1243
- const html = fs5.readFileSync(candidate, "utf-8");
1245
+ if (fs6.existsSync(candidate)) {
1246
+ const html = fs6.readFileSync(candidate, "utf-8");
1244
1247
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1245
1248
  res.end(html);
1246
1249
  return;
@@ -1275,7 +1278,7 @@ var init_server = __esm({
1275
1278
  return;
1276
1279
  }
1277
1280
  const ws = this.channelSockets.get(sessionId2);
1278
- if (!ws || ws.readyState !== WebSocket.OPEN) {
1281
+ if (!ws || ws.readyState !== WebSocket2.OPEN) {
1279
1282
  this.jsonResponse(res, 404, { error: "Session not connected" });
1280
1283
  return;
1281
1284
  }
@@ -1452,14 +1455,14 @@ var init_server = __esm({
1452
1455
  const msg = JSON.parse(data.toString());
1453
1456
  if (msg.type === "message_to_session") {
1454
1457
  const channelWs = this.channelSockets.get(msg.sessionId);
1455
- if (channelWs?.readyState === WebSocket.OPEN) {
1458
+ if (channelWs?.readyState === WebSocket2.OPEN) {
1456
1459
  channelWs.send(JSON.stringify({ ...msg, source: "dashboard" }));
1457
1460
  }
1458
1461
  } else if (msg.type === "image_upload") {
1459
1462
  this.handleImageUpload(msg);
1460
1463
  } else if (msg.type === "permission_response") {
1461
1464
  const channelWs = this.channelSockets.get(msg.sessionId);
1462
- if (channelWs?.readyState === WebSocket.OPEN) {
1465
+ if (channelWs?.readyState === WebSocket2.OPEN) {
1463
1466
  channelWs.send(JSON.stringify(msg));
1464
1467
  logger.info(`Permission verdict [${msg.requestId}]: ${msg.behavior} -> session ${msg.sessionId}`);
1465
1468
  }
@@ -1477,7 +1480,7 @@ var init_server = __esm({
1477
1480
  broadcastToDashboards(msg) {
1478
1481
  const payload = JSON.stringify(msg);
1479
1482
  for (const ws of this.dashboardSockets) {
1480
- if (ws.readyState === WebSocket.OPEN) {
1483
+ if (ws.readyState === WebSocket2.OPEN) {
1481
1484
  ws.send(payload);
1482
1485
  }
1483
1486
  }
@@ -1489,7 +1492,7 @@ var init_server = __esm({
1489
1492
  return;
1490
1493
  }
1491
1494
  const channelWs = this.channelSockets.get(sessionId2);
1492
- if (!channelWs || channelWs.readyState !== WebSocket.OPEN) return;
1495
+ if (!channelWs || channelWs.readyState !== WebSocket2.OPEN) return;
1493
1496
  const allowedTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"];
1494
1497
  if (!allowedTypes.includes(mimeType)) return;
1495
1498
  const base64Data = imageData.replace(/^data:image\/\w+;base64,/, "");
@@ -1498,11 +1501,11 @@ var init_server = __esm({
1498
1501
  logger.warn("Image upload rejected: exceeds 10MB");
1499
1502
  return;
1500
1503
  }
1501
- fs5.mkdirSync(UPLOADS_DIR, { recursive: true });
1504
+ fs6.mkdirSync(UPLOADS_DIR, { recursive: true });
1502
1505
  const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
1503
1506
  const filename = `${randomUUID3()}.${ext}`;
1504
- const filePath = path5.join(UPLOADS_DIR, filename);
1505
- fs5.writeFileSync(filePath, buffer);
1507
+ const filePath = path6.join(UPLOADS_DIR, filename);
1508
+ fs6.writeFileSync(filePath, buffer);
1506
1509
  const forwardMsg = {
1507
1510
  type: "image_to_session",
1508
1511
  sessionId: sessionId2,
@@ -1515,7 +1518,7 @@ var init_server = __esm({
1515
1518
  logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
1516
1519
  setTimeout(() => {
1517
1520
  try {
1518
- fs5.unlinkSync(filePath);
1521
+ fs6.unlinkSync(filePath);
1519
1522
  } catch {
1520
1523
  }
1521
1524
  }, 5 * 60 * 1e3);
@@ -1542,7 +1545,7 @@ var init_server = __esm({
1542
1545
  this.telegramBot.getSessions = () => this.sessions.getAll();
1543
1546
  this.telegramBot.onMessageToSession = (sessionId2, content) => {
1544
1547
  const channelWs = this.channelSockets.get(sessionId2);
1545
- if (channelWs?.readyState === WebSocket.OPEN) {
1548
+ if (channelWs?.readyState === WebSocket2.OPEN) {
1546
1549
  const msg = { type: "message_to_session", sessionId: sessionId2, content, source: "telegram" };
1547
1550
  channelWs.send(JSON.stringify(msg));
1548
1551
  logger.info(`Telegram message forwarded to session: ${sessionId2}`);
@@ -1550,7 +1553,7 @@ var init_server = __esm({
1550
1553
  };
1551
1554
  this.telegramBot.onImageToSession = (sessionId2, imagePath, mimeType, caption) => {
1552
1555
  const channelWs = this.channelSockets.get(sessionId2);
1553
- if (channelWs?.readyState === WebSocket.OPEN) {
1556
+ if (channelWs?.readyState === WebSocket2.OPEN) {
1554
1557
  const msg = { type: "image_to_session", sessionId: sessionId2, imagePath, mimeType, content: caption };
1555
1558
  channelWs.send(JSON.stringify(msg));
1556
1559
  logger.info(`Telegram photo forwarded to session: ${sessionId2}`);
@@ -1558,7 +1561,7 @@ var init_server = __esm({
1558
1561
  };
1559
1562
  this.telegramBot.onPermissionVerdict = (sessionId2, requestId, behavior) => {
1560
1563
  const channelWs = this.channelSockets.get(sessionId2);
1561
- if (channelWs?.readyState === WebSocket.OPEN) {
1564
+ if (channelWs?.readyState === WebSocket2.OPEN) {
1562
1565
  const msg = { type: "permission_response", sessionId: sessionId2, requestId, behavior };
1563
1566
  channelWs.send(JSON.stringify(msg));
1564
1567
  logger.info(`Telegram permission verdict [${requestId}]: ${behavior} -> session ${sessionId2}`);
@@ -1696,11 +1699,11 @@ var init_server = __esm({
1696
1699
  }
1697
1700
  cleanupUploads() {
1698
1701
  try {
1699
- if (!fs5.existsSync(UPLOADS_DIR)) return;
1700
- const files = fs5.readdirSync(UPLOADS_DIR);
1702
+ if (!fs6.existsSync(UPLOADS_DIR)) return;
1703
+ const files = fs6.readdirSync(UPLOADS_DIR);
1701
1704
  for (const file of files) {
1702
1705
  try {
1703
- fs5.unlinkSync(path5.join(UPLOADS_DIR, file));
1706
+ fs6.unlinkSync(path6.join(UPLOADS_DIR, file));
1704
1707
  } catch {
1705
1708
  }
1706
1709
  }
@@ -1773,7 +1776,7 @@ Content-Length: 0\r
1773
1776
  });
1774
1777
 
1775
1778
  // src/channel/hub-client.ts
1776
- import WebSocket2 from "ws";
1779
+ import WebSocket3 from "ws";
1777
1780
  var HubClient;
1778
1781
  var init_hub_client = __esm({
1779
1782
  "src/channel/hub-client.ts"() {
@@ -1802,7 +1805,7 @@ var init_hub_client = __esm({
1802
1805
  const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;
1803
1806
  logger.debug(`Connecting to hub at ${url}`);
1804
1807
  try {
1805
- const ws = new WebSocket2(url);
1808
+ const ws = new WebSocket3(url);
1806
1809
  this.ws = ws;
1807
1810
  ws.on("open", () => {
1808
1811
  if (this.ws !== ws) return;
@@ -1842,7 +1845,7 @@ var init_hub_client = __esm({
1842
1845
  }
1843
1846
  }
1844
1847
  send(msg) {
1845
- if (this.connected && this.ws?.readyState === WebSocket2.OPEN) {
1848
+ if (this.connected && this.ws?.readyState === WebSocket3.OPEN) {
1846
1849
  this.ws.send(JSON.stringify(msg));
1847
1850
  } else {
1848
1851
  if (this.queue.length < 100) {
@@ -1867,7 +1870,7 @@ var init_hub_client = __esm({
1867
1870
  this.connected = false;
1868
1871
  }
1869
1872
  reregister() {
1870
- if (this.connected && this.ws?.readyState === WebSocket2.OPEN) {
1873
+ if (this.connected && this.ws?.readyState === WebSocket3.OPEN) {
1871
1874
  this.ws.send(JSON.stringify(this.registration()));
1872
1875
  }
1873
1876
  }
@@ -1899,9 +1902,9 @@ var init_hub_client = __esm({
1899
1902
  });
1900
1903
 
1901
1904
  // src/channel/peer-name.ts
1902
- import fs6 from "fs";
1905
+ import fs7 from "fs";
1903
1906
  import os2 from "os";
1904
- import path6 from "path";
1907
+ import path7 from "path";
1905
1908
  function findPeerName(records, env) {
1906
1909
  const valid = records.filter((r) => typeof r === "object" && r !== null);
1907
1910
  const nameOf = (match) => {
@@ -1921,18 +1924,18 @@ function readPeerName(env = process.env) {
1921
1924
  sessionId: env.CLAUDE_CODE_SESSION_ID
1922
1925
  };
1923
1926
  if (!lookup.messagingSocket && !lookup.sessionId) return void 0;
1924
- const configDir = env.CLAUDE_CONFIG_DIR || path6.join(os2.homedir(), ".claude");
1925
- const dir = path6.join(configDir, "sessions");
1927
+ const configDir = env.CLAUDE_CONFIG_DIR || path7.join(os2.homedir(), ".claude");
1928
+ const dir = path7.join(configDir, "sessions");
1926
1929
  let files;
1927
1930
  try {
1928
- files = fs6.readdirSync(dir).filter((f) => f.endsWith(".json"));
1931
+ files = fs7.readdirSync(dir).filter((f) => f.endsWith(".json"));
1929
1932
  } catch {
1930
1933
  return void 0;
1931
1934
  }
1932
1935
  const records = [];
1933
1936
  for (const f of files) {
1934
1937
  try {
1935
- records.push(JSON.parse(fs6.readFileSync(path6.join(dir, f), "utf8")));
1938
+ records.push(JSON.parse(fs7.readFileSync(path7.join(dir, f), "utf8")));
1936
1939
  } catch {
1937
1940
  }
1938
1941
  }
@@ -1955,7 +1958,7 @@ import {
1955
1958
  } from "@modelcontextprotocol/sdk/types.js";
1956
1959
  import { z } from "zod";
1957
1960
  import { randomUUID as randomUUID4 } from "crypto";
1958
- import path7 from "path";
1961
+ import path8 from "path";
1959
1962
  async function main() {
1960
1963
  logger.info(`Starting MCP channel server (session: ${sessionId})`);
1961
1964
  hubClient.connect();
@@ -2024,7 +2027,7 @@ var init_server2 = __esm({
2024
2027
  init_hub_client();
2025
2028
  init_peer_name2();
2026
2029
  sessionId = randomUUID4();
2027
- sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path7.basename(process.cwd());
2030
+ sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path8.basename(process.cwd());
2028
2031
  peerName = readPeerName();
2029
2032
  server = new Server(
2030
2033
  {
@@ -2200,15 +2203,35 @@ NOTIFICATIONS:
2200
2203
 
2201
2204
  // src/cli.ts
2202
2205
  init_config();
2206
+ import { spawn as spawn3 } from "child_process";
2207
+ import fs8 from "fs";
2208
+ import path9 from "path";
2209
+ import readline from "readline";
2210
+ import { fileURLToPath as fileURLToPath2 } from "url";
2211
+
2212
+ // src/codex/transport.ts
2213
+ init_logger();
2214
+ import { spawn } from "child_process";
2215
+ import fs2 from "fs";
2216
+ import path3 from "path";
2217
+ import { Duplex } from "stream";
2218
+ import WebSocket from "ws";
2219
+ function findOnPath(command, platform = process.platform, env = process.env) {
2220
+ const dirs = (env.PATH ?? env.Path ?? "").split(path3.delimiter).filter(Boolean);
2221
+ for (const ext of platform === "win32" ? [".exe", ".cmd"] : [""]) {
2222
+ for (const dir of dirs) {
2223
+ const file = path3.join(dir, command + ext);
2224
+ if (fs2.existsSync(file)) return file;
2225
+ }
2226
+ }
2227
+ return void 0;
2228
+ }
2229
+
2230
+ // src/cli.ts
2203
2231
  init_constants();
2204
2232
  init_logger();
2205
2233
  init_crash_guard();
2206
- import { spawn as spawn2 } from "child_process";
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));
2234
+ var __dirname2 = path9.dirname(fileURLToPath2(import.meta.url));
2212
2235
  function authHeaders(token) {
2213
2236
  return token ? { Authorization: `Bearer ${token}` } : {};
2214
2237
  }
@@ -2240,8 +2263,8 @@ Quick start:
2240
2263
  }
2241
2264
  async function checkForUpdates() {
2242
2265
  try {
2243
- const pkgPath = path8.join(__dirname2, "..", "package.json");
2244
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
2266
+ const pkgPath = path9.join(__dirname2, "..", "package.json");
2267
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
2245
2268
  const currentVersion = pkg.version;
2246
2269
  const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
2247
2270
  signal: AbortSignal.timeout(3e3)
@@ -2264,25 +2287,25 @@ async function hubStart(daemon) {
2264
2287
  const port = config2.hub.port ?? DEFAULT_HUB_PORT;
2265
2288
  const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
2266
2289
  checkForUpdates();
2267
- if (fs7.existsSync(PID_FILE)) {
2268
- const pid = parseInt(fs7.readFileSync(PID_FILE, "utf-8").trim(), 10);
2290
+ if (fs8.existsSync(PID_FILE)) {
2291
+ const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
2269
2292
  if (isProcessRunning(pid)) {
2270
2293
  console.log(`Hub is already running (PID: ${pid}). Dashboard: ${loginLink(displayHost, port, config2.hub.token)}`);
2271
2294
  return;
2272
2295
  }
2273
- fs7.unlinkSync(PID_FILE);
2296
+ fs8.unlinkSync(PID_FILE);
2274
2297
  }
2275
2298
  if (daemon) {
2276
2299
  ensureConfigDir();
2277
- const logFd = fs7.openSync(LOG_FILE, "a");
2278
- const hubScript = path8.join(__dirname2, "hub", "server.js");
2279
- const child = spawn2(process.execPath, [hubScript], {
2300
+ const logFd = fs8.openSync(LOG_FILE, "a");
2301
+ const hubScript = path9.join(__dirname2, "hub", "server.js");
2302
+ const child = spawn3(process.execPath, [hubScript], {
2280
2303
  detached: true,
2281
2304
  stdio: ["ignore", logFd, logFd],
2282
2305
  env: { ...process.env }
2283
2306
  });
2284
2307
  if (child.pid) {
2285
- fs7.writeFileSync(PID_FILE, String(child.pid), "utf-8");
2308
+ fs8.writeFileSync(PID_FILE, String(child.pid), "utf-8");
2286
2309
  child.unref();
2287
2310
  console.log(`Hub started as daemon (PID: ${child.pid})`);
2288
2311
  console.log(`Dashboard: ${loginLink(displayHost, port, config2.hub.token)}`);
@@ -2303,11 +2326,11 @@ async function hubStart(daemon) {
2303
2326
  const hub = new HubServer2(config2);
2304
2327
  await hub.start();
2305
2328
  ensureConfigDir();
2306
- fs7.writeFileSync(PID_FILE, String(process.pid), "utf-8");
2329
+ fs8.writeFileSync(PID_FILE, String(process.pid), "utf-8");
2307
2330
  const shutdown = async () => {
2308
2331
  console.log("\nShutting down...");
2309
2332
  await hub.stop();
2310
- if (fs7.existsSync(PID_FILE)) fs7.unlinkSync(PID_FILE);
2333
+ if (fs8.existsSync(PID_FILE)) fs8.unlinkSync(PID_FILE);
2311
2334
  process.exit(0);
2312
2335
  };
2313
2336
  process.on("SIGINT", shutdown);
@@ -2315,18 +2338,18 @@ async function hubStart(daemon) {
2315
2338
  }
2316
2339
  }
2317
2340
  function hubStop() {
2318
- if (!fs7.existsSync(PID_FILE)) {
2341
+ if (!fs8.existsSync(PID_FILE)) {
2319
2342
  console.log("Hub is not running (no PID file found)");
2320
2343
  return;
2321
2344
  }
2322
- const pid = parseInt(fs7.readFileSync(PID_FILE, "utf-8").trim(), 10);
2345
+ const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
2323
2346
  try {
2324
2347
  process.kill(pid, "SIGTERM");
2325
2348
  console.log(`Hub stopped (PID: ${pid})`);
2326
2349
  } catch {
2327
2350
  console.log("Hub process not found (may have already stopped)");
2328
2351
  }
2329
- fs7.unlinkSync(PID_FILE);
2352
+ fs8.unlinkSync(PID_FILE);
2330
2353
  }
2331
2354
  async function hubStatus() {
2332
2355
  const config2 = loadConfig();
@@ -2334,8 +2357,8 @@ async function hubStatus() {
2334
2357
  const port = config2.hub.port ?? DEFAULT_HUB_PORT;
2335
2358
  const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
2336
2359
  let pidInfo = "not running";
2337
- if (fs7.existsSync(PID_FILE)) {
2338
- const pid = parseInt(fs7.readFileSync(PID_FILE, "utf-8").trim(), 10);
2360
+ if (fs8.existsSync(PID_FILE)) {
2361
+ const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
2339
2362
  if (isProcessRunning(pid)) {
2340
2363
  pidInfo = `running (PID: ${pid})`;
2341
2364
  } else {
@@ -2407,7 +2430,7 @@ function ask(question) {
2407
2430
  }
2408
2431
  async function init() {
2409
2432
  const dir = process.cwd();
2410
- const projectName = path8.basename(dir);
2433
+ const projectName = path9.basename(dir);
2411
2434
  console.log(`
2412
2435
  claude-alarm init for "${projectName}"
2413
2436
  `);
@@ -2427,11 +2450,11 @@ claude-alarm init for "${projectName}"
2427
2450
  if (port) env.CLAUDE_ALARM_HUB_PORT = port;
2428
2451
  if (token) env.CLAUDE_ALARM_HUB_TOKEN = token;
2429
2452
  }
2430
- const mcpPath = path8.join(dir, ".mcp.json");
2453
+ const mcpPath = path9.join(dir, ".mcp.json");
2431
2454
  let mcpConfig = {};
2432
- if (fs7.existsSync(mcpPath)) {
2455
+ if (fs8.existsSync(mcpPath)) {
2433
2456
  try {
2434
- mcpConfig = JSON.parse(fs7.readFileSync(mcpPath, "utf-8"));
2457
+ mcpConfig = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
2435
2458
  } catch {
2436
2459
  mcpConfig = {};
2437
2460
  }
@@ -2442,7 +2465,7 @@ claude-alarm init for "${projectName}"
2442
2465
  args: ["-y", "@delt/claude-alarm", "serve"],
2443
2466
  env
2444
2467
  };
2445
- fs7.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
2468
+ fs8.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
2446
2469
  console.log(`
2447
2470
  \u2713 Created ${mcpPath}`);
2448
2471
  if (remote.toLowerCase() !== "y") {
@@ -2463,6 +2486,11 @@ claude-alarm init for "${projectName}"
2463
2486
  console.log(` claude-alarm hub start`);
2464
2487
  }
2465
2488
  console.log(` Dashboard: http://${displayHost}:${port}`);
2489
+ if (shouldOfferCodex(config2, findOnPath("codex") !== void 0)) {
2490
+ const answer = await ask("\nCodex is installed. Show Codex conversations on the dashboard too? (y/N): ");
2491
+ if (answer.toLowerCase() === "y") codexEnable(true);
2492
+ else setCodexEnabled(false);
2493
+ }
2466
2494
  }
2467
2495
  console.log(`
2468
2496
  Next step:`);
@@ -2488,8 +2516,8 @@ function isProcessRunning(pid) {
2488
2516
  }
2489
2517
  }
2490
2518
  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);
2519
+ if (!fs8.existsSync(CODEX_PID_FILE)) return void 0;
2520
+ const pid = parseInt(fs8.readFileSync(CODEX_PID_FILE, "utf-8").trim(), 10);
2493
2521
  return Number.isNaN(pid) ? void 0 : pid;
2494
2522
  }
2495
2523
  function codexEnable(enabled) {
@@ -2503,8 +2531,8 @@ function codexStart() {
2503
2531
  return;
2504
2532
  }
2505
2533
  ensureConfigDir();
2506
- const logFd = fs7.openSync(CODEX_LOG_FILE, "a");
2507
- const child = spawn2(process.execPath, [path8.join(__dirname2, "codex", "main.js")], {
2534
+ const logFd = fs8.openSync(CODEX_LOG_FILE, "a");
2535
+ const child = spawn3(process.execPath, [path9.join(__dirname2, "codex", "main.js")], {
2508
2536
  detached: true,
2509
2537
  stdio: ["ignore", logFd, logFd],
2510
2538
  env: { ...process.env }
@@ -2525,7 +2553,7 @@ function codexStop() {
2525
2553
  console.log("Codex adapter process not found (may have already stopped)");
2526
2554
  }
2527
2555
  try {
2528
- fs7.unlinkSync(CODEX_PID_FILE);
2556
+ fs8.unlinkSync(CODEX_PID_FILE);
2529
2557
  } catch {
2530
2558
  }
2531
2559
  }