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,72 @@
1
+ /**
2
+ * 白名单服务 — 控制消息收发权限,仅白名单内 wxid 可收发消息。
3
+ */
4
+ import { configService } from './configService.js'
5
+ import { resolveTalker } from '../utils/talkerUtils.js'
6
+
7
+ export interface WhitelistEntry {
8
+ wxid: string
9
+ displayName: string
10
+ }
11
+
12
+ export class WhitelistService {
13
+ getList(): string[] {
14
+ return configService.getWhitelist()
15
+ }
16
+
17
+ isAllowed(wxid: string): boolean {
18
+ return configService.getWhitelist().includes(wxid)
19
+ }
20
+
21
+ /** 直接添加 wxid(不解析昵称),用于 CLI 确认后的最终写入 */
22
+ addDirect(wxid: string): void {
23
+ const list = configService.getWhitelist()
24
+ if (!list.includes(wxid)) {
25
+ list.push(wxid)
26
+ configService.setWhitelist(list)
27
+ }
28
+ }
29
+
30
+ async add(target: string): Promise<{ success: boolean; error?: string; wxid?: string }> {
31
+ let wxid: string
32
+
33
+ // 已知格式直接使用,否则通过 resolveTalker 解析
34
+ if (target.startsWith('wxid_') || target.includes('@chatroom') || target.includes('@openim')) {
35
+ wxid = target
36
+ } else {
37
+ try {
38
+ wxid = await resolveTalker(target)
39
+ } catch (e: any) {
40
+ return { success: false, error: `无法解析 "${target}": ${e.message}` }
41
+ }
42
+ }
43
+
44
+ const list = configService.getWhitelist()
45
+ if (list.includes(wxid)) {
46
+ return { success: false, error: `"${wxid}" 已在白名单中` }
47
+ }
48
+
49
+ list.push(wxid)
50
+ configService.setWhitelist(list)
51
+ return { success: true, wxid }
52
+ }
53
+
54
+ remove(wxid: string): boolean {
55
+ const list = configService.getWhitelist()
56
+ const idx = list.indexOf(wxid)
57
+ if (idx < 0) return false
58
+ list.splice(idx, 1)
59
+ configService.setWhitelist(list)
60
+ return true
61
+ }
62
+
63
+ clear(): void {
64
+ configService.setWhitelist([])
65
+ }
66
+
67
+ reload(): void {
68
+ // configService 每次 getWhitelist 都从内存读取,无需主动 reload
69
+ }
70
+ }
71
+
72
+ export const whitelistService = new WhitelistService()
package/src/types.ts ADDED
@@ -0,0 +1,141 @@
1
+ export interface WxidInfo {
2
+ wxid: string
3
+ modifiedTime: number
4
+ nickname?: string
5
+ avatarUrl?: string
6
+ }
7
+
8
+ export interface ChatSession {
9
+ username: string
10
+ type: number
11
+ unreadCount: number
12
+ summary: string
13
+ sortTimestamp: number
14
+ lastTimestamp: number
15
+ displayName?: string
16
+ avatarUrl?: string
17
+ lastMsgType?: number
18
+ }
19
+
20
+ export interface Message {
21
+ localId: number
22
+ serverId: string
23
+ localType: number
24
+ createTime: number
25
+ isSend: number | null
26
+ senderUsername: string | null
27
+ content: string
28
+ rawContent: string
29
+ parsedContent: string
30
+ mediaType?: string
31
+ mediaFileName?: string
32
+ /** Type=3 图片: 缩略图 base64 数据 */
33
+ imageBase64?: string
34
+ /** Type=3 图片: 图片文件名 (UUID.png) */
35
+ imageFileName?: string
36
+ /** Type=49 AppMsg: 标题 */
37
+ appTitle?: string
38
+ /** Type=49 AppMsg: 描述 */
39
+ appDescription?: string
40
+ /** Type=49 AppMsg: URL */
41
+ appUrl?: string
42
+ }
43
+
44
+ export interface Contact {
45
+ username: string
46
+ displayName: string
47
+ remark?: string
48
+ nickname?: string
49
+ alias?: string
50
+ avatarUrl?: string
51
+ type?: string
52
+ }
53
+
54
+ // === 微信消息通道 ===
55
+
56
+ export interface WechatOCConfig {
57
+ baseUrl?: string
58
+ cdnBaseUrl?: string
59
+ botType?: string
60
+ token?: string
61
+ accountId?: string
62
+ syncBuf?: string
63
+ contextTokens?: Record<string, string>
64
+ longPollTimeoutMs?: number
65
+ apiTimeoutMs?: number
66
+ typingKeepaliveIntervalS?: number
67
+ typingTicketTtlS?: number
68
+ }
69
+
70
+ export interface WechatLoginSession {
71
+ sessionKey: string
72
+ qrcode: string
73
+ qrcodeImgContent: string
74
+ startedAt: number
75
+ status: 'wait' | 'confirmed' | 'expired' | 'scanned'
76
+ botToken?: string
77
+ accountId?: string
78
+ baseUrl?: string
79
+ userId?: string
80
+ error?: string
81
+ }
82
+
83
+ export interface WechatInboundMessage {
84
+ messageId: string
85
+ fromUserId: string
86
+ senderNickname: string
87
+ timestamp: number
88
+ timestampMs: number
89
+ components: WechatMessageComponent[]
90
+ messageStr: string
91
+ messageKind: 'text' | 'image' | 'voice' | 'file' | 'video' | 'unknown'
92
+ rawMessage: Record<string, any>
93
+ isReply: boolean
94
+ quotedText?: string
95
+ }
96
+
97
+ export type WechatMessageComponent =
98
+ | { type: 'plain'; text: string }
99
+ | { type: 'image'; filePath: string }
100
+ | { type: 'record'; filePath: string }
101
+ | { type: 'file'; name: string; filePath: string }
102
+ | { type: 'video'; filePath: string }
103
+
104
+ export interface ExportOptions {
105
+ talker: string
106
+ format: 'json' | 'html' | 'txt' | 'excel'
107
+ output: string
108
+ limit?: number
109
+ start?: string
110
+ end?: string
111
+ }
112
+
113
+ export interface DbKeyResult {
114
+ success: boolean
115
+ key?: string
116
+ error?: string
117
+ logs?: string[]
118
+ }
119
+
120
+ export type DataVersion = '3.x' | '4.x'
121
+
122
+ export interface ConfigData {
123
+ dbPath?: string
124
+ wxid?: string
125
+ decryptKey?: string
126
+ decryptKey3x?: string
127
+ dataVersion?: DataVersion
128
+ dbPath3x?: string
129
+ /** NT 格式: 主消息数据库路径 (message_0.db) */
130
+ ntDbPath?: string
131
+ /** NT 格式: 64位十六进制密钥 */
132
+ ntKey?: string
133
+ /** NT 格式: 32位十六进制盐值 */
134
+ ntSalt?: string
135
+ /** NT 格式: 联系人数据库路径 (contact.db) */
136
+ contactDbPath?: string
137
+ /** NT 格式: contact.db 64位十六进制密钥 */
138
+ contactKey?: string
139
+ /** NT 格式: contact.db 32位十六进制盐值 */
140
+ contactSalt?: string
141
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * WeflowError - weflow-cli 统一错误类
3
+ *
4
+ * 提供结构化的错误信息,包含:
5
+ * - message: 人类可读的错误描述
6
+ * - code: 错误代码(用于程序化判断)
7
+ * - exitCode: 进程退出码
8
+ */
9
+
10
+ export class WeflowError extends Error {
11
+ constructor(
12
+ message: string,
13
+ public code: string = 'UNKNOWN',
14
+ public exitCode: number = 1,
15
+ ) {
16
+ super(message)
17
+ this.name = 'WeflowError'
18
+ }
19
+ }
20
+
21
+ /**
22
+ * 统一错误处理函数。
23
+ *
24
+ * 根据错误类型输出对应的错误信息并以合适的退出码退出进程:
25
+ * - WeflowError: 使用其自带的 exitCode
26
+ * - 普通 Error: 视为未预期错误,退出码 1
27
+ * - 其他: 视为未知错误,退出码 1
28
+ *
29
+ * @param e 捕获到的错误对象
30
+ */
31
+ export function handleError(e: unknown): void {
32
+ if (e instanceof WeflowError) {
33
+ console.error(`[weflow-cli] ${e.message}`)
34
+ process.exit(e.exitCode)
35
+ } else if (e instanceof Error) {
36
+ console.error(`[weflow-cli] 未预期的错误: ${e.message}`)
37
+ process.exit(1)
38
+ } else {
39
+ console.error('[weflow-cli] 未知错误')
40
+ process.exit(1)
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ import { homedir } from 'os'
2
+
3
+ /**
4
+ * Expand "~" prefix to current user's home directory.
5
+ */
6
+ export function expandHomePath(inputPath: string): string {
7
+ const raw = String(inputPath || '').trim()
8
+ if (!raw) return raw
9
+
10
+ if (raw === '~') return homedir()
11
+ if (/^~[\\/]/.test(raw)) {
12
+ return `${homedir()}${raw.slice(1)}`
13
+ }
14
+
15
+ return raw
16
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * pythonRunner - 统一的 Python 脚本调用工具
3
+ *
4
+ * 消除 bin/weflow-cli.ts 中重复出现的 execFile 调用模式,
5
+ * 统一封装路径解析、参数序列化、错误处理。
6
+ */
7
+ import { execFile } from 'child_process'
8
+ import { promisify } from 'util'
9
+ import { fileURLToPath } from 'url'
10
+ import { dirname, join } from 'path'
11
+ import { existsSync } from 'fs'
12
+
13
+ const execFileAsync = promisify(execFile)
14
+
15
+ export interface PythonScriptResult {
16
+ stdout: string
17
+ stderr: string
18
+ exitCode: number
19
+ }
20
+
21
+ export interface PythonScriptError extends Error {
22
+ script: string
23
+ stderr: string
24
+ exitCode: number
25
+ }
26
+
27
+ /** 获取项目根目录 */
28
+ export function getProjectRoot(): string {
29
+ // 兼容 ESM 和 CJS
30
+ const possibleDirname = typeof __dirname !== 'undefined'
31
+ ? __dirname
32
+ : dirname(fileURLToPath(import.meta.url))
33
+ // 从 src/utils/ 或 bin/ 向上找到 package.json 所在目录
34
+ let dir = possibleDirname
35
+ for (let i = 0; i < 5; i++) {
36
+ try {
37
+ if (existsSync(join(dir, 'package.json'))) return dir
38
+ } catch {}
39
+ dir = dirname(dir)
40
+ }
41
+ return possibleDirname
42
+ }
43
+
44
+ /**
45
+ * 运行 Python 脚本,统一封装路径解析、参数序列化、错误处理。
46
+ *
47
+ * @param scriptName 脚本文件名(如 'biz_daily.py'),相对于 scripts/ 目录
48
+ * @param args 命令行参数数组
49
+ * @param options 可选配置
50
+ * @returns 脚本执行结果
51
+ * @throws PythonScriptError 当脚本执行失败时
52
+ */
53
+ export async function runPythonScript(
54
+ scriptName: string,
55
+ args: string[] = [],
56
+ options: { cwd?: string; timeout?: number; env?: Record<string, string> } = {},
57
+ ): Promise<PythonScriptResult> {
58
+ const projectRoot = getProjectRoot()
59
+ const scriptPath = join(projectRoot, 'scripts', scriptName)
60
+ const cwd = options.cwd || projectRoot
61
+
62
+ try {
63
+ const { stdout, stderr } = await execFileAsync('python', [scriptPath, ...args], {
64
+ cwd,
65
+ timeout: options.timeout || 300000, // 默认 5 分钟
66
+ maxBuffer: 10 * 1024 * 1024, // 10MB
67
+ env: { ...process.env, ...options.env },
68
+ })
69
+ return { stdout, stderr, exitCode: 0 }
70
+ } catch (e: any) {
71
+ const error: PythonScriptError = Object.assign(
72
+ new Error(`Python 脚本 ${scriptName} 执行失败: ${e.message}`),
73
+ {
74
+ script: scriptName,
75
+ stderr: e.stderr || '',
76
+ exitCode: e.code || 1,
77
+ },
78
+ )
79
+ throw error
80
+ }
81
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Talker (wxid) resolution utilities.
3
+ * Supports: wxid / @chatroom / @openim / nickname / remark / index
4
+ */
5
+ import inquirer from 'inquirer'
6
+ import { chatService } from '../services/chatService.js'
7
+ import type { ChatSession } from '../types.js'
8
+
9
+ /**
10
+ * 将用户输入解析为 talker (wxid)。
11
+ *
12
+ * 支持: wxid_xxx / 群聊ID / 昵称 / 备注名 / 序号 [N] / 纯数字
13
+ *
14
+ * @throws {Error} 当输入无法解析到任何会话时
15
+ */
16
+ export async function resolveTalker(input: string): Promise<string> {
17
+ // 1. 已知格式直接返回
18
+ if (input.startsWith('wxid_') || input.includes('@chatroom') || input.includes('@openim')) {
19
+ return input
20
+ }
21
+
22
+ // 2. 序号匹配: [N] 或纯数字
23
+ const numMatch = input.match(/^\[?(\d+)\]?$/)
24
+ if (numMatch) {
25
+ const index = parseInt(numMatch[1]) - 1
26
+ const sessions = await chatService.listSessions()
27
+ if (index < 0 || index >= sessions.length) {
28
+ throw new Error(`序号 ${input} 超出范围,共 ${sessions.length} 个会话。运行 weflow-cli sessions 查看列表`)
29
+ }
30
+ return sessions[index].username
31
+ }
32
+
33
+ // 3. 昵称/备注搜索
34
+ const sessions = await chatService.listSessions(input, 50)
35
+
36
+ if (sessions.length === 0) {
37
+ throw new Error(`找不到 "${input}" 对应的会话。运行 weflow-cli sessions 查看所有会话`)
38
+ }
39
+
40
+ // 精确匹配
41
+ const exact = sessions.find(s =>
42
+ s.displayName === input || s.username === input,
43
+ )
44
+ if (exact) return exact.username
45
+
46
+ // 只有一个匹配
47
+ if (sessions.length === 1) {
48
+ return sessions[0].username
49
+ }
50
+
51
+ // 多个匹配 — 交互选择
52
+ const choices = sessions.slice(0, 20).map(s => ({
53
+ name: `${s.displayName} (${s.username})`,
54
+ value: s.username,
55
+ }))
56
+
57
+ const { selected } = await inquirer.prompt([{
58
+ type: 'select',
59
+ name: 'selected',
60
+ message: `"${input}" 匹配到多个会话,请选择:`,
61
+ choices,
62
+ loop: false,
63
+ }])
64
+
65
+ return selected
66
+ }
67
+
68
+ /**
69
+ * 批量为 wxid 列表查找 displayName。
70
+ * 调用 chatService.listSessions 获取带昵称的会话列表。
71
+ */
72
+ export async function lookupDisplayNames(wxids: string[]): Promise<Map<string, string>> {
73
+ const sessions = await chatService.listSessions()
74
+ const nameMap = new Map<string, string>()
75
+ const sessionMap = new Map<string, ChatSession>()
76
+ for (const s of sessions) {
77
+ sessionMap.set(s.username, s)
78
+ }
79
+ for (const wxid of wxids) {
80
+ const s = sessionMap.get(wxid)
81
+ nameMap.set(wxid, s?.displayName || wxid)
82
+ }
83
+ return nameMap
84
+ }