lynkr 9.0.2 → 9.1.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.
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Markdown → ANSI escape code renderer.
3
+ *
4
+ * Activated by MARKDOWN_RENDER_ANSI=true in the environment.
5
+ * Applied to text blocks in the SSE emission path so clients like claw
6
+ * receive pre-formatted output without needing their own markdown renderer.
7
+ *
8
+ * Deliberately avoids external dependencies — pure regex + string ops.
9
+ */
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // ANSI primitives
13
+ // ---------------------------------------------------------------------------
14
+ const R = '\x1b[0m'; // reset all
15
+ const B = '\x1b[1m'; // bold on
16
+ const B_ = '\x1b[22m'; // bold off
17
+ const I = '\x1b[3m'; // italic on
18
+ const I_ = '\x1b[23m'; // italic off
19
+ const S = '\x1b[9m'; // strikethrough on
20
+ const S_ = '\x1b[29m'; // strikethrough off
21
+ const DIM = '\x1b[2m'; // dim
22
+
23
+ const CYAN = '\x1b[1;96m'; // bold bright-cyan — H1
24
+ const BLUE = '\x1b[1;94m'; // bold bright-blue — H2
25
+ const MAGENTA = '\x1b[1;95m'; // bold bright-magenta — H3
26
+ const WHITE_B = '\x1b[1;97m'; // bold white — H4-H6
27
+ const YELLOW = '\x1b[33m'; // yellow — inline code
28
+ const GREEN = '\x1b[92m'; // bright green — code block body
29
+ const GRAY = '\x1b[90m'; // dark gray — HR / code fence border
30
+ const ORANGE = '\x1b[38;5;214m'; // orange — code fence lang tag
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Inline formatting (applied to single lines outside code fences)
34
+ // ---------------------------------------------------------------------------
35
+ function inlineFmt(line) {
36
+ // Bold + italic: ***text***
37
+ line = line.replace(/\*\*\*(.+?)\*\*\*/g, `${B}${I}$1${I_}${B_}`);
38
+ // Bold: **text** or __text__
39
+ line = line.replace(/\*\*(.+?)\*\*/g, `${B}$1${B_}`);
40
+ line = line.replace(/__(.+?)__/g, `${B}$1${B_}`);
41
+ // Italic: *text* or _text_ (single, not preceded/followed by same char)
42
+ line = line.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `${I}$1${I_}`);
43
+ line = line.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `${I}$1${I_}`);
44
+ // Strikethrough: ~~text~~
45
+ line = line.replace(/~~(.+?)~~/g, `${S}$1${S_}`);
46
+ // Inline code: `code` (done last so ANSI inside code isn't re-processed)
47
+ line = line.replace(/`([^`]+)`/g, `${YELLOW}$1${R}`);
48
+ return line;
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Block-level rendering (processes the whole text at once)
53
+ // ---------------------------------------------------------------------------
54
+ function markdownToAnsi(text) {
55
+ if (!text) return text;
56
+
57
+ const lines = text.split('\n');
58
+ const out = [];
59
+ let inCode = false;
60
+ let codeLang = '';
61
+
62
+ for (let i = 0; i < lines.length; i++) {
63
+ const raw = lines[i];
64
+
65
+ // ── Code fence open/close ──────────────────────────────────────────────
66
+ const fenceMatch = raw.match(/^(`{3,})(.*)/);
67
+ if (fenceMatch) {
68
+ if (!inCode) {
69
+ inCode = true;
70
+ codeLang = fenceMatch[2].trim();
71
+ const tag = codeLang ? ` ${codeLang} ` : '';
72
+ out.push(`${GRAY}┌─${ORANGE}${tag}${GRAY}${'─'.repeat(Math.max(0, 46 - tag.length))}${R}`);
73
+ } else {
74
+ inCode = false;
75
+ out.push(`${GRAY}└${'─'.repeat(48)}${R}`);
76
+ }
77
+ continue;
78
+ }
79
+
80
+ // ── Inside a code block ───────────────────────────────────────────────
81
+ if (inCode) {
82
+ out.push(`${GRAY}│ ${GREEN}${raw}${R}`);
83
+ continue;
84
+ }
85
+
86
+ // ── Horizontal rule ───────────────────────────────────────────────────
87
+ if (/^[-*_]{3,}\s*$/.test(raw.trim())) {
88
+ out.push(`${GRAY}${'─'.repeat(50)}${R}`);
89
+ continue;
90
+ }
91
+
92
+ // ── Headings ──────────────────────────────────────────────────────────
93
+ const h6 = raw.match(/^(#{1,6})\s+(.*)/);
94
+ if (h6) {
95
+ const level = h6[1].length;
96
+ const title = inlineFmt(h6[2]);
97
+ const colors = [CYAN, BLUE, MAGENTA, WHITE_B, WHITE_B, WHITE_B];
98
+ const prefix = ['━━ ', '── ', ' ', ' ', ' ', ' '][level - 1];
99
+ out.push(`${colors[level - 1]}${prefix}${title}${R}`);
100
+ continue;
101
+ }
102
+
103
+ // ── Blockquote ────────────────────────────────────────────────────────
104
+ if (raw.startsWith('> ')) {
105
+ out.push(`${DIM}│ ${inlineFmt(raw.slice(2))}${R}`);
106
+ continue;
107
+ }
108
+
109
+ // ── Unordered list ────────────────────────────────────────────────────
110
+ const ulMatch = raw.match(/^(\s*)[*\-+] (.*)/);
111
+ if (ulMatch) {
112
+ const indent = ulMatch[1];
113
+ const depth = Math.floor(indent.length / 2);
114
+ const bullet = ['•', '◦', '▸'][Math.min(depth, 2)];
115
+ out.push(`${indent}${YELLOW}${bullet}${R} ${inlineFmt(ulMatch[2])}`);
116
+ continue;
117
+ }
118
+
119
+ // ── Ordered list ──────────────────────────────────────────────────────
120
+ const olMatch = raw.match(/^(\s*)(\d+)\. (.*)/);
121
+ if (olMatch) {
122
+ out.push(`${olMatch[1]}${YELLOW}${olMatch[2]}.${R} ${inlineFmt(olMatch[3])}`);
123
+ continue;
124
+ }
125
+
126
+ // ── Normal line (apply inline formatting) ─────────────────────────────
127
+ out.push(inlineFmt(raw));
128
+ }
129
+
130
+ // Close an unclosed code fence gracefully
131
+ if (inCode) out.push(`${GRAY}└${'─'.repeat(48)}${R}`);
132
+
133
+ return out.join('\n');
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Public API
138
+ // ---------------------------------------------------------------------------
139
+ const enabled = process.env.MARKDOWN_RENDER_ANSI === 'true';
140
+
141
+ function renderText(text) {
142
+ if (!enabled || !text) return text;
143
+ return markdownToAnsi(text);
144
+ }
145
+
146
+ module.exports = { renderText, markdownToAnsi, enabled };