weflow-cli 1.5.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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/weflow-cli-electron.cjs +59 -0
  4. package/bin/weflow-cli.ts +2311 -0
  5. package/cli.cjs +3 -0
  6. package/dist/bin/weflow-cli.d.ts +3 -0
  7. package/dist/bin/weflow-cli.d.ts.map +1 -0
  8. package/dist/bin/weflow-cli.js +2270 -0
  9. package/dist/bin/weflow-cli.js.map +1 -0
  10. package/dist/mcp-server/index.d.ts +3 -0
  11. package/dist/mcp-server/index.d.ts.map +1 -0
  12. package/dist/mcp-server/index.js +690 -0
  13. package/dist/mcp-server/index.js.map +1 -0
  14. package/dist/src/core/dbPathService.d.ts +40 -0
  15. package/dist/src/core/dbPathService.d.ts.map +1 -0
  16. package/dist/src/core/dbPathService.js +324 -0
  17. package/dist/src/core/dbPathService.js.map +1 -0
  18. package/dist/src/core/keyService.d.ts +59 -0
  19. package/dist/src/core/keyService.d.ts.map +1 -0
  20. package/dist/src/core/keyService.js +368 -0
  21. package/dist/src/core/keyService.js.map +1 -0
  22. package/dist/src/core/ntCore.d.ts +62 -0
  23. package/dist/src/core/ntCore.d.ts.map +1 -0
  24. package/dist/src/core/ntCore.js +191 -0
  25. package/dist/src/core/ntCore.js.map +1 -0
  26. package/dist/src/core/sqlcipherCore.d.ts +119 -0
  27. package/dist/src/core/sqlcipherCore.d.ts.map +1 -0
  28. package/dist/src/core/sqlcipherCore.js +693 -0
  29. package/dist/src/core/sqlcipherCore.js.map +1 -0
  30. package/dist/src/core/wcdbCore.d.ts +738 -0
  31. package/dist/src/core/wcdbCore.d.ts.map +1 -0
  32. package/dist/src/core/wcdbCore.js +4545 -0
  33. package/dist/src/core/wcdbCore.js.map +1 -0
  34. package/dist/src/core/wechatClient.d.ts +23 -0
  35. package/dist/src/core/wechatClient.d.ts.map +1 -0
  36. package/dist/src/core/wechatClient.js +166 -0
  37. package/dist/src/core/wechatClient.js.map +1 -0
  38. package/dist/src/services/chatService.d.ts +25 -0
  39. package/dist/src/services/chatService.d.ts.map +1 -0
  40. package/dist/src/services/chatService.js +291 -0
  41. package/dist/src/services/chatService.js.map +1 -0
  42. package/dist/src/services/configService.d.ts +43 -0
  43. package/dist/src/services/configService.d.ts.map +1 -0
  44. package/dist/src/services/configService.js +156 -0
  45. package/dist/src/services/configService.js.map +1 -0
  46. package/dist/src/services/exportService.d.ts +30 -0
  47. package/dist/src/services/exportService.d.ts.map +1 -0
  48. package/dist/src/services/exportService.js +273 -0
  49. package/dist/src/services/exportService.js.map +1 -0
  50. package/dist/src/services/wechat-formatter.d.ts +69 -0
  51. package/dist/src/services/wechat-formatter.d.ts.map +1 -0
  52. package/dist/src/services/wechat-formatter.js +248 -0
  53. package/dist/src/services/wechat-formatter.js.map +1 -0
  54. package/dist/src/services/wechatMessageService.d.ts +28 -0
  55. package/dist/src/services/wechatMessageService.d.ts.map +1 -0
  56. package/dist/src/services/wechatMessageService.js +341 -0
  57. package/dist/src/services/wechatMessageService.js.map +1 -0
  58. package/dist/src/services/wereadService.d.ts +206 -0
  59. package/dist/src/services/wereadService.d.ts.map +1 -0
  60. package/dist/src/services/wereadService.js +145 -0
  61. package/dist/src/services/wereadService.js.map +1 -0
  62. package/dist/src/services/whitelistService.d.ts +20 -0
  63. package/dist/src/services/whitelistService.d.ts.map +1 -0
  64. package/dist/src/services/whitelistService.js +60 -0
  65. package/dist/src/services/whitelistService.js.map +1 -0
  66. package/dist/src/types.d.ts +140 -0
  67. package/dist/src/types.d.ts.map +1 -0
  68. package/dist/src/types.js +2 -0
  69. package/dist/src/types.js.map +1 -0
  70. package/dist/src/utils/errors.d.ts +25 -0
  71. package/dist/src/utils/errors.d.ts.map +1 -0
  72. package/dist/src/utils/errors.js +43 -0
  73. package/dist/src/utils/errors.js.map +1 -0
  74. package/dist/src/utils/pathUtils.d.ts +5 -0
  75. package/dist/src/utils/pathUtils.d.ts.map +1 -0
  76. package/dist/src/utils/pathUtils.js +16 -0
  77. package/dist/src/utils/pathUtils.js.map +1 -0
  78. package/dist/src/utils/pythonRunner.d.ts +27 -0
  79. package/dist/src/utils/pythonRunner.d.ts.map +1 -0
  80. package/dist/src/utils/pythonRunner.js +62 -0
  81. package/dist/src/utils/pythonRunner.js.map +1 -0
  82. package/dist/src/utils/talkerUtils.d.ts +14 -0
  83. package/dist/src/utils/talkerUtils.d.ts.map +1 -0
  84. package/dist/src/utils/talkerUtils.js +73 -0
  85. package/dist/src/utils/talkerUtils.js.map +1 -0
  86. package/mcp-server/index.ts +757 -0
  87. package/package.json +72 -0
  88. package/resources/key/win32/x64/wx_key.dll +0 -0
  89. package/resources/wcdb/win32/x64/SDL2.dll +0 -0
  90. package/resources/wcdb/win32/x64/WCDB.dll +0 -0
  91. package/resources/wcdb/win32/x64/msvcp140.dll +0 -0
  92. package/resources/wcdb/win32/x64/msvcp140_1.dll +0 -0
  93. package/resources/wcdb/win32/x64/vcruntime140.dll +0 -0
  94. package/resources/wcdb/win32/x64/vcruntime140_1.dll +0 -0
  95. package/resources/wcdb/win32/x64/wcdb_api.dll +0 -0
  96. package/scripts/_batch_link_sources.py +73 -0
  97. package/scripts/_utils.py +531 -0
  98. package/scripts/annual_report.py +796 -0
  99. package/scripts/auto_tag.py +128 -0
  100. package/scripts/biz_daily.py +896 -0
  101. package/scripts/chat_report.py +397 -0
  102. package/scripts/chat_stats.py +702 -0
  103. package/scripts/classify_daily.py +411 -0
  104. package/scripts/compile_wiki.py +258 -0
  105. package/scripts/create_reading_notes.py +370 -0
  106. package/scripts/enrich_backlinks.py +163 -0
  107. package/scripts/export_chat_html.py +766 -0
  108. package/scripts/extract_3x_key.py +86 -0
  109. package/scripts/extract_todos.py +404 -0
  110. package/scripts/fav_server.py +392 -0
  111. package/scripts/fix_topics.py +111 -0
  112. package/scripts/generate_ai_report.py +367 -0
  113. package/scripts/generate_html.py +1372 -0
  114. package/scripts/generate_review.py +170 -0
  115. package/scripts/mcp_bridge.py +398 -0
  116. package/scripts/nt_decrypt.py +562 -0
  117. package/scripts/pipeline.py +145 -0
  118. package/scripts/promote_all.py +310 -0
  119. package/scripts/promote_ideas.py +297 -0
  120. package/scripts/rag_chat.py +191 -0
  121. package/scripts/scan_decrypt_4x.py +329 -0
  122. package/scripts/semantic_search.py +431 -0
  123. package/scripts/sync_fav.py +146 -0
  124. package/scripts/sync_weread.py +192 -0
  125. package/scripts/vault_rag.py +139 -0
  126. package/scripts/vault_search.py +141 -0
  127. package/src/core/dbPathService.ts +348 -0
  128. package/src/core/keyService.ts +409 -0
  129. package/src/core/ntCore.ts +224 -0
  130. package/src/core/sqlcipherCore.ts +764 -0
  131. package/src/core/wcdbCore.ts +4568 -0
  132. package/src/core/wechatClient.ts +204 -0
  133. package/src/lz4.d.ts +10 -0
  134. package/src/services/chatService.ts +296 -0
  135. package/src/services/configService.ts +197 -0
  136. package/src/services/exportService.ts +305 -0
  137. package/src/services/wechat-formatter.ts +348 -0
  138. package/src/services/wechatMessageService.ts +383 -0
  139. package/src/services/wereadService.ts +308 -0
  140. package/src/services/whitelistService.ts +72 -0
  141. package/src/types.ts +141 -0
  142. package/src/utils/errors.ts +42 -0
  143. package/src/utils/pathUtils.ts +16 -0
  144. package/src/utils/pythonRunner.ts +81 -0
  145. package/src/utils/talkerUtils.ts +84 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * 微信公众号 Markdown → HTML 排版转换器
