weflow-cli 1.5.0 → 1.6.1
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/ARCHITECTURE.md +105 -0
- package/CHANGELOG.md +121 -0
- package/CONTRIBUTING.md +45 -0
- package/LICENSE +21 -21
- package/OPERATIONS.md +272 -0
- package/README.en.md +281 -0
- package/README.md +334 -149
- package/SECURITY.md +47 -0
- package/bin/weflow-cli.ts +4050 -581
- package/dist/bin/weflow-cli.js +4310 -588
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.js +178 -236
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/dbPathService.d.ts +15 -1
- package/dist/src/core/dbPathService.d.ts.map +1 -1
- package/dist/src/core/dbPathService.js +177 -19
- package/dist/src/core/dbPathService.js.map +1 -1
- package/dist/src/core/keyService.d.ts +8 -0
- package/dist/src/core/keyService.d.ts.map +1 -1
- package/dist/src/core/keyService.js +127 -22
- package/dist/src/core/keyService.js.map +1 -1
- package/dist/src/core/ntCore.d.ts +59 -1
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +201 -65
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
- package/dist/src/core/sqlcipherCore.js +18 -1
- package/dist/src/core/sqlcipherCore.js.map +1 -1
- package/dist/src/core/wcdbCore.d.ts.map +1 -1
- package/dist/src/core/wcdbCore.js +3 -6
- package/dist/src/core/wcdbCore.js.map +1 -1
- package/dist/src/services/assistantDaemon.d.ts +19 -0
- package/dist/src/services/assistantDaemon.d.ts.map +1 -0
- package/dist/src/services/assistantDaemon.js +117 -0
- package/dist/src/services/assistantDaemon.js.map +1 -0
- package/dist/src/services/assistantMemory.d.ts +36 -0
- package/dist/src/services/assistantMemory.d.ts.map +1 -0
- package/dist/src/services/assistantMemory.js +182 -0
- package/dist/src/services/assistantMemory.js.map +1 -0
- package/dist/src/services/assistantPrivacy.d.ts +25 -0
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -0
- package/dist/src/services/assistantPrivacy.js +77 -0
- package/dist/src/services/assistantPrivacy.js.map +1 -0
- package/dist/src/services/assistantRouting.d.ts +18 -0
- package/dist/src/services/assistantRouting.d.ts.map +1 -0
- package/dist/src/services/assistantRouting.js +68 -0
- package/dist/src/services/assistantRouting.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +37 -0
- package/dist/src/services/assistantService.d.ts.map +1 -0
- package/dist/src/services/assistantService.js +284 -0
- package/dist/src/services/assistantService.js.map +1 -0
- package/dist/src/services/assistantTools.d.ts +24 -0
- package/dist/src/services/assistantTools.d.ts.map +1 -0
- package/dist/src/services/assistantTools.js +582 -0
- package/dist/src/services/assistantTools.js.map +1 -0
- package/dist/src/services/chatService.d.ts +44 -0
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +105 -4
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/configService.d.ts +55 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +147 -6
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/evidenceService.d.ts +30 -0
- package/dist/src/services/evidenceService.d.ts.map +1 -0
- package/dist/src/services/evidenceService.js +98 -0
- package/dist/src/services/evidenceService.js.map +1 -0
- package/dist/src/services/exportService.d.ts +13 -20
- package/dist/src/services/exportService.d.ts.map +1 -1
- package/dist/src/services/exportService.js +113 -47
- package/dist/src/services/exportService.js.map +1 -1
- package/dist/src/services/initKeyService.d.ts +19 -0
- package/dist/src/services/initKeyService.d.ts.map +1 -0
- package/dist/src/services/initKeyService.js +80 -0
- package/dist/src/services/initKeyService.js.map +1 -0
- package/dist/src/services/messageContract.d.ts +23 -0
- package/dist/src/services/messageContract.d.ts.map +1 -0
- package/dist/src/services/messageContract.js +49 -0
- package/dist/src/services/messageContract.js.map +1 -0
- package/dist/src/services/messageQuery.d.ts +7 -0
- package/dist/src/services/messageQuery.d.ts.map +1 -0
- package/dist/src/services/messageQuery.js +30 -0
- package/dist/src/services/messageQuery.js.map +1 -0
- package/dist/src/services/wechat-formatter.js +14 -14
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +31 -3
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/services/whitelistService.d.ts +52 -4
- package/dist/src/services/whitelistService.d.ts.map +1 -1
- package/dist/src/services/whitelistService.js +183 -17
- package/dist/src/services/whitelistService.js.map +1 -1
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/dateRange.d.ts +15 -0
- package/dist/src/utils/dateRange.d.ts.map +1 -0
- package/dist/src/utils/dateRange.js +48 -0
- package/dist/src/utils/dateRange.js.map +1 -0
- package/dist/src/utils/mcpSecurity.d.ts +5 -0
- package/dist/src/utils/mcpSecurity.d.ts.map +1 -0
- package/dist/src/utils/mcpSecurity.js +39 -0
- package/dist/src/utils/mcpSecurity.js.map +1 -0
- package/dist/src/utils/packageRoot.d.ts +2 -0
- package/dist/src/utils/packageRoot.d.ts.map +1 -0
- package/dist/src/utils/packageRoot.js +16 -0
- package/dist/src/utils/packageRoot.js.map +1 -0
- package/dist/src/utils/python.d.ts +2 -0
- package/dist/src/utils/python.d.ts.map +1 -0
- package/dist/src/utils/python.js +72 -0
- package/dist/src/utils/python.js.map +1 -0
- package/dist/src/utils/pythonProcessEnv.d.ts +3 -0
- package/dist/src/utils/pythonProcessEnv.d.ts.map +1 -0
- package/dist/src/utils/pythonProcessEnv.js +46 -0
- package/dist/src/utils/pythonProcessEnv.js.map +1 -0
- package/dist/src/utils/talkerUtils.d.ts +4 -1
- package/dist/src/utils/talkerUtils.d.ts.map +1 -1
- package/dist/src/utils/talkerUtils.js +9 -9
- package/dist/src/utils/talkerUtils.js.map +1 -1
- package/dist/src/utils/wechatEmoji.d.ts +5 -0
- package/dist/src/utils/wechatEmoji.d.ts.map +1 -0
- package/dist/src/utils/wechatEmoji.js +35 -0
- package/dist/src/utils/wechatEmoji.js.map +1 -0
- package/docs/AI_INTERFACE.md +189 -0
- package/docs/BRANCHES.md +87 -0
- package/docs/DATA_CONTRACT.md +74 -0
- package/docs/DECISIONS.md +270 -0
- package/docs/DEEPSEEK_V4_FLASH/346/226/275/345/267/245/346/226/207/346/241/243.md +234 -0
- package/docs/EVIDENCE_GUIDE.md +96 -0
- package/docs/MCP.md +95 -0
- package/docs/PARTNERS.md +41 -0
- package/docs/PROJECT_STATE.md +96 -0
- package/docs/RELEASING.md +124 -0
- package/docs/ROADMAP.md +191 -0
- package/docs/SETUP.md +103 -0
- package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
- package/docs/images/weflow-architecture.png +0 -0
- package/docs/images/weflow-architecture.svg +65 -0
- package/mcp-server/index.ts +169 -269
- package/package.json +81 -72
- package/requirements-3x.txt +4 -0
- package/requirements-voice.txt +15 -0
- package/requirements.txt +9 -0
- package/resources/js/marked.min.js +69 -0
- package/resources/wechat-emoji/666.png +0 -0
- package/resources/wechat-emoji/Awesome.png +0 -0
- package/resources/wechat-emoji/Concerned.png +0 -0
- package/resources/wechat-emoji/Cry.png +0 -0
- package/resources/wechat-emoji/Emm.png +0 -0
- package/resources/wechat-emoji/Facepalm.png +0 -0
- package/resources/wechat-emoji/Grin.png +0 -0
- package/resources/wechat-emoji/OK.png +0 -0
- package/resources/wechat-emoji/Respect.png +0 -0
- package/resources/wechat-emoji/Sick.png +0 -0
- package/resources/wechat-emoji/Sleep.png +0 -0
- package/resources/wechat-emoji/Smile.png +0 -0
- package/resources/wechat-emoji//344/272/262/344/272/262.png +0 -0
- package/resources/wechat-emoji//344/276/277/344/276/277.png +0 -0
- package/resources/wechat-emoji//345/201/267/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/202/262/346/205/242.png +0 -0
- package/resources/wechat-emoji//345/206/215/350/247/201.png +0 -0
- package/resources/wechat-emoji//345/207/213/350/260/242.png +0 -0
- package/resources/wechat-emoji//345/212/240/346/262/271.png +0 -0
- package/resources/wechat-emoji//345/213/276/345/274/225.png +0 -0
- package/resources/wechat-emoji//345/217/221/345/221/206.png +0 -0
- package/resources/wechat-emoji//345/217/221/346/200/222.png +0 -0
- package/resources/wechat-emoji//345/217/221/346/212/226.png +0 -0
- package/resources/wechat-emoji//345/217/257/346/200/234.png +0 -0
- package/resources/wechat-emoji//345/217/263/345/223/274/345/223/274.png +0 -0
- package/resources/wechat-emoji//345/217/271/346/260/224.png +0 -0
- package/resources/wechat-emoji//345/220/203/347/223/234.png +0 -0
- package/resources/wechat-emoji//345/220/210/345/215/201.png +0 -0
- package/resources/wechat-emoji//345/220/220.png +0 -0
- package/resources/wechat-emoji//345/221/262/347/211/231.png +0 -0
- package/resources/wechat-emoji//345/222/222/351/252/202.png +0 -0
- package/resources/wechat-emoji//345/222/226/345/225/241.png +0 -0
- package/resources/wechat-emoji//345/223/207.png +0 -0
- package/resources/wechat-emoji//345/225/244/351/205/222.png +0 -0
- package/resources/wechat-emoji//345/230/230.png +0 -0
- package/resources/wechat-emoji//345/230/264/345/224/207.png +0 -0
- package/resources/wechat-emoji//345/230/277/345/223/210.png +0 -0
- package/resources/wechat-emoji//345/233/247.png +0 -0
- package/resources/wechat-emoji//345/233/260.png +0 -0
- package/resources/wechat-emoji//345/235/217/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/244/247/345/223/255.png +0 -0
- package/resources/wechat-emoji//345/244/251/345/225/212.png +0 -0
- package/resources/wechat-emoji//345/244/252/351/230/263.png +0 -0
- package/resources/wechat-emoji//345/244/261/346/234/233.png +0 -0
- package/resources/wechat-emoji//345/245/270/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/245/275/347/232/204.png +0 -0
- package/resources/wechat-emoji//345/247/224/345/261/210.png +0 -0
- package/resources/wechat-emoji//345/256/263/347/276/236.png +0 -0
- package/resources/wechat-emoji//345/260/264/345/260/254.png +0 -0
- package/resources/wechat-emoji//345/272/206/347/245/235.png +0 -0
- package/resources/wechat-emoji//345/274/261.png +0 -0
- package/resources/wechat-emoji//345/274/272.png +0 -0
- package/resources/wechat-emoji//345/276/227/346/204/217.png +0 -0
- package/resources/wechat-emoji//345/276/256/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/277/203/347/242/216.png +0 -0
- package/resources/wechat-emoji//345/277/253/345/223/255/344/272/206.png +0 -0
- package/resources/wechat-emoji//346/201/220/346/203/247.png +0 -0
- package/resources/wechat-emoji//346/202/240/351/227/262.png +0 -0
- package/resources/wechat-emoji//346/203/212/346/201/220.png +0 -0
- package/resources/wechat-emoji//346/203/212/350/256/266.png +0 -0
- package/resources/wechat-emoji//346/204/211/345/277/253.png +0 -0
- package/resources/wechat-emoji//346/206/250/347/254/221.png +0 -0
- package/resources/wechat-emoji//346/211/223/350/204/270.png +0 -0
- package/resources/wechat-emoji//346/212/223/347/213/202.png +0 -0
- package/resources/wechat-emoji//346/212/240/351/274/273.png +0 -0
- package/resources/wechat-emoji//346/212/261/346/213/263.png +0 -0
- package/resources/wechat-emoji//346/213/245/346/212/261.png +0 -0
- package/resources/wechat-emoji//346/213/263/345/244/264.png +0 -0
- package/resources/wechat-emoji//346/215/202/350/204/270.png +0 -0
- package/resources/wechat-emoji//346/217/241/346/211/213.png +0 -0
- package/resources/wechat-emoji//346/222/207/345/230/264.png +0 -0
- package/resources/wechat-emoji//346/223/246/346/261/227.png +0 -0
- package/resources/wechat-emoji//346/225/262/346/211/223.png +0 -0
- package/resources/wechat-emoji//346/227/240/350/257/255.png +0 -0
- package/resources/wechat-emoji//346/227/272/346/237/264.png +0 -0
- package/resources/wechat-emoji//346/231/225.png +0 -0
- package/resources/wechat-emoji//346/234/210/344/272/256.png +0 -0
- package/resources/wechat-emoji//346/234/272/346/231/272.png +0 -0
- package/resources/wechat-emoji//346/261/227.png +0 -0
- package/resources/wechat-emoji//346/265/201/346/263/252.png +0 -0
- package/resources/wechat-emoji//347/202/270/345/274/271.png +0 -0
- package/resources/wechat-emoji//347/203/237/350/212/261.png +0 -0
- package/resources/wechat-emoji//347/210/206/347/253/271.png +0 -0
- package/resources/wechat-emoji//347/210/261/345/277/203.png +0 -0
- package/resources/wechat-emoji//347/214/252/345/244/264.png +0 -0
- package/resources/wechat-emoji//347/216/253/347/221/260.png +0 -0
- package/resources/wechat-emoji//347/224/237/347/227/205.png +0 -0
- package/resources/wechat-emoji//347/226/221/351/227/256.png +0 -0
- package/resources/wechat-emoji//347/231/274.png +0 -0
- package/resources/wechat-emoji//347/231/275/347/234/274.png +0 -0
- package/resources/wechat-emoji//347/232/261/347/234/211.png +0 -0
- package/resources/wechat-emoji//347/235/241.png +0 -0
- package/resources/wechat-emoji//347/240/264/346/266/225/344/270/272/347/254/221.png +0 -0
- package/resources/wechat-emoji//347/244/274/347/211/251.png +0 -0
- package/resources/wechat-emoji//347/244/276/344/274/232/347/244/276/344/274/232.png +0 -0
- package/resources/wechat-emoji//347/246/217.png +0 -0
- package/resources/wechat-emoji//347/254/221/350/204/270.png +0 -0
- package/resources/wechat-emoji//347/272/242/345/214/205.png +0 -0
- package/resources/wechat-emoji//347/277/273/347/231/275/347/234/274.png +0 -0
- package/resources/wechat-emoji//350/200/266.png +0 -0
- package/resources/wechat-emoji//350/203/234/345/210/251.png +0 -0
- package/resources/wechat-emoji//350/204/270/347/272/242.png +0 -0
- package/resources/wechat-emoji//350/211/262.png +0 -0
- package/resources/wechat-emoji//350/213/246/346/266/251.png +0 -0
- package/resources/wechat-emoji//350/217/234/345/210/200.png +0 -0
- package/resources/wechat-emoji//350/233/213/347/263/225.png +0 -0
- package/resources/wechat-emoji//350/241/260.png +0 -0
- package/resources/wechat-emoji//350/243/202/345/274/200.png +0 -0
- package/resources/wechat-emoji//350/256/251/346/210/221/347/234/213/347/234/213.png +0 -0
- package/resources/wechat-emoji//350/260/203/347/232/256.png +0 -0
- package/resources/wechat-emoji//350/267/263/350/267/263.png +0 -0
- package/resources/wechat-emoji//350/275/254/345/234/210.png +0 -0
- package/resources/wechat-emoji//351/204/231/350/247/206.png +0 -0
- package/resources/wechat-emoji//351/227/255/345/230/264.png +0 -0
- package/resources/wechat-emoji//351/230/264/351/231/251.png +0 -0
- package/resources/wechat-emoji//351/232/276/350/277/207.png +0 -0
- package/resources/wechat-emoji//351/252/267/351/253/205.png +0 -0
- package/resources/wechat-emoji//351/274/223/346/216/214.png +0 -0
- package/scripts/_batch_link_sources.py +73 -73
- package/scripts/_utils.py +29 -0
- package/scripts/annual_report.py +796 -796
- package/scripts/auto_tag.py +128 -128
- package/scripts/biz_daily.py +210 -53
- package/scripts/chat_report.py +10 -0
- package/scripts/chat_stats.py +702 -702
- package/scripts/classify_daily.py +17 -11
- package/scripts/create_reading_notes.py +370 -370
- package/scripts/daily_stats.py +132 -0
- package/scripts/enrich_backlinks.py +163 -163
- package/scripts/export_chat_html.py +1704 -107
- package/scripts/extract_todos.py +445 -404
- package/scripts/fav_server.py +119 -12
- package/scripts/fix_topics.py +111 -111
- package/scripts/generate_ai_report.py +1 -1
- package/scripts/generate_html.py +597 -277
- package/scripts/inspection_records.py +354 -0
- package/scripts/mcp_bridge.py +398 -398
- package/scripts/nt_decrypt.py +702 -48
- package/scripts/pipeline.py +27 -19
- package/scripts/promote_all.py +312 -310
- package/scripts/promote_ideas.py +299 -297
- package/scripts/rag_chat.py +194 -191
- package/scripts/semantic_search.py +435 -431
- package/scripts/sync_fav.py +201 -146
- package/scripts/sync_weread.py +192 -192
- package/scripts/vault_rag.py +140 -139
- package/scripts/vault_search.py +141 -141
- package/scripts/wechat_emoji.py +132 -0
- package/scripts/wechat_emoticon.py +433 -0
- package/scripts/wechat_image.py +258 -0
- package/scripts/wechat_voice.py +327 -0
- package/src/core/dbPathService.ts +163 -18
- package/src/core/keyService.ts +137 -25
- package/src/core/ntCore.ts +215 -62
- package/src/core/sqlcipherCore.ts +17 -1
- package/src/core/wcdbCore.ts +3 -7
- package/src/services/assistantDaemon.ts +111 -0
- package/src/services/assistantMemory.ts +193 -0
- package/src/services/assistantPrivacy.ts +87 -0
- package/src/services/assistantRouting.ts +92 -0
- package/src/services/assistantService.ts +295 -0
- package/src/services/assistantTools.ts +549 -0
- package/src/services/chatService.ts +133 -4
- package/src/services/configService.ts +194 -6
- package/src/services/evidenceService.ts +129 -0
- package/src/services/exportService.ts +113 -48
- package/src/services/initKeyService.ts +83 -0
- package/src/services/messageContract.ts +77 -0
- package/src/services/messageQuery.ts +38 -0
- package/src/services/wechat-formatter.ts +348 -348
- package/src/services/wechatMessageService.ts +25 -3
- package/src/services/wereadService.ts +308 -308
- package/src/services/whitelistService.ts +209 -22
- package/src/types.ts +20 -0
- package/src/utils/dateRange.ts +58 -0
- package/src/utils/mcpSecurity.ts +39 -0
- package/src/utils/packageRoot.ts +14 -0
- package/src/utils/python.ts +73 -0
- package/src/utils/pythonProcessEnv.ts +45 -0
- package/src/utils/talkerUtils.ts +14 -2
- package/dist/src/utils/errors.d.ts +0 -25
- package/dist/src/utils/errors.d.ts.map +0 -1
- package/dist/src/utils/errors.js +0 -43
- package/dist/src/utils/errors.js.map +0 -1
- package/dist/src/utils/pythonRunner.d.ts +0 -27
- package/dist/src/utils/pythonRunner.d.ts.map +0 -1
- package/dist/src/utils/pythonRunner.js +0 -62
- package/dist/src/utils/pythonRunner.js.map +0 -1
- package/src/utils/errors.ts +0 -42
- package/src/utils/pythonRunner.ts +0 -81
|
@@ -1,34 +1,82 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 白名单/黑名单服务 — 控制消息收发权限。
|
|
3
|
+
* - 白名单: 允许收发的 wxid 列表
|
|
4
|
+
* - 黑名单: 绝对禁止收发的 wxid 列表 (优先级高于白名单)
|
|
5
|
+
* - 审计日志: 记录每次发送尝试, 便于事后追溯
|
|
3
6
|
*/
|
|
4
|
-
import {
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { homedir } from 'os'
|
|
9
|
+
import { appendFileSync, mkdirSync, existsSync, readFileSync, statSync } from 'fs'
|
|
10
|
+
import { configService, type ListEntry } from './configService.js'
|
|
5
11
|
import { resolveTalker } from '../utils/talkerUtils.js'
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
const AUDIT_DIR = join(homedir(), '.weflow-cli')
|
|
14
|
+
const AUDIT_FILE = join(AUDIT_DIR, 'audit-send.log')
|
|
15
|
+
|
|
16
|
+
/** 单条文本消息最大字符数 (微信单条上限约 5000, 这里更保守) */
|
|
17
|
+
export const MAX_TEXT_LENGTH = 2000
|
|
18
|
+
/** 默认速率限制: 窗口内最大发送条数 */
|
|
19
|
+
export const DEFAULT_RATE_WINDOW_MS = 60_000
|
|
20
|
+
export const DEFAULT_RATE_MAX = 10
|
|
21
|
+
|
|
22
|
+
export interface SendAuditEntry {
|
|
23
|
+
timestamp: number
|
|
24
|
+
action: 'send'
|
|
25
|
+
targetWxid: string
|
|
26
|
+
targetName?: string
|
|
27
|
+
kind: 'text' | 'image' | 'file'
|
|
28
|
+
success: boolean
|
|
29
|
+
/** 消息预览 (前 80 字符) */
|
|
30
|
+
preview?: string
|
|
31
|
+
/** 失败原因 */
|
|
32
|
+
error?: string
|
|
10
33
|
}
|
|
11
34
|
|
|
12
35
|
export class WhitelistService {
|
|
36
|
+
// ====== 白名单 ======
|
|
37
|
+
|
|
13
38
|
getList(): string[] {
|
|
14
39
|
return configService.getWhitelist()
|
|
15
40
|
}
|
|
16
41
|
|
|
42
|
+
/** 带昵称的白名单条目 */
|
|
43
|
+
getWhitelistEntries(): ListEntry[] {
|
|
44
|
+
return configService.getWhitelistEntries()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 查 wxid 对应的 displayName, 没有则返回 wxid */
|
|
48
|
+
lookupName(wxid: string): string {
|
|
49
|
+
const e = configService.getWhitelistEntries().find(x => x.wxid === wxid)
|
|
50
|
+
if (e?.displayName) return e.displayName
|
|
51
|
+
const b = configService.getBlacklistEntries().find(x => x.wxid === wxid)
|
|
52
|
+
return b?.displayName || wxid
|
|
53
|
+
}
|
|
54
|
+
|
|
17
55
|
isAllowed(wxid: string): boolean {
|
|
56
|
+
// 黑名单优先: 即使在白名单也拒绝
|
|
57
|
+
if (this.isBlocked(wxid)) return false
|
|
18
58
|
return configService.getWhitelist().includes(wxid)
|
|
19
59
|
}
|
|
20
60
|
|
|
21
61
|
/** 直接添加 wxid(不解析昵称),用于 CLI 确认后的最终写入 */
|
|
22
|
-
addDirect(wxid: string): void {
|
|
23
|
-
const
|
|
24
|
-
if (!
|
|
25
|
-
|
|
26
|
-
configService.
|
|
62
|
+
addDirect(wxid: string, displayName?: string): void {
|
|
63
|
+
const entries = configService.getWhitelistEntries()
|
|
64
|
+
if (!entries.some(e => e.wxid === wxid)) {
|
|
65
|
+
entries.push({ wxid, displayName, addedAt: Date.now() })
|
|
66
|
+
configService.setWhitelistEntries(entries)
|
|
67
|
+
} else if (displayName) {
|
|
68
|
+
// 已存在但补全 displayName
|
|
69
|
+
const idx = entries.findIndex(e => e.wxid === wxid)
|
|
70
|
+
if (!entries[idx].displayName) {
|
|
71
|
+
entries[idx].displayName = displayName
|
|
72
|
+
configService.setWhitelistEntries(entries)
|
|
73
|
+
}
|
|
27
74
|
}
|
|
28
75
|
}
|
|
29
76
|
|
|
30
|
-
async add(target: string): Promise<{ success: boolean; error?: string; wxid?: string }> {
|
|
77
|
+
async add(target: string): Promise<{ success: boolean; error?: string; wxid?: string; displayName?: string }> {
|
|
31
78
|
let wxid: string
|
|
79
|
+
let displayName: string | undefined
|
|
32
80
|
|
|
33
81
|
// 已知格式直接使用,否则通过 resolveTalker 解析
|
|
34
82
|
if (target.startsWith('wxid_') || target.includes('@chatroom') || target.includes('@openim')) {
|
|
@@ -41,31 +89,170 @@ export class WhitelistService {
|
|
|
41
89
|
}
|
|
42
90
|
}
|
|
43
91
|
|
|
44
|
-
|
|
45
|
-
if (
|
|
92
|
+
// 黑名单中的目标禁止加入白名单
|
|
93
|
+
if (this.isBlocked(wxid)) {
|
|
94
|
+
return { success: false, error: `"${wxid}" 在黑名单中, 禁止加入白名单` }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const entries = configService.getWhitelistEntries()
|
|
98
|
+
if (entries.some(e => e.wxid === wxid)) {
|
|
46
99
|
return { success: false, error: `"${wxid}" 已在白名单中` }
|
|
47
100
|
}
|
|
48
101
|
|
|
49
|
-
|
|
50
|
-
configService.
|
|
51
|
-
return { success: true, wxid }
|
|
102
|
+
entries.push({ wxid, displayName, addedAt: Date.now() })
|
|
103
|
+
configService.setWhitelistEntries(entries)
|
|
104
|
+
return { success: true, wxid, displayName }
|
|
52
105
|
}
|
|
53
106
|
|
|
54
107
|
remove(wxid: string): boolean {
|
|
55
|
-
const
|
|
56
|
-
const idx =
|
|
108
|
+
const entries = configService.getWhitelistEntries()
|
|
109
|
+
const idx = entries.findIndex(e => e.wxid === wxid)
|
|
57
110
|
if (idx < 0) return false
|
|
58
|
-
|
|
59
|
-
configService.
|
|
111
|
+
entries.splice(idx, 1)
|
|
112
|
+
configService.setWhitelistEntries(entries)
|
|
60
113
|
return true
|
|
61
114
|
}
|
|
62
115
|
|
|
63
116
|
clear(): void {
|
|
64
|
-
configService.
|
|
117
|
+
configService.setWhitelistEntries([])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ====== 黑名单 ======
|
|
121
|
+
|
|
122
|
+
getBlacklist(): string[] {
|
|
123
|
+
return configService.getBlacklist()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getBlacklistEntries(): ListEntry[] {
|
|
127
|
+
return configService.getBlacklistEntries()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
isBlocked(wxid: string): boolean {
|
|
131
|
+
return configService.getBlacklist().includes(wxid)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 直接添加 wxid 到黑名单 */
|
|
135
|
+
blockDirect(wxid: string, displayName?: string, reason?: string): void {
|
|
136
|
+
const entries = configService.getBlacklistEntries()
|
|
137
|
+
if (!entries.some(e => e.wxid === wxid)) {
|
|
138
|
+
entries.push({ wxid, displayName, addedAt: Date.now(), reason })
|
|
139
|
+
configService.setBlacklistEntries(entries)
|
|
140
|
+
}
|
|
141
|
+
// 同步从白名单移除 (黑名单优先级最高)
|
|
142
|
+
if (configService.getWhitelist().includes(wxid)) {
|
|
143
|
+
this.remove(wxid)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async block(target: string, reason?: string): Promise<{ success: boolean; error?: string; wxid?: string; displayName?: string }> {
|
|
148
|
+
let wxid: string
|
|
149
|
+
let displayName: string | undefined
|
|
150
|
+
if (target.startsWith('wxid_') || target.includes('@chatroom') || target.includes('@openim')) {
|
|
151
|
+
wxid = target
|
|
152
|
+
} else {
|
|
153
|
+
try {
|
|
154
|
+
wxid = await resolveTalker(target)
|
|
155
|
+
} catch (e: any) {
|
|
156
|
+
return { success: false, error: `无法解析 "${target}": ${e.message}` }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const entries = configService.getBlacklistEntries()
|
|
161
|
+
if (entries.some(e => e.wxid === wxid)) {
|
|
162
|
+
return { success: false, error: `"${wxid}" 已在黑名单中` }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
entries.push({ wxid, displayName, addedAt: Date.now(), reason })
|
|
166
|
+
configService.setBlacklistEntries(entries)
|
|
167
|
+
// 同步从白名单移除
|
|
168
|
+
if (configService.getWhitelist().includes(wxid)) {
|
|
169
|
+
this.remove(wxid)
|
|
170
|
+
}
|
|
171
|
+
return { success: true, wxid, displayName }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
unblock(wxid: string): boolean {
|
|
175
|
+
const entries = configService.getBlacklistEntries()
|
|
176
|
+
const idx = entries.findIndex(e => e.wxid === wxid)
|
|
177
|
+
if (idx < 0) return false
|
|
178
|
+
entries.splice(idx, 1)
|
|
179
|
+
configService.setBlacklistEntries(entries)
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
clearBlacklist(): void {
|
|
184
|
+
configService.setBlacklistEntries([])
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ====== 审计日志 ======
|
|
188
|
+
|
|
189
|
+
/** 追加一条发送审计记录 (JSON Lines) */
|
|
190
|
+
auditSend(entry: SendAuditEntry): void {
|
|
191
|
+
try {
|
|
192
|
+
if (!existsSync(AUDIT_DIR)) {
|
|
193
|
+
mkdirSync(AUDIT_DIR, { recursive: true })
|
|
194
|
+
}
|
|
195
|
+
appendFileSync(AUDIT_FILE, JSON.stringify(entry) + '\n', 'utf8')
|
|
196
|
+
} catch {
|
|
197
|
+
// 审计失败不影响主流程
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 读取审计日志, 返回最近 limit 条 (默认全部) */
|
|
202
|
+
readAudit(limit?: number, filter?: (e: SendAuditEntry) => boolean): SendAuditEntry[] {
|
|
203
|
+
if (!existsSync(AUDIT_FILE)) return []
|
|
204
|
+
try {
|
|
205
|
+
const raw = readFileSync(AUDIT_FILE, 'utf8')
|
|
206
|
+
const lines = raw.split('\n').filter(Boolean)
|
|
207
|
+
let entries: SendAuditEntry[] = []
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
try {
|
|
210
|
+
entries.push(JSON.parse(line))
|
|
211
|
+
} catch {
|
|
212
|
+
// 跳过损坏行
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (filter) entries = entries.filter(filter)
|
|
216
|
+
// 倒序 (最新在前)
|
|
217
|
+
entries.reverse()
|
|
218
|
+
if (limit && limit > 0) entries = entries.slice(0, limit)
|
|
219
|
+
return entries
|
|
220
|
+
} catch {
|
|
221
|
+
return []
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** 审计日志文件大小 (字节) */
|
|
226
|
+
auditSize(): number {
|
|
227
|
+
try {
|
|
228
|
+
return existsSync(AUDIT_FILE) ? statSync(AUDIT_FILE).size : 0
|
|
229
|
+
} catch {
|
|
230
|
+
return 0
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 速率限制检查: 指定窗口内成功发送次数是否超阈值。
|
|
236
|
+
* @returns { allowed, count, windowMs, max }
|
|
237
|
+
*/
|
|
238
|
+
checkRateLimit(windowMs: number = DEFAULT_RATE_WINDOW_MS, max: number = DEFAULT_RATE_MAX): {
|
|
239
|
+
allowed: boolean
|
|
240
|
+
count: number
|
|
241
|
+
windowMs: number
|
|
242
|
+
max: number
|
|
243
|
+
} {
|
|
244
|
+
const since = Date.now() - windowMs
|
|
245
|
+
const recent = this.readAudit(undefined, e => e.success && e.timestamp >= since)
|
|
246
|
+
return {
|
|
247
|
+
allowed: recent.length < max,
|
|
248
|
+
count: recent.length,
|
|
249
|
+
windowMs,
|
|
250
|
+
max,
|
|
251
|
+
}
|
|
65
252
|
}
|
|
66
253
|
|
|
67
254
|
reload(): void {
|
|
68
|
-
// configService
|
|
255
|
+
// configService 每次读取都从内存读取,无需主动 reload
|
|
69
256
|
}
|
|
70
257
|
}
|
|
71
258
|
|
package/src/types.ts
CHANGED
|
@@ -83,6 +83,12 @@ export interface WechatLoginSession {
|
|
|
83
83
|
export interface WechatInboundMessage {
|
|
84
84
|
messageId: string
|
|
85
85
|
fromUserId: string
|
|
86
|
+
/** Reply destination. A group ID is used only when the upstream explicitly provides one. */
|
|
87
|
+
conversationId: string
|
|
88
|
+
conversationType: 'direct' | 'group'
|
|
89
|
+
/** Person who sent the message; group messages without this identifier are denied. */
|
|
90
|
+
senderId: string
|
|
91
|
+
mentionedBot: boolean
|
|
86
92
|
senderNickname: string
|
|
87
93
|
timestamp: number
|
|
88
94
|
timestampMs: number
|
|
@@ -138,4 +144,18 @@ export interface ConfigData {
|
|
|
138
144
|
contactKey?: string
|
|
139
145
|
/** NT 格式: contact.db 32位十六进制盐值 */
|
|
140
146
|
contactSalt?: string
|
|
147
|
+
/** NT 格式: sns.db 路径 */
|
|
148
|
+
snsDbPath?: string
|
|
149
|
+
/** NT 格式: sns.db 64位十六进制密钥 */
|
|
150
|
+
snsKey?: string
|
|
151
|
+
/** NT 格式: sns.db 32位十六进制盐值 */
|
|
152
|
+
snsSalt?: string
|
|
153
|
+
/** NT 格式: favorite.db 路径 */
|
|
154
|
+
favDbPath?: string
|
|
155
|
+
/** NT 格式: favorite.db 64位十六进制密钥 (PBKDF2 派生 raw key) */
|
|
156
|
+
favKey?: string
|
|
157
|
+
/** NT 格式: 4.x 全库共用 passphrase 密钥 (64位hex, 用于派生各库 raw key) */
|
|
158
|
+
favPassphrase?: string
|
|
159
|
+
/** 账号级 seed,用于解密本地缓存里的自定义表情包 */
|
|
160
|
+
emoticonSeed?: string
|
|
141
161
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export class DateRangeError extends Error {
|
|
2
|
+
constructor(public readonly code: string, message: string) {
|
|
3
|
+
super(message)
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function parseLocalDateOrIso(value: string | undefined, endOfDay = false): number | undefined {
|
|
8
|
+
if (!value) return undefined
|
|
9
|
+
let timestamp: number
|
|
10
|
+
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/)
|
|
11
|
+
if (match) {
|
|
12
|
+
const [, year, month, day] = match
|
|
13
|
+
const date = new Date(
|
|
14
|
+
Number(year),
|
|
15
|
+
Number(month) - 1,
|
|
16
|
+
Number(day),
|
|
17
|
+
endOfDay ? 23 : 0,
|
|
18
|
+
endOfDay ? 59 : 0,
|
|
19
|
+
endOfDay ? 59 : 0,
|
|
20
|
+
endOfDay ? 999 : 0,
|
|
21
|
+
)
|
|
22
|
+
timestamp = date.getFullYear() === Number(year) &&
|
|
23
|
+
date.getMonth() === Number(month) - 1 &&
|
|
24
|
+
date.getDate() === Number(day)
|
|
25
|
+
? date.getTime()
|
|
26
|
+
: Number.NaN
|
|
27
|
+
} else {
|
|
28
|
+
timestamp = Date.parse(value)
|
|
29
|
+
}
|
|
30
|
+
if (Number.isNaN(timestamp)) throw new DateRangeError('INVALID_DATE', '导出日期无效')
|
|
31
|
+
return Math.floor(timestamp / 1000)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveExportDateRange(options: {
|
|
35
|
+
date?: string
|
|
36
|
+
from?: string
|
|
37
|
+
to?: string
|
|
38
|
+
preserveDateForRichHtml?: boolean
|
|
39
|
+
}): { from?: number; to?: number } {
|
|
40
|
+
if (options.date && (options.from || options.to)) {
|
|
41
|
+
throw new DateRangeError('CONFLICTING_DATE_FILTERS', '--date 不能与 --from/--to 同时使用')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let from = parseLocalDateOrIso(options.from)
|
|
45
|
+
let to = parseLocalDateOrIso(options.to, true)
|
|
46
|
+
if (options.date) {
|
|
47
|
+
const dateStart = parseLocalDateOrIso(options.date)
|
|
48
|
+
const dateEnd = parseLocalDateOrIso(options.date, true)
|
|
49
|
+
if (!options.preserveDateForRichHtml) {
|
|
50
|
+
from = dateStart
|
|
51
|
+
to = dateEnd
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (from !== undefined && to !== undefined && from > to) {
|
|
55
|
+
throw new DateRangeError('INVALID_DATE_RANGE', '起始日期不能晚于结束日期')
|
|
56
|
+
}
|
|
57
|
+
return { from, to }
|
|
58
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'fs'
|
|
2
|
+
import { isAbsolute, relative, resolve } from 'path'
|
|
3
|
+
|
|
4
|
+
export function safeChildPath(root: string, child: string): string | null {
|
|
5
|
+
const candidate = resolve(root, child)
|
|
6
|
+
const rel = relative(resolve(root), candidate)
|
|
7
|
+
return rel && !rel.startsWith('..') && !isAbsolute(rel) ? candidate : null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function safeDate(value: unknown): string | null {
|
|
11
|
+
const date = String(value || '')
|
|
12
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isAllowedWeChatArticleUrl(value: unknown): boolean {
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(String(value || ''))
|
|
18
|
+
return url.protocol === 'https:' &&
|
|
19
|
+
url.hostname === 'mp.weixin.qq.com' &&
|
|
20
|
+
!url.username &&
|
|
21
|
+
!url.password
|
|
22
|
+
} catch {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isCoverImage(path: string): boolean {
|
|
28
|
+
try {
|
|
29
|
+
const stat = statSync(path)
|
|
30
|
+
if (!stat.isFile() || stat.size > 10 * 1024 * 1024) return false
|
|
31
|
+
const head = readFileSync(path).subarray(0, 12)
|
|
32
|
+
return (head.length >= 8 && head.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) ||
|
|
33
|
+
(head.length >= 3 && head.subarray(0, 3).equals(Buffer.from([255, 216, 255]))) ||
|
|
34
|
+
(head.length >= 6 && (head.subarray(0, 6).toString() === 'GIF87a' || head.subarray(0, 6).toString() === 'GIF89a')) ||
|
|
35
|
+
(head.length >= 12 && head.subarray(0, 4).toString() === 'RIFF' && head.subarray(8, 12).toString() === 'WEBP')
|
|
36
|
+
} catch {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
export function resolvePackageRoot(moduleUrl: string): string {
|
|
6
|
+
let current = dirname(fileURLToPath(moduleUrl))
|
|
7
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
8
|
+
if (existsSync(join(current, 'package.json')) && existsSync(join(current, 'scripts'))) return current
|
|
9
|
+
const parent = dirname(current)
|
|
10
|
+
if (parent === current) break
|
|
11
|
+
current = parent
|
|
12
|
+
}
|
|
13
|
+
throw new Error('Unable to locate the WeFlow CLI package root')
|
|
14
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process'
|
|
2
|
+
import { delimiter, join } from 'path'
|
|
3
|
+
import { accessSync, constants } from 'fs'
|
|
4
|
+
|
|
5
|
+
let cached: string | null = null
|
|
6
|
+
|
|
7
|
+
const EXE_NAMES = process.platform === 'win32'
|
|
8
|
+
? ['python.exe', 'python3.exe', 'py.exe']
|
|
9
|
+
: ['python3', 'python']
|
|
10
|
+
|
|
11
|
+
function isExecutable(path: string): boolean {
|
|
12
|
+
try {
|
|
13
|
+
accessSync(path, constants.X_OK)
|
|
14
|
+
return true
|
|
15
|
+
} catch {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 按顺序返回 PATH 中所有可用的 Python 解释器(含命令名候选) */
|
|
21
|
+
function candidates(): string[] {
|
|
22
|
+
const found: string[] = []
|
|
23
|
+
const dirs = (process.env.PATH || '').split(delimiter).filter(Boolean)
|
|
24
|
+
for (const dir of dirs) {
|
|
25
|
+
for (const name of EXE_NAMES) {
|
|
26
|
+
const full = join(dir, name)
|
|
27
|
+
if (isExecutable(full) && !found.includes(full)) {
|
|
28
|
+
found.push(full)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// 兜底:直接用命令名(依赖 PATH 解析,可能命中与上面相同的解释器)
|
|
33
|
+
for (const name of process.platform === 'win32' ? ['python', 'python3', 'py'] : ['python3', 'python']) {
|
|
34
|
+
if (!found.includes(name)) found.push(name)
|
|
35
|
+
}
|
|
36
|
+
return found
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function canRun(cmd: string): boolean {
|
|
40
|
+
try {
|
|
41
|
+
execFileSync(cmd, ['--version'], { stdio: 'ignore', timeout: 5000 })
|
|
42
|
+
return true
|
|
43
|
+
} catch {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasSqlcipher(cmd: string): boolean {
|
|
49
|
+
try {
|
|
50
|
+
execFileSync(cmd, ['-c', 'import sqlcipher3'], { stdio: 'ignore', timeout: 10000 })
|
|
51
|
+
return true
|
|
52
|
+
} catch {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function detect(): string {
|
|
58
|
+
// 优先选择能 import sqlcipher3 的解释器(NT 解密核心依赖),
|
|
59
|
+
// 避免命中 PATH 中仅有基础库的裸 Python 环境(如被 IDE 注入的 VM Python 抢占)。
|
|
60
|
+
const list = candidates()
|
|
61
|
+
for (const cmd of list) {
|
|
62
|
+
if (canRun(cmd) && hasSqlcipher(cmd)) return cmd
|
|
63
|
+
}
|
|
64
|
+
for (const cmd of list) {
|
|
65
|
+
if (canRun(cmd)) return cmd
|
|
66
|
+
}
|
|
67
|
+
return 'python'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getPythonCommand(): string {
|
|
71
|
+
cached ??= detect()
|
|
72
|
+
return cached
|
|
73
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const INTERNAL_SECRET_KEYS = [
|
|
2
|
+
'WEFLOW_DB_PATH',
|
|
3
|
+
'WEFLOW_NT_KEY',
|
|
4
|
+
'WEFLOW_NT_SALT',
|
|
5
|
+
'WEFLOW_CONTACT_DB',
|
|
6
|
+
'WEFLOW_CONTACT_KEY',
|
|
7
|
+
'WEFLOW_CONTACT_SALT',
|
|
8
|
+
'WEFLOW_TALKER',
|
|
9
|
+
'WEFLOW_OWN_WXID',
|
|
10
|
+
'WEFLOW_NT_PASSPHRASE',
|
|
11
|
+
'WEFLOW_VAULT_QUESTION',
|
|
12
|
+
'WEFLOW_SEARCH_QUERY',
|
|
13
|
+
'WEFLOW_RAG_QUESTION',
|
|
14
|
+
'WEFLOW_RAG_TALKER',
|
|
15
|
+
'WEFLOW_REPORT_TALKERS',
|
|
16
|
+
'WEFLOW_SCAN_ROOT',
|
|
17
|
+
'WEFLOW_QUERY_KEYWORD',
|
|
18
|
+
'WEFLOW_QUERY_USERNAMES',
|
|
19
|
+
'WEFLOW_EXPORT_NAME',
|
|
20
|
+
'WEFLOW_EXPORT_OUTPUT',
|
|
21
|
+
'WEFLOW_EXPORT_CACHE_DIR',
|
|
22
|
+
'WEFLOW_EXPORT_ACCOUNT_DIR',
|
|
23
|
+
'WEFLOW_EXPORT_DATE',
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
export function createPythonProcessEnv(
|
|
27
|
+
overrides: Record<string, string | undefined> = {},
|
|
28
|
+
): NodeJS.ProcessEnv {
|
|
29
|
+
const env: NodeJS.ProcessEnv = { ...process.env, PYTHONIOENCODING: 'utf-8' }
|
|
30
|
+
for (const key of INTERNAL_SECRET_KEYS) delete env[key]
|
|
31
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
32
|
+
if (value) env[key] = value
|
|
33
|
+
}
|
|
34
|
+
return env
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function safeSubprocessError(error: unknown, label = '子进程执行失败'): string {
|
|
38
|
+
const value = error as { code?: unknown; killed?: boolean; signal?: unknown }
|
|
39
|
+
if (value?.code === 'ENOENT') return `${label}: 运行时或命令不可用`
|
|
40
|
+
if (value?.code === 'ETIMEDOUT' || value?.killed || value?.signal) return `${label}: 执行超时或被终止`
|
|
41
|
+
if (typeof value?.code === 'number' || /^\d+$/.test(String(value?.code || ''))) {
|
|
42
|
+
return `${label} (exit ${String(value.code)})`
|
|
43
|
+
}
|
|
44
|
+
return label
|
|
45
|
+
}
|
package/src/utils/talkerUtils.ts
CHANGED
|
@@ -13,7 +13,11 @@ import type { ChatSession } from '../types.js'
|
|
|
13
13
|
*
|
|
14
14
|
* @throws {Error} 当输入无法解析到任何会话时
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
16
|
+
export interface ResolveTalkerOptions {
|
|
17
|
+
interactive?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function resolveTalker(input: string, options: ResolveTalkerOptions = {}): Promise<string> {
|
|
17
21
|
// 1. 已知格式直接返回
|
|
18
22
|
if (input.startsWith('wxid_') || input.includes('@chatroom') || input.includes('@openim')) {
|
|
19
23
|
return input
|
|
@@ -48,7 +52,15 @@ export async function resolveTalker(input: string): Promise<string> {
|
|
|
48
52
|
return sessions[0].username
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
if (options.interactive === false) {
|
|
56
|
+
const candidates = sessions.slice(0, 20).map(session => ({
|
|
57
|
+
username: session.username,
|
|
58
|
+
displayName: session.displayName || '',
|
|
59
|
+
}))
|
|
60
|
+
throw new Error(`会话名称不唯一,请使用会话 ID。候选: ${JSON.stringify(candidates)}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 多个匹配 - 交互选择
|
|
52
64
|
const choices = sessions.slice(0, 20).map(s => ({
|
|
53
65
|
name: `${s.displayName} (${s.username})`,
|
|
54
66
|
value: s.username,
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WeflowError - weflow-cli 统一错误类
|
|
3
|
-
*
|
|
4
|
-
* 提供结构化的错误信息,包含:
|
|
5
|
-
* - message: 人类可读的错误描述
|
|
6
|
-
* - code: 错误代码(用于程序化判断)
|
|
7
|
-
* - exitCode: 进程退出码
|
|
8
|
-
*/
|
|
9
|
-
export declare class WeflowError extends Error {
|
|
10
|
-
code: string;
|
|
11
|
-
exitCode: number;
|
|
12
|
-
constructor(message: string, code?: string, exitCode?: number);
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* 统一错误处理函数。
|
|
16
|
-
*
|
|
17
|
-
* 根据错误类型输出对应的错误信息并以合适的退出码退出进程:
|
|
18
|
-
* - WeflowError: 使用其自带的 exitCode
|
|
19
|
-
* - 普通 Error: 视为未预期错误,退出码 1
|
|
20
|
-
* - 其他: 视为未知错误,退出码 1
|
|
21
|
-
*
|
|
22
|
-
* @param e 捕获到的错误对象
|
|
23
|
-
*/
|
|
24
|
-
export declare function handleError(e: unknown): void;
|
|
25
|
-
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qBAAa,WAAY,SAAQ,KAAK;IAG3B,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;gBAFvB,OAAO,EAAE,MAAM,EACR,IAAI,GAAE,MAAkB,EACxB,QAAQ,GAAE,MAAU;CAK9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAW5C"}
|
package/dist/src/utils/errors.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WeflowError - weflow-cli 统一错误类
|
|
3
|
-
*
|
|
4
|
-
* 提供结构化的错误信息,包含:
|
|
5
|
-
* - message: 人类可读的错误描述
|
|
6
|
-
* - code: 错误代码(用于程序化判断)
|
|
7
|
-
* - exitCode: 进程退出码
|
|
8
|
-
*/
|
|
9
|
-
export class WeflowError extends Error {
|
|
10
|
-
code;
|
|
11
|
-
exitCode;
|
|
12
|
-
constructor(message, code = 'UNKNOWN', exitCode = 1) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.code = code;
|
|
15
|
-
this.exitCode = exitCode;
|
|
16
|
-
this.name = 'WeflowError';
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* 统一错误处理函数。
|
|
21
|
-
*
|
|
22
|
-
* 根据错误类型输出对应的错误信息并以合适的退出码退出进程:
|
|
23
|
-
* - WeflowError: 使用其自带的 exitCode
|
|
24
|
-
* - 普通 Error: 视为未预期错误,退出码 1
|
|
25
|
-
* - 其他: 视为未知错误,退出码 1
|
|
26
|
-
*
|
|
27
|
-
* @param e 捕获到的错误对象
|
|
28
|
-
*/
|
|
29
|
-
export function handleError(e) {
|
|
30
|
-
if (e instanceof WeflowError) {
|
|
31
|
-
console.error(`[weflow-cli] ${e.message}`);
|
|
32
|
-
process.exit(e.exitCode);
|
|
33
|
-
}
|
|
34
|
-
else if (e instanceof Error) {
|
|
35
|
-
console.error(`[weflow-cli] 未预期的错误: ${e.message}`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
console.error('[weflow-cli] 未知错误');
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAG3B;IACA;IAHT,YACE,OAAe,EACR,OAAe,SAAS,EACxB,WAAmB,CAAC;QAE3B,KAAK,CAAC,OAAO,CAAC,CAAA;QAHP,SAAI,GAAJ,IAAI,CAAoB;QACxB,aAAQ,GAAR,QAAQ,CAAY;QAG3B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;IAC3B,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,CAAU;IACpC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC1B,CAAC;SAAM,IAAI,CAAC,YAAY,KAAK,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC"}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export interface PythonScriptResult {
|
|
2
|
-
stdout: string;
|
|
3
|
-
stderr: string;
|
|
4
|
-
exitCode: number;
|
|
5
|
-
}
|
|
6
|
-
export interface PythonScriptError extends Error {
|
|
7
|
-
script: string;
|
|
8
|
-
stderr: string;
|
|
9
|
-
exitCode: number;
|
|
10
|
-
}
|
|
11
|
-
/** 获取项目根目录 */
|
|
12
|
-
export declare function getProjectRoot(): string;
|
|
13
|
-
/**
|
|
14
|
-
* 运行 Python 脚本,统一封装路径解析、参数序列化、错误处理。
|
|
15
|
-
*
|
|
16
|
-
* @param scriptName 脚本文件名(如 'biz_daily.py'),相对于 scripts/ 目录
|
|
17
|
-
* @param args 命令行参数数组
|
|
18
|
-
* @param options 可选配置
|
|
19
|
-
* @returns 脚本执行结果
|
|
20
|
-
* @throws PythonScriptError 当脚本执行失败时
|
|
21
|
-
*/
|
|
22
|
-
export declare function runPythonScript(scriptName: string, args?: string[], options?: {
|
|
23
|
-
cwd?: string;
|
|
24
|
-
timeout?: number;
|
|
25
|
-
env?: Record<string, string>;
|
|
26
|
-
}): Promise<PythonScriptResult>;
|
|
27
|
-
//# sourceMappingURL=pythonRunner.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pythonRunner.d.ts","sourceRoot":"","sources":["../../../src/utils/pythonRunner.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,KAAK;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,cAAc;AACd,wBAAgB,cAAc,IAAI,MAAM,CAcvC;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAC7E,OAAO,CAAC,kBAAkB,CAAC,CAwB7B"}
|