@delt/claude-alarm 0.5.3 → 0.5.5

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 CHANGED
@@ -140,17 +140,22 @@ Configure via dashboard (⚙ Settings → Webhook tab) or in config:
140
140
 
141
141
  ### Telegram Bot
142
142
 
143
- Two-way messaging with Claude sessions via Telegram:
143
+ Two-way messaging with Claude sessions via Telegram — text and images.
144
+
145
+ **Setup (guided wizard in dashboard):**
144
146
 
145
147
  1. Create a bot with [@BotFather](https://t.me/BotFather) on Telegram
146
- 2. Send any message to your bot, then visit `https://api.telegram.org/bot<TOKEN>/getUpdates` to find your Chat ID
147
- 3. Open dashboard SettingsTelegram tab
148
- 4. Enter Bot Token + Chat ID → TestSave
148
+ 2. Open dashboard Settings Telegram tab
149
+ 3. **Step 1:** Paste your Bot Token Next
150
+ 4. **Step 2:** Send any message to your bot, then click **Detect Chat ID**select your chat Next
151
+ 5. **Step 3:** Send Test → Save
149
152
 
150
153
  **Features:**
151
154
  - Notifications forwarded to Telegram with session labels
152
- - Reply to a notification message → routed to the correct session
155
+ - Reply to a notification → routed to the correct session
153
156
  - Send a new message → auto-delivered if 1 session, or pick from a list
157
+ - Send photos from Telegram → downloaded and forwarded to Claude
158
+ - Photo captions included as text alongside the image
154
159
 
155
160
  ```json
156
161
  {
@@ -188,14 +193,19 @@ Two-way messaging with Claude sessions via Telegram:
188
193
  }
189
194
  ```
190
195
 
191
- ## Image Upload (Local Sessions)
196
+ ## Image Support
192
197
 
193
- Send images to Claude via the dashboard:
198
+ **Dashboard (local sessions):**
194
199
  - **Ctrl+V** — Paste from clipboard
195
200
  - **Drag & Drop** — Drop image onto message area
196
201
  - **Attach button** — Click 📎 to browse files
202
+ - Images + text sent together as one message
203
+
204
+ **Telegram:**
205
+ - Send photos to the bot → forwarded to Claude session
206
+ - Photo captions included as text
197
207
 
198
- > Images are only available for local sessions (same machine as Hub). Max 10MB, auto-deleted after 5 minutes.
208
+ > Dashboard images are only available for local sessions (same machine as Hub). Max 10MB, auto-deleted after 5 minutes.
199
209
 
200
210
  ## Platform Support
201
211
 
package/dist/cli.js CHANGED
@@ -293,11 +293,15 @@ var init_notifier = __esm({
293
293
  });
294
294
 
295
295
  // src/hub/telegram.ts
296
+ import fs2 from "fs";
297
+ import path3 from "path";
298
+ import { randomUUID as randomUUID2 } from "crypto";
296
299
  var TELEGRAM_API, TelegramBot;
297
300
  var init_telegram = __esm({
298
301
  "src/hub/telegram.ts"() {
299
302
  "use strict";
300
303
  init_logger();
304
+ init_constants();
301
305
  TELEGRAM_API = "https://api.telegram.org/bot";
302
306
  TelegramBot = class {
303
307
  config;
@@ -306,13 +310,15 @@ var init_telegram = __esm({
306
310
  pollTimer = null;
307
311
  // message_id -> sessionId mapping for reply-based routing
308
312
  messageSessionMap = /* @__PURE__ */ new Map();
309
- // Callback: when a message arrives from Telegram for a session
313
+ // Callback: when a text message arrives from Telegram for a session
310
314
  onMessageToSession;
315
+ // Callback: when an image arrives from Telegram for a session
316
+ onImageToSession;
311
317
  // Callback: get current sessions list
312
318
  getSessions;
313
- // Callback: when user needs to select a session (sends inline keyboard)
319
+ // Pending messages for session selection
314
320
  pendingMessages = /* @__PURE__ */ new Map();
315
- // chatId -> pending message text
321
+ // chatId -> pending
316
322
  constructor(config2) {
317
323
  this.config = config2;
318
324
  }
@@ -408,31 +414,42 @@ ${message}`;
408
414
  if (!this.polling) return;
409
415
  this.pollTimer = setTimeout(() => this.poll(), delay);
410
416
  }
411
- handleIncomingMessage(msg) {
412
- if (!msg.text) return;
417
+ async handleIncomingMessage(msg) {
413
418
  if (String(msg.chat.id) !== String(this.config.chatId)) return;
414
- const text = msg.text.trim();
419
+ const hasPhoto = msg.photo && msg.photo.length > 0;
420
+ const text = (msg.text || msg.caption || "").trim();
421
+ if (!text && !hasPhoto) return;
415
422
  if (msg.reply_to_message) {
416
423
  const sessionId2 = this.messageSessionMap.get(msg.reply_to_message.message_id);
417
424
  if (sessionId2) {
418
- this.deliverToSession(sessionId2, text);
425
+ if (hasPhoto) {
426
+ await this.deliverPhotoToSession(sessionId2, msg.photo, text);
427
+ } else {
428
+ this.deliverToSession(sessionId2, text);
429
+ }
419
430
  return;
420
431
  }
421
432
  }
422
- const selectMatch = text.match(/^\/s_(\d+)$/);
423
- if (selectMatch) {
424
- const pendingText = this.pendingMessages.get(msg.chat.id);
425
- if (pendingText) {
426
- this.pendingMessages.delete(msg.chat.id);
427
- const sessions2 = this.getSessions?.() ?? [];
428
- const idx = parseInt(selectMatch[1], 10) - 1;
429
- if (idx >= 0 && idx < sessions2.length) {
430
- this.deliverToSession(sessions2[idx].id, pendingText);
431
- this.sendMessage(`Sent to [${this.getLabel(sessions2[idx])}]`);
432
- } else {
433
- this.sendMessage("Invalid session number.");
433
+ if (text) {
434
+ const selectMatch = text.match(/^\/s_(\d+)$/);
435
+ if (selectMatch) {
436
+ const pending = this.pendingMessages.get(msg.chat.id);
437
+ if (pending) {
438
+ this.pendingMessages.delete(msg.chat.id);
439
+ const sessions2 = this.getSessions?.() ?? [];
440
+ const idx = parseInt(selectMatch[1], 10) - 1;
441
+ if (idx >= 0 && idx < sessions2.length) {
442
+ if (pending.photoFileId) {
443
+ await this.deliverPhotoToSessionByFileId(sessions2[idx].id, pending.photoFileId, pending.caption);
444
+ } else if (pending.text) {
445
+ this.deliverToSession(sessions2[idx].id, pending.text);
446
+ }
447
+ this.sendMessage(`Sent to [${this.getLabel(sessions2[idx])}]`);
448
+ } else {
449
+ this.sendMessage("Invalid session number.");
450
+ }
451
+ return;
434
452
  }
435
- return;
436
453
  }
437
454
  }
438
455
  const sessions = this.getSessions?.() ?? [];
@@ -441,10 +458,19 @@ ${message}`;
441
458
  return;
442
459
  }
443
460
  if (sessions.length === 1) {
444
- this.deliverToSession(sessions[0].id, text);
461
+ if (hasPhoto) {
462
+ await this.deliverPhotoToSession(sessions[0].id, msg.photo, text);
463
+ } else {
464
+ this.deliverToSession(sessions[0].id, text);
465
+ }
445
466
  return;
446
467
  }
447
- this.pendingMessages.set(msg.chat.id, text);
468
+ if (hasPhoto) {
469
+ const largest = msg.photo[msg.photo.length - 1];
470
+ this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });
471
+ } else {
472
+ this.pendingMessages.set(msg.chat.id, { text });
473
+ }
448
474
  const sessionList = sessions.map((s, i) => `/s_${i + 1} - ${this.getLabel(s)}`).join("\n");
449
475
  this.sendMessage(`Multiple sessions active. Reply with a command to select:
450
476
 
@@ -455,6 +481,46 @@ ${sessionList}`);
455
481
  this.onMessageToSession(sessionId2, content);
456
482
  }
457
483
  }
484
+ async deliverPhotoToSession(sessionId2, photos, caption) {
485
+ const largest = photos[photos.length - 1];
486
+ await this.deliverPhotoToSessionByFileId(sessionId2, largest.file_id, caption);
487
+ }
488
+ async deliverPhotoToSessionByFileId(sessionId2, fileId, caption) {
489
+ try {
490
+ const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);
491
+ if (!fileRes.ok) {
492
+ logger.warn("Failed to get Telegram file info");
493
+ return;
494
+ }
495
+ const fileData = await fileRes.json();
496
+ if (!fileData.ok) return;
497
+ const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;
498
+ const imgRes = await fetch(downloadUrl);
499
+ if (!imgRes.ok) {
500
+ logger.warn("Failed to download Telegram photo");
501
+ return;
502
+ }
503
+ const buffer = Buffer.from(await imgRes.arrayBuffer());
504
+ const ext = fileData.result.file_path.split(".").pop() || "jpg";
505
+ const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
506
+ fs2.mkdirSync(UPLOADS_DIR, { recursive: true });
507
+ const filename = `${randomUUID2()}.${ext}`;
508
+ const filePath = path3.join(UPLOADS_DIR, filename);
509
+ fs2.writeFileSync(filePath, buffer);
510
+ logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
511
+ if (this.onImageToSession) {
512
+ this.onImageToSession(sessionId2, filePath, mimeType, caption);
513
+ }
514
+ setTimeout(() => {
515
+ try {
516
+ fs2.unlinkSync(filePath);
517
+ } catch {
518
+ }
519
+ }, 5 * 60 * 1e3);
520
+ } catch (err) {
521
+ logger.warn(`Telegram photo download failed: ${err.message}`);
522
+ }
523
+ }
458
524
  getLabel(session) {
459
525
  return session.cwd?.replace(/^.*[/\\]/, "") || session.name;
460
526
  }
@@ -475,11 +541,11 @@ __export(server_exports, {
475
541
  HubServer: () => HubServer
476
542
  });
477
543
  import http from "http";
478
- import fs2 from "fs";
479
- import path3 from "path";
544
+ import fs3 from "fs";
545
+ import path4 from "path";
480
546
  import { fileURLToPath } from "url";
481
547
  import { WebSocketServer, WebSocket } from "ws";
482
- import { randomUUID as randomUUID2 } from "crypto";
548
+ import { randomUUID as randomUUID3 } from "crypto";
483
549
  var __dirname, HubServer;
484
550
  var init_server = __esm({
485
551
  "src/hub/server.ts"() {
@@ -490,7 +556,7 @@ var init_server = __esm({
490
556
  init_notifier();
491
557
  init_telegram();
492
558
  init_config();
493
- __dirname = path3.dirname(fileURLToPath(import.meta.url));
559
+ __dirname = path4.dirname(fileURLToPath(import.meta.url));
494
560
  HubServer = class {
495
561
  httpServer;
496
562
  wssChannel;
@@ -647,23 +713,23 @@ var init_server = __esm({
647
713
  }
648
714
  serveDashboard(res) {
649
715
  const candidates = [
650
- path3.join(__dirname, "..", "dashboard", "index.html"),
716
+ path4.join(__dirname, "..", "dashboard", "index.html"),
651
717
  // from dist/hub/
652
- path3.join(__dirname, "dashboard", "index.html"),
718
+ path4.join(__dirname, "dashboard", "index.html"),
653
719
  // from dist/ (bundled index.js)
654
- path3.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
720
+ path4.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
655
721
  // from dist/hub/ -> src/
656
- path3.join(__dirname, "..", "src", "dashboard", "index.html"),
722
+ path4.join(__dirname, "..", "src", "dashboard", "index.html"),
657
723
  // from dist/ -> src/
658
- path3.join(process.cwd(), "dist", "dashboard", "index.html"),
724
+ path4.join(process.cwd(), "dist", "dashboard", "index.html"),
659
725
  // from cwd
660
- path3.join(process.cwd(), "src", "dashboard", "index.html")
726
+ path4.join(process.cwd(), "src", "dashboard", "index.html")
661
727
  // from cwd/src
662
728
  ];
663
729
  logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
664
730
  for (const candidate of candidates) {
665
- if (fs2.existsSync(candidate)) {
666
- const html = fs2.readFileSync(candidate, "utf-8");
731
+ if (fs3.existsSync(candidate)) {
732
+ const html = fs3.readFileSync(candidate, "utf-8");
667
733
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
668
734
  res.end(html);
669
735
  return;
@@ -841,11 +907,11 @@ var init_server = __esm({
841
907
  logger.warn("Image upload rejected: exceeds 10MB");
842
908
  return;
843
909
  }
844
- fs2.mkdirSync(UPLOADS_DIR, { recursive: true });
910
+ fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
845
911
  const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
846
- const filename = `${randomUUID2()}.${ext}`;
847
- const filePath = path3.join(UPLOADS_DIR, filename);
848
- fs2.writeFileSync(filePath, buffer);
912
+ const filename = `${randomUUID3()}.${ext}`;
913
+ const filePath = path4.join(UPLOADS_DIR, filename);
914
+ fs3.writeFileSync(filePath, buffer);
849
915
  const forwardMsg = {
850
916
  type: "image_to_session",
851
917
  sessionId: sessionId2,
@@ -858,7 +924,7 @@ var init_server = __esm({
858
924
  logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
859
925
  setTimeout(() => {
860
926
  try {
861
- fs2.unlinkSync(filePath);
927
+ fs3.unlinkSync(filePath);
862
928
  } catch {
863
929
  }
864
930
  }, 5 * 60 * 1e3);
@@ -891,6 +957,14 @@ var init_server = __esm({
891
957
  logger.info(`Telegram message forwarded to session: ${sessionId2}`);
892
958
  }
893
959
  };
960
+ this.telegramBot.onImageToSession = (sessionId2, imagePath, mimeType, caption) => {
961
+ const channelWs = this.channelSockets.get(sessionId2);
962
+ if (channelWs?.readyState === WebSocket.OPEN) {
963
+ const msg = { type: "image_to_session", sessionId: sessionId2, imagePath, mimeType, content: caption };
964
+ channelWs.send(JSON.stringify(msg));
965
+ logger.info(`Telegram photo forwarded to session: ${sessionId2}`);
966
+ }
967
+ };
894
968
  this.notifier.configure({ telegramBot: this.telegramBot });
895
969
  this.telegramBot.startPolling();
896
970
  logger.info("Telegram bot initialized");
@@ -995,11 +1069,11 @@ var init_server = __esm({
995
1069
  }
996
1070
  cleanupUploads() {
997
1071
  try {
998
- if (!fs2.existsSync(UPLOADS_DIR)) return;
999
- const files = fs2.readdirSync(UPLOADS_DIR);
1072
+ if (!fs3.existsSync(UPLOADS_DIR)) return;
1073
+ const files = fs3.readdirSync(UPLOADS_DIR);
1000
1074
  for (const file of files) {
1001
1075
  try {
1002
- fs2.unlinkSync(path3.join(UPLOADS_DIR, file));
1076
+ fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
1003
1077
  } catch {
1004
1078
  }
1005
1079
  }
@@ -1172,8 +1246,8 @@ import {
1172
1246
  CallToolRequestSchema,
1173
1247
  ListToolsRequestSchema
1174
1248
  } from "@modelcontextprotocol/sdk/types.js";
1175
- import { randomUUID as randomUUID3 } from "crypto";
1176
- import path4 from "path";
1249
+ import { randomUUID as randomUUID4 } from "crypto";
1250
+ import path5 from "path";
1177
1251
  async function main() {
1178
1252
  logger.info(`Starting MCP channel server (session: ${sessionId})`);
1179
1253
  hubClient.connect();
@@ -1212,8 +1286,8 @@ var init_server2 = __esm({
1212
1286
  init_constants();
1213
1287
  init_config();
1214
1288
  init_hub_client();
1215
- sessionId = randomUUID3();
1216
- sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path4.basename(process.cwd());
1289
+ sessionId = randomUUID4();
1290
+ sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path5.basename(process.cwd());
1217
1291
  server = new Server(
1218
1292
  {
1219
1293
  name: CHANNEL_SERVER_NAME,
@@ -1347,11 +1421,11 @@ init_config();
1347
1421
  init_constants();
1348
1422
  init_logger();
1349
1423
  import { spawn } from "child_process";
1350
- import fs3 from "fs";
1351
- import path5 from "path";
1424
+ import fs4 from "fs";
1425
+ import path6 from "path";
1352
1426
  import readline from "readline";
1353
1427
  import { fileURLToPath as fileURLToPath2 } from "url";
1354
- var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
1428
+ var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
1355
1429
  function printUsage() {
1356
1430
  console.log(`
1357
1431
  claude-alarm - Monitor Claude Code sessions with notifications
@@ -1372,8 +1446,8 @@ Quick start:
1372
1446
  }
1373
1447
  async function checkForUpdates() {
1374
1448
  try {
1375
- const pkgPath = path5.join(__dirname2, "..", "package.json");
1376
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
1449
+ const pkgPath = path6.join(__dirname2, "..", "package.json");
1450
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
1377
1451
  const currentVersion = pkg.version;
1378
1452
  const res = await fetch("https://registry.npmjs.org/@delt/claude-alarm/latest", {
1379
1453
  signal: AbortSignal.timeout(3e3)
@@ -1396,25 +1470,25 @@ async function hubStart(daemon) {
1396
1470
  const port = config2.hub.port ?? DEFAULT_HUB_PORT;
1397
1471
  const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
1398
1472
  checkForUpdates();
1399
- if (fs3.existsSync(PID_FILE)) {
1400
- const pid = parseInt(fs3.readFileSync(PID_FILE, "utf-8").trim(), 10);
1473
+ if (fs4.existsSync(PID_FILE)) {
1474
+ const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
1401
1475
  if (isProcessRunning(pid)) {
1402
1476
  console.log(`Hub is already running (PID: ${pid}) on http://${displayHost}:${port}`);
1403
1477
  return;
1404
1478
  }
1405
- fs3.unlinkSync(PID_FILE);
1479
+ fs4.unlinkSync(PID_FILE);
1406
1480
  }
1407
1481
  if (daemon) {
1408
1482
  ensureConfigDir();
1409
- const logFd = fs3.openSync(LOG_FILE, "a");
1410
- const hubScript = path5.join(__dirname2, "hub", "server.js");
1483
+ const logFd = fs4.openSync(LOG_FILE, "a");
1484
+ const hubScript = path6.join(__dirname2, "hub", "server.js");
1411
1485
  const child = spawn(process.execPath, [hubScript], {
1412
1486
  detached: true,
1413
1487
  stdio: ["ignore", logFd, logFd],
1414
1488
  env: { ...process.env }
1415
1489
  });
1416
1490
  if (child.pid) {
1417
- fs3.writeFileSync(PID_FILE, String(child.pid), "utf-8");
1491
+ fs4.writeFileSync(PID_FILE, String(child.pid), "utf-8");
1418
1492
  child.unref();
1419
1493
  console.log(`Hub started as daemon (PID: ${child.pid})`);
1420
1494
  console.log(`Dashboard: http://${displayHost}:${port}`);
@@ -1431,11 +1505,11 @@ async function hubStart(daemon) {
1431
1505
  const hub = new HubServer2(config2);
1432
1506
  await hub.start();
1433
1507
  ensureConfigDir();
1434
- fs3.writeFileSync(PID_FILE, String(process.pid), "utf-8");
1508
+ fs4.writeFileSync(PID_FILE, String(process.pid), "utf-8");
1435
1509
  const shutdown = async () => {
1436
1510
  console.log("\nShutting down...");
1437
1511
  await hub.stop();
1438
- if (fs3.existsSync(PID_FILE)) fs3.unlinkSync(PID_FILE);
1512
+ if (fs4.existsSync(PID_FILE)) fs4.unlinkSync(PID_FILE);
1439
1513
  process.exit(0);
1440
1514
  };
1441
1515
  process.on("SIGINT", shutdown);
@@ -1443,18 +1517,18 @@ async function hubStart(daemon) {
1443
1517
  }
1444
1518
  }
1445
1519
  function hubStop() {
1446
- if (!fs3.existsSync(PID_FILE)) {
1520
+ if (!fs4.existsSync(PID_FILE)) {
1447
1521
  console.log("Hub is not running (no PID file found)");
1448
1522
  return;
1449
1523
  }
1450
- const pid = parseInt(fs3.readFileSync(PID_FILE, "utf-8").trim(), 10);
1524
+ const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
1451
1525
  try {
1452
1526
  process.kill(pid, "SIGTERM");
1453
1527
  console.log(`Hub stopped (PID: ${pid})`);
1454
1528
  } catch {
1455
1529
  console.log("Hub process not found (may have already stopped)");
1456
1530
  }
1457
- fs3.unlinkSync(PID_FILE);
1531
+ fs4.unlinkSync(PID_FILE);
1458
1532
  }
1459
1533
  async function hubStatus() {
1460
1534
  const config2 = loadConfig();
@@ -1462,8 +1536,8 @@ async function hubStatus() {
1462
1536
  const port = config2.hub.port ?? DEFAULT_HUB_PORT;
1463
1537
  const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
1464
1538
  let pidInfo = "not running";
1465
- if (fs3.existsSync(PID_FILE)) {
1466
- const pid = parseInt(fs3.readFileSync(PID_FILE, "utf-8").trim(), 10);
1539
+ if (fs4.existsSync(PID_FILE)) {
1540
+ const pid = parseInt(fs4.readFileSync(PID_FILE, "utf-8").trim(), 10);
1467
1541
  if (isProcessRunning(pid)) {
1468
1542
  pidInfo = `running (PID: ${pid})`;
1469
1543
  } else {
@@ -1535,7 +1609,7 @@ function ask(question) {
1535
1609
  }
1536
1610
  async function init() {
1537
1611
  const dir = process.cwd();
1538
- const projectName = path5.basename(dir);
1612
+ const projectName = path6.basename(dir);
1539
1613
  console.log(`
1540
1614
  claude-alarm init for "${projectName}"
1541
1615
  `);
@@ -1555,11 +1629,11 @@ claude-alarm init for "${projectName}"
1555
1629
  if (port) env.CLAUDE_ALARM_HUB_PORT = port;
1556
1630
  if (token) env.CLAUDE_ALARM_HUB_TOKEN = token;
1557
1631
  }
1558
- const mcpPath = path5.join(dir, ".mcp.json");
1632
+ const mcpPath = path6.join(dir, ".mcp.json");
1559
1633
  let mcpConfig = {};
1560
- if (fs3.existsSync(mcpPath)) {
1634
+ if (fs4.existsSync(mcpPath)) {
1561
1635
  try {
1562
- mcpConfig = JSON.parse(fs3.readFileSync(mcpPath, "utf-8"));
1636
+ mcpConfig = JSON.parse(fs4.readFileSync(mcpPath, "utf-8"));
1563
1637
  } catch {
1564
1638
  mcpConfig = {};
1565
1639
  }
@@ -1570,7 +1644,7 @@ claude-alarm init for "${projectName}"
1570
1644
  args: ["-y", "@delt/claude-alarm", "serve"],
1571
1645
  env
1572
1646
  };
1573
- fs3.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
1647
+ fs4.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
1574
1648
  console.log(`
1575
1649
  \u2713 Created ${mcpPath}`);
1576
1650
  if (remote.toLowerCase() !== "y") {