3
+ *
4
+ * 基于 wechat-publisher-mcp 的 MarkdownConverter.js(MIT 许可)改写,
5
+ * 增加多主题支持,输出微信兼容的 inline-style HTML。
6
+ */
7
+ export interface FormatOptions {
8
+ theme?: 'default' | 'warm' | 'minimal' | 'green';
9
+ fontSize?: number;
10
+ lineHeight?: number;
11
+ }
12
+
13
+ interface ThemeConfig {
14
+ id: string;
15
+ name: string;
16
+ description: string;
17
+ h1: { color: string; border: string };
18
+ h2: { color: string; prefix: string };
19
+ h3: { color: string; prefix: string };
20
+ h4: { color: string; prefix: string };
21
+ bold: { color: string };
22
+ italic: { color: string };
23
+ del: { color: string };
24
+ codeBlock: { bg: string; border: string };
25
+ inlineCode: { bg: string; color: string };
26
+ blockquote: { border: string; bg: string; color: string };
27
+ link: { color: string };
28
+ }
29
+
30
+ const THEMES: Record<string, ThemeConfig> = {
31
+ default: {
32
+ id: 'default',
33
+ name: '经典蓝',
34
+ description: '蓝色强调 + 红色重点,适配大多数内容',
35
+ h1: { color: '#2c3e50', border: '#3498db' },
36
+ h2: { color: '#3498db', prefix: '🔹' },
37
+ h3: { color: '#27ae60', prefix: '▶' },
38
+ h4: { color: '#8e44ad', prefix: '•' },
39
+ bold: { color: '#e74c3c' },
40
+ italic: { color: '#9b59b6' },
41
+ del: { color: '#95a5a6' },
42
+ codeBlock: { bg: '#f8f9fa', border: '#e9ecef' },
43
+ inlineCode: { bg: '#f1f3f4', color: '#e91e63' },
44
+ blockquote: { border: '#3498db', bg: '#f8fafb', color: '#555' },
45
+ link: { color: '#3498db' },
46
+ },
47
+ warm: {
48
+ id: 'warm',
49
+ name: '暖橙',
50
+ description: '温暖橙色系,适合生活/人文类内容',
51
+ h1: { color: '#c0392b', border: '#e67e22' },
52
+ h2: { color: '#e67e22', prefix: '🔥' },
53
+ h3: { color: '#d35400', prefix: '▸' },
54
+ h4: { color: '#a0522d', prefix: '·' },
55
+ bold: { color: '#c0392b' },
56
+ italic: { color: '#d35400' },
57
+ del: { color: '#bdc3c7' },
58
+ codeBlock: { bg: '#fef9f4', border: '#f5d9b5' },
59
+ inlineCode: { bg: '#fdf2e9', color: '#c0392b' },
60
+ blockquote: { border: '#e67e22', bg: '#fef9f4', color: '#7f5539' },
61
+ link: { color: '#e67e22' },
62
+ },
63
+ minimal: {
64
+ id: 'minimal',
65
+ name: '极简黑白',
66
+ description: '黑白灰极简风,适合技术/严肃内容',
67
+ h1: { color: '#1a1a1a', border: '#333' },
68
+ h2: { color: '#333', prefix: '#' },
69
+ h3: { color: '#555', prefix: '##' },
70
+ h4: { color: '#777', prefix: '###' },
71
+ bold: { color: '#1a1a1a' },
72
+ italic: { color: '#666' },
73
+ del: { color: '#aaa' },
74
+ codeBlock: { bg: '#f5f5f5', border: '#ddd' },
75
+ inlineCode: { bg: '#f0f0f0', color: '#333' },
76
+ blockquote: { border: '#999', bg: '#f9f9f9', color: '#666' },
77
+ link: { color: '#333' },
78
+ },
79
+ green: {
80
+ id: 'green',
81
+ name: '清新绿',
82
+ description: '绿色自然系,适合健康/科普/环保内容',
83
+ h1: { color: '#1e5631', border: '#27ae60' },
84
+ h2: { color: '#27ae60', prefix: '🌿' },
85
+ h3: { color: '#2ecc71', prefix: '▹' },
86
+ h4: { color: '#16a085', prefix: '◦' },
87
+ bold: { color: '#27ae60' },
88
+ italic: { color: '#2ecc71' },
89
+ del: { color: '#a0c4a8' },
90
+ codeBlock: { bg: '#f0faf3', border: '#c8e6c9' },
91
+ inlineCode: { bg: '#e8f5e9', color: '#1e5631' },
92
+ blockquote: { border: '#27ae60', bg: '#f0faf3', color: '#2e7d32' },
93
+ link: { color: '#27ae60' },
94
+ },
95
+ };
96
+
97
+ /** 列出所有可用主题 */
98
+ export function listThemes(): { id: string; name: string; description: string }[] {
99
+ return Object.values(THEMES).map(t => ({
100
+ id: t.id,
101
+ name: t.name,
102
+ description: t.description,
103
+ }));
104
+ }
105
+
106
+ /** 获取单个主题配置 */
107
+ export function getTheme(id: string): ThemeConfig | undefined {
108
+ return THEMES[id];
109
+ }
110
+
111
+ /** HTML 实体转义 */
112
+ function escapeHtml(text: string): string {
113
+ const map: Record<string, string> = {
114
+ '&': '&amp;',
115
+ '<': '&lt;',
116
+ '>': '&gt;',
117
+ '"': '&quot;',
118
+ "'": '&#039;',
119
+ };
120
+ return text.replace(/[&<>"']/g, m => map[m]);
121
+ }
122
+
123
+ /** 主入口:Markdown → 微信兼容 HTML */
124
+ export function formatWeChatArticle(markdown: string, options: FormatOptions = {}): string {
125
+ if (!markdown || typeof markdown !== 'string') return '';
126
+
127
+ const theme = THEMES[options.theme || 'default'] || THEMES.default;
128
+ const fontSize = options.fontSize || 16;
129
+ const lineHeight = options.lineHeight || 1.8;
130
+
131
+ let html = markdown;
132
+
133
+ // 1. 代码块(最先处理,避免被后续规则影响)
134
+ html = convertCodeBlocks(html, theme);
135
+
136
+ // 2. 标题
137
+ html = convertHeadings(html, theme);
138
+
139
+ // 3. 文本格式
140
+ html = convertTextFormatting(html, theme);
141
+
142
+ // 4. 列表
143
+ html = convertLists(html);
144
+
145
+ // 5. 引用
146
+ html = convertBlockquotes(html, theme);
147
+
148
+ // 6. 链接
149
+ html = convertLinks(html, theme);
150
+
151
+ // 7. 表格
152
+ html = convertTables(html);
153
+
154
+ // 8. 段落
155
+ html = convertParagraphs(html, fontSize, lineHeight);
156
+
157
+ // 9. 清理
158
+ html = cleanupHTML(html);
159
+
160
+ // 10. 添加基础样式
161
+ return addBaseStyles(html, fontSize, lineHeight);
162
+ }
163
+
164
+ function convertCodeBlocks(html: string, theme: ThemeConfig): string {
165
+ // 围栏代码块
166
+ html = html.replace(
167
+ /```(\w+)?\n([\s\S]*?)```/g,
168
+ (_match, lang, code) => {
169
+ const language = lang || 'text';
170
+ return `<pre data-language="${language}" style="background: ${theme.codeBlock.bg}; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 14px; line-height: 1.4; border: 1px solid ${theme.codeBlock.border}; margin: 16px 0;"><code style="color: #333; background: none; padding: 0;">${escapeHtml(code.trim())}</code></pre>`;
171
+ }
172
+ );
173
+
174
+ // 行内代码
175
+ html = html.replace(
176
+ /`([^`]+)`/g,
177
+ `<code style="background: ${theme.inlineCode.bg}; color: ${theme.inlineCode.color}; padding: 2px 6px; border-radius: 4px; font-size: 0.9em;">$1</code>`
178
+ );
179
+
180
+ return html;
181
+ }
182
+
183
+ function convertHeadings(html: string, theme: ThemeConfig): string {
184
+ html = html.replace(
185
+ /^# (.+)$/gm,
186
+ `<h1 style="color: ${theme.h1.color}; font-size: 26px; font-weight: bold; margin: 24px 0 16px 0; line-height: 1.3; border-bottom: 3px solid ${theme.h1.border}; padding-bottom: 8px;">$1</h1>`
187
+ );
188
+ html = html.replace(
189
+ /^## (.+)$/gm,
190
+ `<h2 style="color: ${theme.h2.color}; font-size: 22px; font-weight: bold; margin: 20px 0 12px 0; line-height: 1.3;">${theme.h2.prefix} $1</h2>`
191
+ );
192
+ html = html.replace(
193
+ /^### (.+)$/gm,
194
+ `<h3 style="color: ${theme.h3.color}; font-size: 18px; font-weight: bold; margin: 18px 0 10px 0; line-height: 1.3;">${theme.h3.prefix} $1</h3>`
195
+ );
196
+ html = html.replace(
197
+ /^#### (.+)$/gm,
198
+ `<h4 style="color: ${theme.h4.color}; font-size: 17px; font-weight: bold; margin: 16px 0 8px 0; line-height: 1.3;">${theme.h4.prefix} $1</h4>`
199
+ );
200
+ return html;
201
+ }
202
+
203
+ function convertTextFormatting(html: string, theme: ThemeConfig): string {
204
+ html = html.replace(
205
+ /\*\*(.+?)\*\*/g,
206
+ `<strong style="color: ${theme.bold.color}; font-weight: bold;">$1</strong>`
207
+ );
208
+ html = html.replace(
209
+ /\*(?!\*)(.+?)(?<!\*)\*/g,
210
+ `<em style="color: ${theme.italic.color}; font-style: italic;">$1</em>`
211
+ );
212
+ html = html.replace(
213
+ /~~(.+?)~~/g,
214
+ `<del style="color: ${theme.del.color}; text-decoration: line-through;">$1</del>`
215
+ );
216
+ return html;
217
+ }
218
+
219
+ function convertLists(html: string): string {
220
+ // 有序列表
221
+ html = html.replace(
222
+ /^\d+\.\s+(.+)$/gm,
223
+ '<li style="margin: 8px 0; line-height: 1.6;">$1</li>'
224
+ );
225
+ // 无序列表
226
+ html = html.replace(
227
+ /^[-*+]\s+(.+)$/gm,
228
+ '<li style="margin: 8px 0; line-height: 1.6;">$1</li>'
229
+ );
230
+ // 包裹连续的 <li>
231
+ html = html.replace(
232
+ /(<li[^>]*>.*?<\/li>(\s*<li[^>]*>.*?<\/li>)*)/gs,
233
+ (match: string) => `<ul style="margin: 16px 0; padding-left: 24px; list-style-type: disc;">${match}</ul>`
234
+ );
235
+ return html;
236
+ }
237
+
238
+ function convertBlockquotes(html: string, theme: ThemeConfig): string {
239
+ return html.replace(
240
+ /^>\s*(.+)$/gm,
241
+ `<blockquote style="border-left: 4px solid ${theme.blockquote.border}; padding: 16px 20px; margin: 16px 0; background: ${theme.blockquote.bg}; font-style: italic; color: ${theme.blockquote.color}; border-radius: 0 8px 8px 0;">$1</blockquote>`
242
+ );
243
+ }
244
+
245
+ function convertLinks(html: string, theme: ThemeConfig): string {
246
+ return html.replace(
247
+ /\[([^\]]+)\]\(([^)]+)\)/g,
248
+ `<a href="$2" style="color: ${theme.link.color}; text-decoration: none; border-bottom: 1px dotted ${theme.link.color};" target="_blank">$1</a>`
249
+ );
250
+ }
251
+
252
+ function convertTables(html: string): string {
253
+ const lines = html.split('\n');
254
+ let inTable = false;
255
+ let tableLines: string[] = [];
256
+ const result: string[] = [];
257
+
258
+ for (const line of lines) {
259
+ const trimmed = line.trim();
260
+ if (trimmed.includes('|') && !inTable) {
261
+ inTable = true;
262
+ tableLines = [trimmed];
263
+ } else if (trimmed.includes('|') && inTable) {
264
+ tableLines.push(trimmed);
265
+ } else if (inTable) {
266
+ if (tableLines.length > 0) result.push(convertTableToHTML(tableLines));
267
+ inTable = false;
268
+ tableLines = [];
269
+ result.push(line);
270
+ } else {
271
+ result.push(line);
272
+ }
273
+ }
274
+ if (inTable && tableLines.length > 0) {
275
+ result.push(convertTableToHTML(tableLines));
276
+ }
277
+ return result.join('\n');
278
+ }
279
+
280
+ function convertTableToHTML(tableLines: string[]): string {
281
+ if (tableLines.length < 2) return tableLines.join('\n');
282
+
283
+ const headerLine = tableLines[0];
284
+ const separatorLine = tableLines[1];
285
+ const dataLines = tableLines.slice(2);
286
+
287
+ const headers = headerLine.split('|').map(h => h.trim()).filter(h => h);
288
+ if (!separatorLine.includes('-')) return tableLines.join('\n');
289
+
290
+ let tableHTML =
291
+ '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">';
292
+ tableHTML += '<thead><tr style="background: #f8f9fa;">';
293
+ headers.forEach(header => {
294
+ tableHTML += `<th style="border: 1px solid #dee2e6; padding: 12px 8px; text-align: left; font-weight: bold; color: #495057;">${header}</th>`;
295
+ });
296
+ tableHTML += '</tr></thead><tbody>';
297
+ dataLines.forEach((line, index) => {
298
+ const cells = line.split('|').map(c => c.trim()).filter(c => c);
299
+ const bg = index % 2 === 0 ? '#ffffff' : '#f8f9fa';
300
+ tableHTML += `<tr style="background: ${bg};">`;
301
+ cells.forEach(cell => {
302
+ tableHTML += `<th style="border: 1px solid #dee2e6; padding: 12px 8px; color: #495057;">${cell}</th>`;
303
+ });
304
+ tableHTML += '</tr>';
305
+ });
306
+ tableHTML += '</tbody></table>';
307
+ return tableHTML;
308
+ }
309
+
310
+ function convertParagraphs(html: string, fontSize: number, lineHeight: number): string {
311
+ html = html.replace(
312
+ /\n\s*\n/g,
313
+ `</p><p style="margin: 15px 0; line-height: ${lineHeight}; text-align: justify; color: #333; font-size: ${fontSize}px;">`
314
+ );
315
+ html =
316
+ `<p style="margin: 15px 0; line-height: ${lineHeight}; text-align: justify; color: #333; font-size: ${fontSize}px;">${html}</p>`;
317
+ return html;
318
+ }
319
+
320
+ function cleanupHTML(html: string): string {
321
+ html = html.replace(/<p[^>]*>\s*<\/p>/g, '');
322
+ html = html.replace(/<p[^>]*>(\s*<h[1-6][^>]*>.*?<\/h[1-6]>\s*)<\/p>/g, '$1');
323
+ html = html.replace(/<p[^>]*>(\s*<ul[^>]*>.*?<\/ul>\s*)<\/p>/gs, '$1');
324
+ html = html.replace(/<p[^>]*>(\s*<ol[^>]*>.*?<\/ol>\s*)<\/p>/gs, '$1');
325
+ html = html.replace(/<p[^>]*>(\s*<blockquote[^>]*>.*?<\/blockquote>\s*)<\/p>/gs, '$1');
326
+ html = html.replace(/<p[^>]*>(\s*<pre[^>]*>.*?<\/pre>\s*)<\/p>/gs, '$1');
327
+ html = html.replace(/<p[^>]*>(\s*<table[^>]*>.*?<\/table>\s*)<\/p>/gs, '$1');
328
+ return html;
329
+ }
330
+
331
+ function addBaseStyles(html: string, fontSize: number, lineHeight: number): string {
332
+ const style = `
333
+ <style>
334
+ body {
335
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
336
+ line-height: ${lineHeight};
337
+ color: #333;
338
+ background: #fff;
339
+ font-size: ${fontSize}px;
340
+ margin: 0;
341
+ padding: 20px;
342
+ }
343
+ img { max-width: 100%; height: auto; display: block; margin: 16px auto; border-radius: 8px; }
344
+ hr { border: none; height: 1px; background: linear-gradient(to right, transparent, #ddd, transparent); margin: 24px 0; }
345
+ </style>
346
+ `;
347
+ return style + html;
348
+ }