@delt/claude-alarm 0.8.1 → 0.9.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/dist/index.js CHANGED
@@ -41,6 +41,12 @@ var WS_PATH_DASHBOARD = "/ws/dashboard";
41
41
  var CHANNEL_SERVER_NAME = "claude-alarm";
42
42
  var CHANNEL_SERVER_VERSION = "0.1.0";
43
43
 
44
+ // src/shared/peer-name.ts
45
+ var SAFE_PEER_NAME = /^[^\u0000-\u001f\u007f"\\[\]]{1,128}$/;
46
+ function isValidPeerName(name) {
47
+ return typeof name === "string" && name.trim() !== "" && SAFE_PEER_NAME.test(name);
48
+ }
49
+
44
50
  // src/hub/session-manager.ts
45
51
  var SessionManager = class {
46
52
  sessions = /* @__PURE__ */ new Map();
@@ -64,7 +70,7 @@ var SessionManager = class {
64
70
  } else {
65
71
  session.displayName = baseName;
66
72
  }
67
- this.sessions.set(session.id, { ...session });
73
+ this.sessions.set(session.id, { ...session, peerName: isValidPeerName(session.peerName) ? session.peerName : void 0 });
68
74
  }
69
75
  unregister(sessionId) {
70
76
  const session = this.sessions.get(sessionId);
@@ -79,6 +85,13 @@ var SessionManager = class {
79
85
  }
80
86
  return session;
81
87
  }
88
+ setPeerName(sessionId, peerName) {
89
+ const session = this.sessions.get(sessionId);
90
+ if (session) {
91
+ session.peerName = isValidPeerName(peerName) ? peerName : void 0;
92
+ }
93
+ return session;
94
+ }
82
95
  updateActivity(sessionId) {
83
96
  const session = this.sessions.get(sessionId);
84
97
  if (session) {
@@ -210,7 +223,7 @@ import fs from "fs";
210
223
  import path2 from "path";
211
224
  import { randomUUID } from "crypto";
212
225
  var TELEGRAM_API = "https://api.telegram.org/bot";
213
- var TelegramBot = class {
226
+ var TelegramBot = class _TelegramBot {
214
227
  config;
215
228
  offset = 0;
216
229
  polling = false;
@@ -442,33 +455,45 @@ ${this.mdToHtml(message)}`;
442
455
  }
443
456
  /** Send a permission request with inline buttons */
444
457
  async sendPermissionRequest(sessionId, sessionLabel, requestId, toolName, description, inputPreview) {
458
+ const isNotify = toolName.endsWith("__notify") || toolName === "notify";
445
459
  let preview = inputPreview;
460
+ let kind = "code";
461
+ let lang = "";
446
462
  let truncated = false;
447
463
  try {
448
464
  const p = JSON.parse(inputPreview);
449
- if (p.command) preview = `$ ${p.command}`;
450
- else if (p.title && p.message) preview = p.message;
451
- else if (p.file_path) {
465
+ if (p.command) {
466
+ preview = `$ ${p.command}`;
467
+ lang = "bash";
468
+ } else if (p.title && p.message) {
469
+ preview = p.message;
470
+ kind = "prose";
471
+ } else if (p.file_path) {
452
472
  preview = p.file_path;
473
+ lang = this.langOf(p.file_path);
453
474
  if (p.content) {
454
475
  preview += "\n" + p.content.slice(0, 3e3);
455
476
  if (p.content.length > 500) truncated = true;
456
477
  }
457
478
  } else if (p.content && typeof p.content === "string") {
458
479
  preview = p.content.slice(0, 3e3);
480
+ if (isNotify) kind = "prose";
459
481
  if (p.content.length > 500) truncated = true;
460
482
  }
461
483
  } catch {
462
484
  truncated = true;
463
485
  const cmdMatch = inputPreview.match(/"command"\s*:\s*"((?:[^"\\]|\\.)*)"/);
464
486
  const contentMatch = inputPreview.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)"/);
465
- if (cmdMatch) preview = `$ ${cmdMatch[1]}`;
466
- else if (contentMatch) preview = contentMatch[1].slice(0, 3e3);
487
+ if (cmdMatch) {
488
+ preview = `$ ${cmdMatch[1]}`;
489
+ lang = "bash";
490
+ } else if (contentMatch) preview = contentMatch[1].slice(0, 3e3);
467
491
  }
468
492
  const truncNote = truncated ? "\n\n<i>...truncated</i>" : "";
469
493
  const previewSlice = preview.slice(0, 3e3);
470
494
  const isShort = !previewSlice.includes("\n") && previewSlice.length < 100;
471
- const previewHtml = isShort ? `<code>${this.escHtml(previewSlice)}</code>` : this.escHtml(previewSlice);
495
+ const langAttr = lang ? ` class="language-${lang}"` : "";
496
+ const previewHtml = kind === "prose" ? this.mdToHtml(previewSlice) : isShort ? `<code>${this.escHtml(previewSlice)}</code>` : `<pre><code${langAttr}>${this.escHtml(previewSlice)}</code></pre>`;
472
497
  let displayTool = toolName;
473
498
  if ((toolName.endsWith("__notify") || toolName === "notify") && inputPreview) {
474
499
  try {
@@ -568,6 +593,40 @@ ${label}`);
568
593
  escHtml(s) {
569
594
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
570
595
  }
596
+ static LANG_BY_EXT = {
597
+ js: "javascript",
598
+ mjs: "javascript",
599
+ cjs: "javascript",
600
+ jsx: "javascript",
601
+ ts: "typescript",
602
+ tsx: "typescript",
603
+ py: "python",
604
+ rb: "ruby",
605
+ go: "go",
606
+ java: "java",
607
+ kt: "kotlin",
608
+ rs: "rust",
609
+ php: "php",
610
+ cs: "csharp",
611
+ sh: "bash",
612
+ bash: "bash",
613
+ zsh: "bash",
614
+ ps1: "powershell",
615
+ json: "json",
616
+ yml: "yaml",
617
+ yaml: "yaml",
618
+ xml: "xml",
619
+ sql: "sql",
620
+ md: "markdown",
621
+ html: "html",
622
+ vue: "html",
623
+ css: "css",
624
+ scss: "scss"
625
+ };
626
+ langOf(path5) {
627
+ const m = /\.([a-z0-9]+)$/i.exec(path5 || "");
628
+ return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
629
+ }
571
630
  /** Convert markdown to Telegram HTML (escape first, then apply formatting) */
572
631
  mdToHtml(s) {
573
632
  let text = s.replace(
@@ -1019,7 +1078,7 @@ var HubServer = class {
1019
1078
  this.sessions.register(session);
1020
1079
  this.channelSockets.set(session.id, ws);
1021
1080
  if (isLocal) this.localChannels.add(session.id);
1022
- logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false})`);
1081
+ logger.info(`Session registered: ${session.id} (${session.name}, channel: ${session.channelEnabled ?? false}, peer: ${session.peerName ?? "-"})`);
1023
1082
  this.broadcastToDashboards({
1024
1083
  type: isReregister ? "session_updated" : "session_connected",
1025
1084
  session
@@ -1033,6 +1092,15 @@ var HubServer = class {
1033
1092
  }
1034
1093
  break;
1035
1094
  }
1095
+ case "peer_name": {
1096
+ if (this.channelSockets.get(msg.sessionId) !== ws) break;
1097
+ const updated = this.sessions.setPeerName(msg.sessionId, msg.peerName);
1098
+ if (updated) {
1099
+ logger.info(`Peer name for ${msg.sessionId}: ${msg.peerName ?? "-"}`);
1100
+ this.broadcastToDashboards({ type: "session_updated", session: updated });
1101
+ }
1102
+ break;
1103
+ }
1036
1104
  case "notify": {
1037
1105
  this.sessions.updateActivity(msg.sessionId);
1038
1106
  const notifySession = this.sessions.get(msg.sessionId);
@@ -1399,12 +1467,13 @@ if (process.argv[1] && (process.argv[1].endsWith("hub/server.js") || process.arg
1399
1467
  // src/channel/hub-client.ts
1400
1468
  import WebSocket2 from "ws";
1401
1469
  var HubClient = class {
1402
- constructor(sessionId, sessionName, hubHost = DEFAULT_HUB_HOST, hubPort = DEFAULT_HUB_PORT, token) {
1470
+ constructor(sessionId, sessionName, hubHost = DEFAULT_HUB_HOST, hubPort = DEFAULT_HUB_PORT, token, getPeerName = () => void 0) {
1403
1471
  this.sessionId = sessionId;
1404
1472
  this.sessionName = sessionName;
1405
1473
  this.hubHost = hubHost;
1406
1474
  this.hubPort = hubPort;
1407
1475
  this.token = token;
1476
+ this.getPeerName = getPeerName;
1408
1477
  }
1409
1478
  ws = null;
1410
1479
  reconnectTimer = null;
@@ -1429,7 +1498,8 @@ var HubClient = class {
1429
1498
  connectedAt: Date.now(),
1430
1499
  lastActivity: Date.now(),
1431
1500
  cwd: process.cwd(),
1432
- channelEnabled: true
1501
+ channelEnabled: true,
1502
+ peerName: this.getPeerName()
1433
1503
  }
1434
1504
  };
1435
1505
  this.ws.send(JSON.stringify(registration));