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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/weflow-cli-electron.cjs +59 -0
- package/bin/weflow-cli.ts +2311 -0
- package/cli.cjs +3 -0
- package/dist/bin/weflow-cli.d.ts +3 -0
- package/dist/bin/weflow-cli.d.ts.map +1 -0
- package/dist/bin/weflow-cli.js +2270 -0
- package/dist/bin/weflow-cli.js.map +1 -0
- package/dist/mcp-server/index.d.ts +3 -0
- package/dist/mcp-server/index.d.ts.map +1 -0
- package/dist/mcp-server/index.js +690 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/src/core/dbPathService.d.ts +40 -0
- package/dist/src/core/dbPathService.d.ts.map +1 -0
- package/dist/src/core/dbPathService.js +324 -0
- package/dist/src/core/dbPathService.js.map +1 -0
- package/dist/src/core/keyService.d.ts +59 -0
- package/dist/src/core/keyService.d.ts.map +1 -0
- package/dist/src/core/keyService.js +368 -0
- package/dist/src/core/keyService.js.map +1 -0
- package/dist/src/core/ntCore.d.ts +62 -0
- package/dist/src/core/ntCore.d.ts.map +1 -0
- package/dist/src/core/ntCore.js +191 -0
- package/dist/src/core/ntCore.js.map +1 -0
- package/dist/src/core/sqlcipherCore.d.ts +119 -0
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -0
- package/dist/src/core/sqlcipherCore.js +693 -0
- package/dist/src/core/sqlcipherCore.js.map +1 -0
- package/dist/src/core/wcdbCore.d.ts +738 -0
- package/dist/src/core/wcdbCore.d.ts.map +1 -0
- package/dist/src/core/wcdbCore.js +4545 -0
- package/dist/src/core/wcdbCore.js.map +1 -0
- package/dist/src/core/wechatClient.d.ts +23 -0
- package/dist/src/core/wechatClient.d.ts.map +1 -0
- package/dist/src/core/wechatClient.js +166 -0
- package/dist/src/core/wechatClient.js.map +1 -0
- package/dist/src/services/chatService.d.ts +25 -0
- package/dist/src/services/chatService.d.ts.map +1 -0
- package/dist/src/services/chatService.js +291 -0
- package/dist/src/services/chatService.js.map +1 -0
- package/dist/src/services/configService.d.ts +43 -0
- package/dist/src/services/configService.d.ts.map +1 -0
- package/dist/src/services/configService.js +156 -0
- package/dist/src/services/configService.js.map +1 -0
- package/dist/src/services/exportService.d.ts +30 -0
- package/dist/src/services/exportService.d.ts.map +1 -0
- package/dist/src/services/exportService.js +273 -0
- package/dist/src/services/exportService.js.map +1 -0
- package/dist/src/services/wechat-formatter.d.ts +69 -0
- package/dist/src/services/wechat-formatter.d.ts.map +1 -0
- package/dist/src/services/wechat-formatter.js +248 -0
- package/dist/src/services/wechat-formatter.js.map +1 -0
- package/dist/src/services/wechatMessageService.d.ts +28 -0
- package/dist/src/services/wechatMessageService.d.ts.map +1 -0
- package/dist/src/services/wechatMessageService.js +341 -0
- package/dist/src/services/wechatMessageService.js.map +1 -0
- package/dist/src/services/wereadService.d.ts +206 -0
- package/dist/src/services/wereadService.d.ts.map +1 -0
- package/dist/src/services/wereadService.js +145 -0
- package/dist/src/services/wereadService.js.map +1 -0
- package/dist/src/services/whitelistService.d.ts +20 -0
- package/dist/src/services/whitelistService.d.ts.map +1 -0
- package/dist/src/services/whitelistService.js +60 -0
- package/dist/src/services/whitelistService.js.map +1 -0
- package/dist/src/types.d.ts +140 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/errors.d.ts +25 -0
- package/dist/src/utils/errors.d.ts.map +1 -0
- package/dist/src/utils/errors.js +43 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/pathUtils.d.ts +5 -0
- package/dist/src/utils/pathUtils.d.ts.map +1 -0
- package/dist/src/utils/pathUtils.js +16 -0
- package/dist/src/utils/pathUtils.js.map +1 -0
- package/dist/src/utils/pythonRunner.d.ts +27 -0
- package/dist/src/utils/pythonRunner.d.ts.map +1 -0
- package/dist/src/utils/pythonRunner.js +62 -0
- package/dist/src/utils/pythonRunner.js.map +1 -0
- package/dist/src/utils/talkerUtils.d.ts +14 -0
- package/dist/src/utils/talkerUtils.d.ts.map +1 -0
- package/dist/src/utils/talkerUtils.js +73 -0
- package/dist/src/utils/talkerUtils.js.map +1 -0
- package/mcp-server/index.ts +757 -0
- package/package.json +72 -0
- package/resources/key/win32/x64/wx_key.dll +0 -0
- package/resources/wcdb/win32/x64/SDL2.dll +0 -0
- package/resources/wcdb/win32/x64/WCDB.dll +0 -0
- package/resources/wcdb/win32/x64/msvcp140.dll +0 -0
- package/resources/wcdb/win32/x64/msvcp140_1.dll +0 -0
- package/resources/wcdb/win32/x64/vcruntime140.dll +0 -0
- package/resources/wcdb/win32/x64/vcruntime140_1.dll +0 -0
- package/resources/wcdb/win32/x64/wcdb_api.dll +0 -0
- package/scripts/_batch_link_sources.py +73 -0
- package/scripts/_utils.py +531 -0
- package/scripts/annual_report.py +796 -0
- package/scripts/auto_tag.py +128 -0
- package/scripts/biz_daily.py +896 -0
- package/scripts/chat_report.py +397 -0
- package/scripts/chat_stats.py +702 -0
- package/scripts/classify_daily.py +411 -0
- package/scripts/compile_wiki.py +258 -0
- package/scripts/create_reading_notes.py +370 -0
- package/scripts/enrich_backlinks.py +163 -0
- package/scripts/export_chat_html.py +766 -0
- package/scripts/extract_3x_key.py +86 -0
- package/scripts/extract_todos.py +404 -0
- package/scripts/fav_server.py +392 -0
- package/scripts/fix_topics.py +111 -0
- package/scripts/generate_ai_report.py +367 -0
- package/scripts/generate_html.py +1372 -0
- package/scripts/generate_review.py +170 -0
- package/scripts/mcp_bridge.py +398 -0
- package/scripts/nt_decrypt.py +562 -0
- package/scripts/pipeline.py +145 -0
- package/scripts/promote_all.py +310 -0
- package/scripts/promote_ideas.py +297 -0
- package/scripts/rag_chat.py +191 -0
- package/scripts/scan_decrypt_4x.py +329 -0
- package/scripts/semantic_search.py +431 -0
- package/scripts/sync_fav.py +146 -0
- package/scripts/sync_weread.py +192 -0
- package/scripts/vault_rag.py +139 -0
- package/scripts/vault_search.py +141 -0
- package/src/core/dbPathService.ts +348 -0
- package/src/core/keyService.ts +409 -0
- package/src/core/ntCore.ts +224 -0
- package/src/core/sqlcipherCore.ts +764 -0
- package/src/core/wcdbCore.ts +4568 -0
- package/src/core/wechatClient.ts +204 -0
- package/src/lz4.d.ts +10 -0
- package/src/services/chatService.ts +296 -0
- package/src/services/configService.ts +197 -0
- package/src/services/exportService.ts +305 -0
- package/src/services/wechat-formatter.ts +348 -0
- package/src/services/wechatMessageService.ts +383 -0
- package/src/services/wereadService.ts +308 -0
- package/src/services/whitelistService.ts +72 -0
- package/src/types.ts +141 -0
- package/src/utils/errors.ts +42 -0
- package/src/utils/pathUtils.ts +16 -0
- package/src/utils/pythonRunner.ts +81 -0
- 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
|
+
}
|