@delt/claude-alarm 0.8.1 → 0.8.2

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.
@@ -617,12 +617,35 @@
617
617
  border-radius: 5px;
618
618
  padding: 6px 10px;
619
619
  margin-top: 5px;
620
- max-height: 60px;
621
- overflow-y: auto;
620
+ max-height: 220px;
621
+ overflow: auto;
622
622
  white-space: pre-wrap;
623
623
  word-break: break-all;
624
624
  line-height: 1.4;
625
625
  }
626
+ .perm-preview pre {
627
+ margin: 0;
628
+ white-space: pre;
629
+ font-family: inherit;
630
+ font-size: inherit;
631
+ }
632
+ .perm-preview code { font-family: inherit; }
633
+ .perm-preview.prose {
634
+ font-family: inherit;
635
+ white-space: normal;
636
+ word-break: normal;
637
+ }
638
+ .perm-preview.prose pre,
639
+ .perm-preview.prose code { font-family: monospace; }
640
+ .perm-preview.prose ul { margin: 4px 0; padding-left: 20px; }
641
+ .perm-preview.prose li { margin: 2px 0; }
642
+ .perm-preview.prose strong { font-weight: 600; }
643
+ .perm-preview.prose h2,
644
+ .perm-preview.prose h3,
645
+ .perm-preview.prose h4 { font-size: 12px; font-weight: 600; margin: 4px 0; }
646
+ .perm-preview.prose table { border-collapse: collapse; margin: 4px 0; font-size: 11px; }
647
+ .perm-preview.prose th,
648
+ .perm-preview.prose td { border: 1px solid var(--border); padding: 3px 6px; text-align: left; }
626
649
  .perm-actions {
627
650
  display: flex;
628
651
  gap: 10px;
@@ -1670,9 +1693,26 @@
1670
1693
  return toolName;
1671
1694
  }
1672
1695
 
1673
- // Returns { text } — dashboard shows full content, no truncation
1696
+ const LANG_BY_EXT = {
1697
+ js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'javascript',
1698
+ ts: 'typescript', tsx: 'typescript', py: 'python', rb: 'ruby', go: 'go',
1699
+ java: 'java', kt: 'kotlin', rs: 'rust', php: 'php', cs: 'csharp',
1700
+ sh: 'bash', bash: 'bash', zsh: 'bash', ps1: 'powershell',
1701
+ json: 'json', yml: 'yaml', yaml: 'yaml', xml: 'xml', sql: 'sql',
1702
+ md: 'markdown', html: 'html', vue: 'html', css: 'css', scss: 'scss',
1703
+ };
1704
+ function langOf(path) {
1705
+ const m = /\.([a-z0-9]+)$/i.exec(path || '');
1706
+ return m ? (LANG_BY_EXT[m[1].toLowerCase()] || '') : '';
1707
+ }
1708
+
1709
+ // Returns { text, kind, lang } — dashboard shows full content, no truncation.
1710
+ // kind 'code' must stay byte-exact: markdown rendering would swallow the ** and `
1711
+ // characters that the approver needs to see to judge what actually gets executed.
1712
+ // Only notify-style messages (prose Claude wrote) are safe to render as markdown.
1674
1713
  function formatPermPreview(toolName, raw) {
1675
- if (!raw) return { text: '' };
1714
+ if (!raw) return { text: '', kind: 'code', lang: '' };
1715
+ const isNotify = toolName === 'notify' || toolName.endsWith('__notify');
1676
1716
  let p = null;
1677
1717
  try { p = JSON.parse(raw); } catch {
1678
1718
  // Truncated JSON - extract known fields with regex (closing quote optional for truncated strings)
@@ -1682,43 +1722,52 @@
1682
1722
  const contentMatch = raw.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)"?/);
1683
1723
  const titleMatch = raw.match(/"title"\s*:\s*"((?:[^"\\]|\\.)*)"?/);
1684
1724
  const msgMatch = raw.match(/"message"\s*:\s*"((?:[^"\\]|\\.)*)"?/);
