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
+ import { join, basename } from 'path'
2
+ import { existsSync, readdirSync, statSync, readFileSync } from 'fs'
3
+ import { homedir } from 'os'
4
+ import { createDecipheriv } from 'crypto'
5
+ import { expandHomePath } from '../utils/pathUtils.js'
6
+
7
+ export interface WxidInfo {
8
+ wxid: string
9
+ modifiedTime: number
10
+ nickname?: string
11
+ avatarUrl?: string
12
+ }
13
+
14
+ export class DbPathService {
15
+ private readVarint(buf: Buffer, offset: number): { value: number, length: number } {
16
+ let value = 0;
17
+ let length = 0;
18
+ let shift = 0;
19
+ while (offset < buf.length && shift < 32) {
20
+ const b = buf[offset++];
21
+ value |= (b & 0x7f) << shift;
22
+ length++;
23
+ if ((b & 0x80) === 0) break;
24
+ shift += 7;
25
+ }
26
+ return { value, length };
27
+ }
28
+
29
+ private extractMmkvString(buf: Buffer, keyName: string): string {
30
+ const keyBuf = Buffer.from(keyName, 'utf8');
31
+ const idx = buf.indexOf(keyBuf);
32
+ if (idx === -1) return '';
33
+
34
+ try {
35
+ let offset = idx + keyBuf.length;
36
+ const v1 = this.readVarint(buf, offset);
37
+ offset += v1.length;
38
+ const v2 = this.readVarint(buf, offset);
39
+ offset += v2.length;
40
+
41
+ // 合理性检查
42
+ if (v2.value > 0 && v2.value <= 10000 && offset + v2.value <= buf.length) {
43
+ return buf.toString('utf8', offset, offset + v2.value);
44
+ }
45
+ } catch { }
46
+ return '';
47
+ }
48
+
49
+ private parseGlobalConfig(rootPath: string): { wxid: string, nickname: string, avatarUrl: string } | null {
50
+ try {
51
+ const configPath = join(rootPath, 'all_users', 'config', 'global_config');
52
+ if (!existsSync(configPath)) return null;
53
+
54
+ const fullData = readFileSync(configPath);
55
+ if (fullData.length <= 4) return null;
56
+ const encryptedData = fullData.subarray(4);
57
+
58
+ const key = Buffer.alloc(16, 0);
59
+ Buffer.from('xwechat_crypt_key').copy(key); // 直接硬编码,iv更是不重要
60
+ const iv = Buffer.alloc(16, 0);
61
+
62
+ const decipher = createDecipheriv('aes-128-cfb', key, iv);
63
+ decipher.setAutoPadding(false);
64
+ const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
65
+
66
+ const wxid = this.extractMmkvString(decrypted, 'mmkv_key_user_name');
67
+ const nickname = this.extractMmkvString(decrypted, 'mmkv_key_nick_name');
68
+ let avatarUrl = this.extractMmkvString(decrypted, 'mmkv_key_head_img_url');
69
+
70
+ if (!avatarUrl && decrypted.includes('http')) {
71
+ const httpIdx = decrypted.indexOf('http');
72
+ const nullIdx = decrypted.indexOf(0x00, httpIdx);
73
+ if (nullIdx !== -1) {
74
+ avatarUrl = decrypted.toString('utf8', httpIdx, nullIdx);
75
+ }
76
+ }
77
+
78
+ if (wxid || nickname) {
79
+ return { wxid, nickname, avatarUrl };
80
+ }
81
+ return null;
82
+ } catch (e) {
83
+ console.error('解析 global_config 失败:', e);
84
+ return null;
85
+ }
86
+ }
87
+
88
+
89
+ /**
90
+ * 自动检测微信数据库根目录
91
+ */
92
+ async autoDetect(): Promise<{ success: boolean; path?: string; error?: string }> {
93
+ try {
94
+ const possiblePaths: string[] = []
95
+ const home = homedir()
96
+
97
+ if (process.platform === 'darwin') {
98
+ // macOS 微信 4.0.5+ 新路径(优先检测)
99
+ const appSupportBase = join(home, 'Library', 'Containers', 'com.tencent.xinWeChat', 'Data', 'Library', 'Application Support', 'com.tencent.xinWeChat')
100
+ if (existsSync(appSupportBase)) {
101
+ try {
102
+ const entries = readdirSync(appSupportBase)
103
+ for (const entry of entries) {
104
+ // 匹配形如 2.0b4.0.9 的版本目录
105
+ if (/^\d+\.\d+b\d+\.\d+/.test(entry) || /^\d+\.\d+\.\d+/.test(entry)) {
106
+ possiblePaths.push(join(appSupportBase, entry))
107
+ }
108
+ }
109
+ } catch { }
110
+ }
111
+ // macOS 旧路径兜底
112
+ possiblePaths.push(join(home, 'Library', 'Containers', 'com.tencent.xinWeChat', 'Data', 'Documents', 'xwechat_files'))
113
+ } else {
114
+ // Windows: 优先检测 NT 格式 (xwechat_files)
115
+ possiblePaths.push(join(home, 'xwechat_files'))
116
+ // Windows 微信4.x 数据目录 (Documents)
117
+ possiblePaths.push(join(home, 'Documents', 'xwechat_files'))
118
+ // Windows 微信3.x 数据目录
119
+ possiblePaths.push(join(home, 'Documents', 'WeChat Files'))
120
+ }
121
+
122
+ for (const path of possiblePaths) {
123
+ if (!existsSync(path)) continue
124
+
125
+ // 检查是否有有效的账号目录,或本身就是账号目录
126
+ const accounts = this.findAccountDirs(path)
127
+ if (accounts.length > 0) {
128
+ return { success: true, path }
129
+ }
130
+
131
+ // 如果该目录本身就是账号目录(直接包含 db_storage 等)
132
+ if (this.isAccountDir(path)) {
133
+ return { success: true, path }
134
+ }
135
+ }
136
+
137
+ return { success: false, error: '未能自动检测到微信数据库目录' }
138
+ } catch (e) {
139
+ return { success: false, error: String(e) }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * 查找账号目录(包含 db_storage 或图片目录)
145
+ */
146
+ findAccountDirs(rootPath: string): string[] {
147
+ const resolvedRootPath = expandHomePath(rootPath)
148
+ const accounts: string[] = []
149
+
150
+ try {
151
+ const entries = readdirSync(resolvedRootPath)
152
+
153
+ for (const entry of entries) {
154
+ const entryPath = join(resolvedRootPath, entry)
155
+ let stat: ReturnType<typeof statSync>
156
+ try {
157
+ stat = statSync(entryPath)
158
+ } catch {
159
+ continue
160
+ }
161
+
162
+ if (stat.isDirectory()) {
163
+ if (!this.isPotentialAccountName(entry)) continue
164
+
165
+ // 检查是否有有效账号目录结构
166
+ if (this.isAccountDir(entryPath)) {
167
+ accounts.push(entry)
168
+ }
169
+ }
170
+ }
171
+ } catch { }
172
+
173
+ return accounts
174
+ }
175
+
176
+ private isAccountDir(entryPath: string): boolean {
177
+ return (
178
+ existsSync(join(entryPath, 'db_storage')) || // WeChat 4.x
179
+ existsSync(join(entryPath, 'Msg')) || // WeChat 3.x
180
+ existsSync(join(entryPath, 'FileStorage', 'Image')) ||
181
+ existsSync(join(entryPath, 'FileStorage', 'Image2'))
182
+ )
183
+ }
184
+
185
+ private isPotentialAccountName(name: string): boolean {
186
+ const lower = name.toLowerCase()
187
+ if (lower.startsWith('all') || lower.startsWith('applet') || lower.startsWith('backup') || lower.startsWith('wmpf')) {
188
+ return false
189
+ }
190
+ return true
191
+ }
192
+
193
+ private getAccountModifiedTime(entryPath: string): number {
194
+ try {
195
+ const accountStat = statSync(entryPath)
196
+ let latest = accountStat.mtimeMs
197
+
198
+ const dbPath = join(entryPath, 'db_storage')
199
+ if (existsSync(dbPath)) {
200
+ const dbStat = statSync(dbPath)
201
+ latest = Math.max(latest, dbStat.mtimeMs)
202
+ }
203
+
204
+ const imagePath = join(entryPath, 'FileStorage', 'Image')
205
+ if (existsSync(imagePath)) {
206
+ const imageStat = statSync(imagePath)
207
+ latest = Math.max(latest, imageStat.mtimeMs)
208
+ }
209
+
210
+ const image2Path = join(entryPath, 'FileStorage', 'Image2')
211
+ if (existsSync(image2Path)) {
212
+ const image2Stat = statSync(image2Path)
213
+ latest = Math.max(latest, image2Stat.mtimeMs)
214
+ }
215
+
216
+ return latest
217
+ } catch {
218
+ return 0
219
+ }
220
+ }
221
+
222
+ /**
223
+ * 扫描目录名候选(仅包含下划线的文件夹,排除 all_users)
224
+ */
225
+ scanWxidCandidates(rootPath: string): WxidInfo[] {
226
+ const resolvedRootPath = expandHomePath(rootPath)
227
+ const wxids: WxidInfo[] = []
228
+
229
+ try {
230
+ if (existsSync(resolvedRootPath)) {
231
+ const entries = readdirSync(resolvedRootPath)
232
+ for (const entry of entries) {
233
+ const entryPath = join(resolvedRootPath, entry)
234
+ let stat: ReturnType<typeof statSync>
235
+ try { stat = statSync(entryPath) } catch { continue }
236
+ if (!stat.isDirectory()) continue
237
+ const lower = entry.toLowerCase()
238
+ if (lower === 'all_users') continue
239
+ if (!entry.includes('_')) continue
240
+ wxids.push({ wxid: entry, modifiedTime: stat.mtimeMs })
241
+ }
242
+ }
243
+
244
+
245
+ if (wxids.length === 0) {
246
+ const rootName = basename(resolvedRootPath)
247
+ if (rootName.includes('_') && rootName.toLowerCase() !== 'all_users') {
248
+ const rootStat = statSync(resolvedRootPath)
249
+ wxids.push({ wxid: rootName, modifiedTime: rootStat.mtimeMs })
250
+ }
251
+ }
252
+ } catch { }
253
+
254
+ const sorted = wxids.sort((a, b) => {
255
+ if (b.modifiedTime !== a.modifiedTime) return b.modifiedTime - a.modifiedTime
256
+ return a.wxid.localeCompare(b.wxid)
257
+ });
258
+
259
+ const globalInfo = this.parseGlobalConfig(resolvedRootPath);
260
+ if (globalInfo) {
261
+ for (const w of sorted) {
262
+ if (w.wxid.startsWith(globalInfo.wxid) || sorted.length === 1) {
263
+ w.nickname = globalInfo.nickname;
264
+ w.avatarUrl = globalInfo.avatarUrl;
265
+ }
266
+ }
267
+ }
268
+
269
+ return sorted;
270
+ }
271
+
272
+
273
+ /**
274
+ * 扫描 wxid 列表
275
+ */
276
+ scanWxids(rootPath: string): WxidInfo[] {
277
+ const resolvedRootPath = expandHomePath(rootPath)
278
+ const wxids: WxidInfo[] = []
279
+
280
+ try {
281
+ if (this.isAccountDir(resolvedRootPath)) {
282
+ const wxid = basename(resolvedRootPath)
283
+ const modifiedTime = this.getAccountModifiedTime(resolvedRootPath)
284
+ return [{ wxid, modifiedTime }]
285
+ }
286
+
287
+ const accounts = this.findAccountDirs(resolvedRootPath)
288
+
289
+ for (const account of accounts) {
290
+ const fullPath = join(resolvedRootPath, account)
291
+ const modifiedTime = this.getAccountModifiedTime(fullPath)
292
+ wxids.push({ wxid: account, modifiedTime })
293
+ }
294
+ } catch { }
295
+
296
+ const sorted = wxids.sort((a, b) => {
297
+ if (b.modifiedTime !== a.modifiedTime) return b.modifiedTime - a.modifiedTime
298
+ return a.wxid.localeCompare(b.wxid)
299
+ });
300
+
301
+ const globalInfo = this.parseGlobalConfig(resolvedRootPath);
302
+ if (globalInfo) {
303
+ for (const w of sorted) {
304
+ if (w.wxid.startsWith(globalInfo.wxid) || sorted.length === 1) {
305
+ w.nickname = globalInfo.nickname;
306
+ w.avatarUrl = globalInfo.avatarUrl;
307
+ }
308
+ }
309
+ }
310
+ return sorted;
311
+ }
312
+
313
+ /**
314
+ * 获取默认数据库路径
315
+ */
316
+ getDefaultPath(): string {
317
+ const home = homedir()
318
+ if (process.platform === 'darwin') {
319
+ // 优先返回 4.0.5+ 新路径
320
+ const appSupportBase = join(home, 'Library', 'Containers', 'com.tencent.xinWeChat', 'Data', 'Library', 'Application Support', 'com.tencent.xinWeChat')
321
+ if (existsSync(appSupportBase)) {
322
+ try {
323
+ const entries = readdirSync(appSupportBase)
324
+ for (const entry of entries) {
325
+ if (/^\d+\.\d+b\d+\.\d+/.test(entry) || /^\d+\.\d+\.\d+/.test(entry)) {
326
+ const candidate = join(appSupportBase, entry)
327
+ if (existsSync(candidate)) return candidate
328
+ }
329
+ }
330
+ } catch { }
331
+ }
332
+ // 旧版本路径兜底
333
+ return join(home, 'Library', 'Containers', 'com.tencent.xinWeChat', 'Data', 'Documents', 'xwechat_files')
334
+ }
335
+ // 优先返回 4.x 路径
336
+ const xwechatPath = join(home, 'Documents', 'xwechat_files')
337
+ if (existsSync(xwechatPath)) return xwechatPath
338
+ // 备用 4.x 路径(部分安装将数据放在用户目录下)
339
+ const xwechatHomePath = join(home, 'xwechat_files')
340
+ if (existsSync(xwechatHomePath)) return xwechatHomePath
341
+ // 兜底 3.x 路径
342
+ const wechatFilesPath = join(home, 'Documents', 'WeChat Files')
343
+ if (existsSync(wechatFilesPath)) return wechatFilesPath
344
+ return xwechatPath
345
+ }
346
+ }
347
+
348
+ export const dbPathService = new DbPathService()