1685
- if (titleMatch && msgMatch) return { text: msgMatch[1] };
1686
- if (cmdMatch) return { text: '$ ' + cmdMatch[1] };
1687
- if (fileMatch) return { text: fileMatch[1] };
1688
- if (patternMatch) return { text: patternMatch[1] };
1689
- if (contentMatch) return { text: contentMatch[1] };
1690
- return { text: raw };
1725
+ if (titleMatch && msgMatch) return { text: msgMatch[1], kind: 'prose', lang: '' };
1726
+ if (cmdMatch) return { text: '$ ' + cmdMatch[1], kind: 'code', lang: 'bash' };
1727
+ if (fileMatch) return { text: fileMatch[1], kind: 'code', lang: langOf(fileMatch[1]) };
1728
+ if (patternMatch) return { text: patternMatch[1], kind: 'code', lang: '' };
1729
+ if (contentMatch) return { text: contentMatch[1], kind: 'code', lang: '' };
1730
+ return { text: raw, kind: 'code', lang: '' };
1691
1731
  }
1692
1732
  let text;
1733
+ let kind = 'code';
1734
+ let lang = '';
1693
1735
  switch (toolName) {
1694
1736
  case 'Bash':
1695
- text = p.command ? '$ ' + p.command : raw; break;
1737
+ text = p.command ? '$ ' + p.command : raw; lang = 'bash'; break;
1696
1738
  case 'Write':
1697
1739
  case 'Edit':
1698
1740
  if (p.file_path) {
1699
1741
  text = p.file_path;
1742
+ lang = langOf(p.file_path);
1700
1743
  if (p.new_string) text += '\n+ ' + p.new_string;
1701
1744
  else if (p.content) text += '\n' + p.content;
1702
1745
  } else { text = raw; }
1703
1746
  break;
1704
1747
  case 'Read':
1705
- text = p.file_path || raw; break;
1748
+ text = p.file_path || raw; lang = langOf(p.file_path); break;
1706
1749
  case 'Glob':
1707
1750
  text = p.pattern || raw; break;
1708
1751
  case 'Grep':
1709
1752
  text = (p.pattern || '') + (p.path ? ' in ' + p.path : ''); break;
1710
1753
  default:
1711
1754
  if (p.title && p.message) {
1712
- text = p.message;
1755
+ text = p.message; kind = 'prose';
1713
1756
  } else if (p.content && typeof p.content === 'string') {
1714
- text = p.content;
1757
+ text = p.content; kind = isNotify ? 'prose' : 'code';
1715
1758
  } else {
1716
1759
  const entries = Object.entries(p);
1717
1760
  if (entries.length <= 3) text = entries.map(([k, v]) => k + ': ' + String(v)).join('\n');
1718
1761
  else text = JSON.stringify(p, null, 1);
1719
1762
  }
1720
1763
  }
1721
- return { text: text || '' };
1764
+ return { text: text || '', kind, lang };
1765
+ }
1766
+
1767
+ function renderPermPreview(text, kind, lang) {
1768
+ if (kind === 'prose') return `<div class="perm-preview prose">${renderMarkdown(text)}</div>`;
1769
+ const cls = lang ? ` class="lang-${esc(lang)}"` : '';
1770
+ return `<div class="perm-preview"><pre><code${cls}>${esc(text)}</code></pre></div>`;
1722
1771
  }
1723
1772
 
1724
1773
  function sendPermissionVerdict(sessionId, requestId, behavior) {
@@ -1742,7 +1791,7 @@
1742
1791
  if (!reqs.length) { bar.classList.remove('active'); bar.innerHTML = ''; return; }
1743
1792
  bar.classList.add('active');
1744
1793
  bar.innerHTML = reqs.map(r => {
1745
- const { text: previewText } = formatPermPreview(r.toolName, r.inputPreview);
1794
+ const { text: previewText, kind: previewKind, lang: previewLang } = formatPermPreview(r.toolName, r.inputPreview);
1746
1795
  const displayName = formatToolName(r.toolName, r.inputPreview);
1747
1796
  return `<div class="perm-item">
1748
1797
  <div class="perm-icon">&#9888;</div>
@@ -1750,7 +1799,7 @@
1750
1799
  <div class="perm-header">
1751
1800
  <span class="perm-tool">${esc(displayName)}</span>
1752
1801
  </div>
1753
- ${previewText ? `<div class="perm-preview">${esc(previewText)}</div>` : ''}
1802
+ ${previewText ? renderPermPreview(previewText, previewKind, previewLang) : ''}
1754
1803
  </div>
1755
1804
  <div class="perm-actions">
1756
1805
  <button class="perm-allow" data-req-id="${esc(r.requestId)}" data-session-id="${esc(sid)}">Allow <span class="perm-kbd">(Enter)</span></button>