weflow-cli 1.5.0 → 1.6.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/ARCHITECTURE.md +105 -0
- package/CHANGELOG.md +110 -0
- package/CONTRIBUTING.md +45 -0
- package/LICENSE +21 -21
- package/OPERATIONS.md +272 -0
- package/README.en.md +281 -0
- package/README.md +333 -149
- package/SECURITY.md +47 -0
- package/bin/weflow-cli.ts +4032 -583
- package/dist/bin/weflow-cli.js +4296 -593
- 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 +188 -64
- 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/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 +693 -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 +202 -61
- 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
package/src/core/ntCore.ts
CHANGED
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { execFile } from 'child_process'
|
|
9
9
|
import { promisify } from 'util'
|
|
10
|
-
import { existsSync } from 'fs'
|
|
11
|
-
import { join
|
|
12
|
-
import
|
|
10
|
+
import { existsSync, openSync, readSync, closeSync } from 'fs'
|
|
11
|
+
import { join } from 'path'
|
|
12
|
+
import crypto from 'crypto'
|
|
13
13
|
import { configService } from '../services/configService.js'
|
|
14
|
+
import { getPythonCommand } from '../utils/python.js'
|
|
15
|
+
import { createPythonProcessEnv, safeSubprocessError } from '../utils/pythonProcessEnv.js'
|
|
16
|
+
import { resolvePackageRoot } from '../utils/packageRoot.js'
|
|
14
17
|
import type { ChatSession, Message, Contact } from '../types.js'
|
|
15
18
|
|
|
16
19
|
const execFileAsync = promisify(execFile)
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
function getPackageRoot(): string {
|
|
21
|
+
return resolvePackageRoot(import.meta.url)
|
|
22
|
+
}
|
|
19
23
|
|
|
20
24
|
export interface NtResult {
|
|
21
25
|
success: boolean
|
|
@@ -42,6 +46,19 @@ export interface NtScanResult {
|
|
|
42
46
|
matched?: Array<{ path: string; name: string; salt: string; size: number; wxid: string; key: string }>
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
interface NtPythonSecrets {
|
|
50
|
+
dbPath?: string
|
|
51
|
+
key?: string
|
|
52
|
+
salt?: string
|
|
53
|
+
contactDbPath?: string | null
|
|
54
|
+
contactKey?: string | null
|
|
55
|
+
contactSalt?: string | null
|
|
56
|
+
talker?: string
|
|
57
|
+
ownWxid?: string
|
|
58
|
+
keyword?: string
|
|
59
|
+
usernames?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
export class NtCore {
|
|
46
63
|
private dbPath: string
|
|
47
64
|
private keyHex: string
|
|
@@ -81,37 +98,58 @@ export class NtCore {
|
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
private get scriptPath(): string {
|
|
84
|
-
|
|
85
|
-
const pkgRoot = join(__dirname, '..', '..', '..')
|
|
86
|
-
return join(pkgRoot, 'scripts', 'nt_decrypt.py')
|
|
101
|
+
return join(getPackageRoot(), 'scripts', 'nt_decrypt.py')
|
|
87
102
|
}
|
|
88
103
|
|
|
89
|
-
private async callPython(args: string[]): Promise<any> {
|
|
104
|
+
private async callPython(args: string[], secrets: NtPythonSecrets = {}): Promise<any> {
|
|
105
|
+
const values: NtPythonSecrets = {
|
|
106
|
+
dbPath: this.dbPath,
|
|
107
|
+
key: this.keyHex,
|
|
108
|
+
salt: this.saltHex,
|
|
109
|
+
contactDbPath: this.contactDbPath,
|
|
110
|
+
contactKey: this.contactKey,
|
|
111
|
+
contactSalt: this.contactSalt,
|
|
112
|
+
...secrets,
|
|
113
|
+
}
|
|
90
114
|
try {
|
|
91
|
-
const { stdout } = await execFileAsync(
|
|
115
|
+
const { stdout } = await execFileAsync(getPythonCommand(), [this.scriptPath, ...args], {
|
|
92
116
|
timeout: 120_000,
|
|
93
117
|
maxBuffer: 50 * 1024 * 1024,
|
|
94
|
-
env: {
|
|
118
|
+
env: createPythonProcessEnv({
|
|
119
|
+
WEFLOW_DB_PATH: values.dbPath,
|
|
120
|
+
WEFLOW_NT_KEY: values.key,
|
|
121
|
+
WEFLOW_NT_SALT: values.salt,
|
|
122
|
+
WEFLOW_CONTACT_DB: values.contactDbPath || undefined,
|
|
123
|
+
WEFLOW_CONTACT_KEY: values.contactKey || undefined,
|
|
124
|
+
WEFLOW_CONTACT_SALT: values.contactSalt || undefined,
|
|
125
|
+
WEFLOW_TALKER: values.talker,
|
|
126
|
+
WEFLOW_OWN_WXID: values.ownWxid,
|
|
127
|
+
WEFLOW_QUERY_KEYWORD: values.keyword,
|
|
128
|
+
WEFLOW_QUERY_USERNAMES: values.usernames,
|
|
129
|
+
}),
|
|
95
130
|
encoding: 'utf-8',
|
|
96
131
|
})
|
|
97
132
|
|
|
98
133
|
const lines = stdout.split('\n').filter((l: string) => l.trim())
|
|
99
134
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
100
135
|
const trimmed = lines[i].trim()
|
|
101
|
-
if (trimmed.startsWith('{')) {
|
|
102
|
-
|
|
136
|
+
if (trimmed.startsWith('{"')) {
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(trimmed)
|
|
139
|
+
} catch {
|
|
140
|
+
// Try next line
|
|
141
|
+
}
|
|
103
142
|
}
|
|
104
143
|
}
|
|
105
144
|
return { error: 'No valid JSON output from Python script' }
|
|
106
145
|
} catch (e: any) {
|
|
107
|
-
|
|
108
|
-
if (msg.includes('ENOENT') || msg.includes('python')) {
|
|
146
|
+
if (e?.code === 'ENOENT') {
|
|
109
147
|
return { error: 'Python not found. Install Python and sqlcipher3: pip install sqlcipher3' }
|
|
110
148
|
}
|
|
111
|
-
if (
|
|
149
|
+
if (e?.code === 'ETIMEDOUT' || e?.killed || e?.signal) {
|
|
112
150
|
return { error: 'NT database query timed out' }
|
|
113
151
|
}
|
|
114
|
-
return { error:
|
|
152
|
+
return { error: safeSubprocessError(e, 'NT database query failed') }
|
|
115
153
|
}
|
|
116
154
|
}
|
|
117
155
|
|
|
@@ -119,22 +157,23 @@ export class NtCore {
|
|
|
119
157
|
* Scan memory for NT keys and match to databases.
|
|
120
158
|
* Requires WeChat to be running.
|
|
121
159
|
*/
|
|
122
|
-
static async scan(): Promise<NtScanResult> {
|
|
160
|
+
static async scan(root?: string): Promise<NtScanResult> {
|
|
123
161
|
try {
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const { stdout } = await execFileAsync(
|
|
162
|
+
const scriptPath = join(getPackageRoot(), 'scripts', 'nt_decrypt.py')
|
|
163
|
+
const scanArgs = ['scan', '--json']
|
|
164
|
+
const { stdout } = await execFileAsync(getPythonCommand(), [scriptPath, ...scanArgs], {
|
|
127
165
|
timeout: 300_000,
|
|
128
166
|
maxBuffer: 10 * 1024 * 1024,
|
|
129
|
-
env: {
|
|
167
|
+
env: createPythonProcessEnv(root ? { WEFLOW_SCAN_ROOT: root } : {}),
|
|
130
168
|
encoding: 'utf-8',
|
|
131
169
|
})
|
|
132
170
|
|
|
133
171
|
const lines = stdout.split('\n').filter((l: string) => l.trim())
|
|
134
172
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
135
173
|
const trimmed = lines[i].trim()
|
|
136
|
-
if (trimmed.startsWith('{')) {
|
|
137
|
-
|
|
174
|
+
if (trimmed.startsWith('{"')) {
|
|
175
|
+
let result: any
|
|
176
|
+
try { result = JSON.parse(trimmed) } catch { continue }
|
|
138
177
|
if (result.error) {
|
|
139
178
|
return { success: false, error: result.error }
|
|
140
179
|
}
|
|
@@ -148,28 +187,64 @@ export class NtCore {
|
|
|
148
187
|
}
|
|
149
188
|
return { success: false, error: 'Scan produced no valid output' }
|
|
150
189
|
} catch (e: any) {
|
|
151
|
-
|
|
152
|
-
if (msg.includes('ENOENT') || msg.includes('python')) {
|
|
190
|
+
if (e?.code === 'ENOENT') {
|
|
153
191
|
return { success: false, error: 'Python not found. Install Python and add to PATH.' }
|
|
154
192
|
}
|
|
155
|
-
return { success: false, error:
|
|
193
|
+
return { success: false, error: safeSubprocessError(e, 'NT scan failed') }
|
|
156
194
|
}
|
|
157
195
|
}
|
|
158
196
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
/**
|
|
198
|
+
* 验证 key+salt 能否真实打开数据库 (读取 sqlite_master 触发解密)。
|
|
199
|
+
* cannotVerify=true 表示环境问题 (Python/sqlcipher3 缺失) 而非密钥错误。
|
|
200
|
+
*/
|
|
201
|
+
static async verifyDbKey(dbPath: string, keyHex: string, saltHex: string): Promise<{
|
|
202
|
+
success: boolean
|
|
203
|
+
error?: string
|
|
204
|
+
tables?: number
|
|
205
|
+
cannotVerify?: boolean
|
|
206
|
+
}> {
|
|
207
|
+
const scriptPath = join(getPackageRoot(), 'scripts', 'nt_decrypt.py')
|
|
208
|
+
let stdout = ''
|
|
209
|
+
try {
|
|
210
|
+
const r = await execFileAsync(getPythonCommand(), [scriptPath, 'verify'], {
|
|
211
|
+
timeout: 60_000,
|
|
212
|
+
maxBuffer: 1024 * 1024,
|
|
213
|
+
env: createPythonProcessEnv({
|
|
214
|
+
WEFLOW_DB_PATH: dbPath,
|
|
215
|
+
WEFLOW_NT_KEY: keyHex,
|
|
216
|
+
WEFLOW_NT_SALT: saltHex,
|
|
217
|
+
}),
|
|
218
|
+
encoding: 'utf-8',
|
|
219
|
+
})
|
|
220
|
+
stdout = r.stdout
|
|
221
|
+
} catch (e: any) {
|
|
222
|
+
// 缺 sqlcipher3 等场景: 脚本打印 JSON 后 exit 1
|
|
223
|
+
stdout = e?.stdout || ''
|
|
224
|
+
if (!stdout.trim()) {
|
|
225
|
+
return { success: false, cannotVerify: true, error: safeSubprocessError(e, 'Python 调用失败') }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const lines = stdout.split('\n').filter((l: string) => l.trim())
|
|
229
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
230
|
+
const trimmed = lines[i].trim()
|
|
231
|
+
if (trimmed.startsWith('{"')) {
|
|
232
|
+
try {
|
|
233
|
+
const r = JSON.parse(trimmed)
|
|
234
|
+
if (r.success === undefined) {
|
|
235
|
+
// 只有 error 字段 = 环境问题 (如 "需要 sqlcipher3")
|
|
236
|
+
return { success: false, cannotVerify: true, error: r.error }
|
|
237
|
+
}
|
|
238
|
+
return { success: !!r.success, tables: r.tables, error: r.error }
|
|
239
|
+
} catch { /* try next line */ }
|
|
240
|
+
}
|
|
171
241
|
}
|
|
172
|
-
|
|
242
|
+
return { success: false, cannotVerify: true, error: 'verify 无有效输出' }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getSessions(keyword?: string): Promise<NtSessionsResult> {
|
|
246
|
+
const args: string[] = ['sessions']
|
|
247
|
+
const result = await this.callPython(args, { keyword })
|
|
173
248
|
if (result.error) {
|
|
174
249
|
return { success: false, error: result.error }
|
|
175
250
|
}
|
|
@@ -179,22 +254,12 @@ export class NtCore {
|
|
|
179
254
|
async getMessages(talker: string, limit = 100, offset = 0): Promise<NtMessagesResult> {
|
|
180
255
|
const args: string[] = [
|
|
181
256
|
'messages',
|
|
182
|
-
'--db', this.dbPath,
|
|
183
|
-
'--key', this.keyHex,
|
|
184
|
-
'--salt', this.saltHex,
|
|
185
|
-
'--talker', talker,
|
|
186
257
|
'--limit', String(limit),
|
|
187
258
|
'--offset', String(offset),
|
|
188
259
|
]
|
|
189
|
-
if (this.contactDbPath && this.contactKey && this.contactSalt) {
|
|
190
|
-
args.push('--contact-db', this.contactDbPath)
|
|
191
|
-
args.push('--contact-key', this.contactKey)
|
|
192
|
-
args.push('--contact-salt', this.contactSalt)
|
|
193
|
-
}
|
|
194
260
|
// Pass own wxid from config for self-message detection
|
|
195
261
|
const ownWxid = configService.get('wxid')
|
|
196
|
-
|
|
197
|
-
const result = await this.callPython(args)
|
|
262
|
+
const result = await this.callPython(args, { talker, ownWxid })
|
|
198
263
|
if (result.error) {
|
|
199
264
|
return { success: false, error: result.error }
|
|
200
265
|
}
|
|
@@ -204,21 +269,97 @@ export class NtCore {
|
|
|
204
269
|
async getContacts(keyword?: string, limit = 200): Promise<NtContactsResult> {
|
|
205
270
|
const args: string[] = [
|
|
206
271
|
'contacts',
|
|
207
|
-
'--db', this.dbPath,
|
|
208
|
-
'--key', this.keyHex,
|
|
209
|
-
'--salt', this.saltHex,
|
|
210
272
|
'--limit', String(limit),
|
|
211
273
|
]
|
|
212
|
-
|
|
213
|
-
if (this.contactDbPath && this.contactKey && this.contactSalt) {
|
|
214
|
-
args.push('--contact-db', this.contactDbPath)
|
|
215
|
-
args.push('--contact-key', this.contactKey)
|
|
216
|
-
args.push('--contact-salt', this.contactSalt)
|
|
217
|
-
}
|
|
218
|
-
const result = await this.callPython(args)
|
|
274
|
+
const result = await this.callPython(args, { keyword })
|
|
219
275
|
if (result.error) {
|
|
220
276
|
return { success: false, error: result.error }
|
|
221
277
|
}
|
|
222
278
|
return { success: true, contacts: result.contacts || [] }
|
|
223
279
|
}
|
|
280
|
+
|
|
281
|
+
// ====== SNS (朋友圈) ======
|
|
282
|
+
|
|
283
|
+
/** 查询 SNS 朋友圈时间线 */
|
|
284
|
+
async getSnsTimeline(snsDbPath: string, snsKey: string, snsSalt: string, opts: {
|
|
285
|
+
limit?: number; offset?: number; usernames?: string[];
|
|
286
|
+
keyword?: string; startTime?: number; endTime?: number;
|
|
287
|
+
} = {}): Promise<{ success: boolean; timeline?: any[]; error?: string }> {
|
|
288
|
+
const args: string[] = [
|
|
289
|
+
'sns-timeline',
|
|
290
|
+
'--limit', String(opts.limit ?? 20),
|
|
291
|
+
'--offset', String(opts.offset ?? 0),
|
|
292
|
+
]
|
|
293
|
+
if (opts.startTime) args.push('--start-time', String(opts.startTime))
|
|
294
|
+
if (opts.endTime) args.push('--end-time', String(opts.endTime))
|
|
295
|
+
const result = await this.callPython(args, {
|
|
296
|
+
dbPath: snsDbPath,
|
|
297
|
+
key: snsKey,
|
|
298
|
+
salt: snsSalt,
|
|
299
|
+
keyword: opts.keyword,
|
|
300
|
+
usernames: opts.usernames?.length ? JSON.stringify(opts.usernames) : undefined,
|
|
301
|
+
})
|
|
302
|
+
if (result.error) return { success: false, error: result.error }
|
|
303
|
+
return { success: true, timeline: result.timeline || [] }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** 获取朋友圈中有动态的用户列表 */
|
|
307
|
+
async getSnsUsernames(snsDbPath: string, snsKey: string, snsSalt: string): Promise<{ success: boolean; usernames?: string[]; error?: string }> {
|
|
308
|
+
const result = await this.callPython(['sns-usernames'], { dbPath: snsDbPath, key: snsKey, salt: snsSalt })
|
|
309
|
+
if (result.error) return { success: false, error: result.error }
|
|
310
|
+
return { success: true, usernames: result.usernames || [] }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** 获取朋友圈统计信息 */
|
|
314
|
+
async getSnsExportStats(snsDbPath: string, snsKey: string, snsSalt: string, myWxid?: string): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number; myPosts: number | null }; error?: string }> {
|
|
315
|
+
const result = await this.callPython(['sns-stats'], { dbPath: snsDbPath, key: snsKey, salt: snsSalt, ownWxid: myWxid })
|
|
316
|
+
if (result.error) return { success: false, error: result.error }
|
|
317
|
+
return { success: true, data: result.data }
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ====== 收藏 (favorite.db) ======
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 从全库共用 passphrase 派生某库的 raw key。
|
|
324
|
+
* 微信 4.x 各库密钥 = PBKDF2-HMAC-SHA512(passphrase, 库salt, 256000轮, 32字节)
|
|
325
|
+
*/
|
|
326
|
+
static deriveRawKey(passphraseHex: string, saltHex: string): string {
|
|
327
|
+
const passphrase = Buffer.from(passphraseHex, 'hex')
|
|
328
|
+
const salt = Buffer.from(saltHex, 'hex')
|
|
329
|
+
return crypto.pbkdf2Sync(passphrase, salt, 256000, 32, 'sha512').toString('hex')
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** 读取数据库文件前 16 字节盐值 */
|
|
333
|
+
static readDbSalt(dbPath: string): string | null {
|
|
334
|
+
let fd: number
|
|
335
|
+
try {
|
|
336
|
+
fd = openSync(dbPath, 'r')
|
|
337
|
+
} catch {
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const buf = Buffer.alloc(16)
|
|
342
|
+
const n = readSync(fd, buf, 0, 16, 0)
|
|
343
|
+
return n === 16 ? buf.toString('hex') : null
|
|
344
|
+
} catch {
|
|
345
|
+
return null
|
|
346
|
+
} finally {
|
|
347
|
+
closeSync(fd)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** 查询收藏列表 (keyHex 为派生后的 raw key) */
|
|
352
|
+
async getFavorites(favDbPath: string, keyHex: string, opts: {
|
|
353
|
+
limit?: number; offset?: number; keyword?: string; favType?: number;
|
|
354
|
+
} = {}): Promise<{ success: boolean; favorites?: any[]; total?: number; error?: string }> {
|
|
355
|
+
const args: string[] = [
|
|
356
|
+
'fav-list',
|
|
357
|
+
'--limit', String(opts.limit ?? 100),
|
|
358
|
+
'--offset', String(opts.offset ?? 0),
|
|
359
|
+
]
|
|
360
|
+
if (opts.favType != null) args.push('--type', String(opts.favType))
|
|
361
|
+
const result = await this.callPython(args, { dbPath: favDbPath, key: keyHex, salt: undefined, keyword: opts.keyword })
|
|
362
|
+
if (result.error) return { success: false, error: result.error }
|
|
363
|
+
return { success: true, favorites: result.favorites || [], total: result.total ?? 0 }
|
|
364
|
+
}
|
|
224
365
|
}
|
|
@@ -17,9 +17,23 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync, writeFile
|
|
|
17
17
|
import { join, basename, dirname } from 'path'
|
|
18
18
|
import os from 'os'
|
|
19
19
|
import { DatabaseSync } from 'node:sqlite'
|
|
20
|
-
import
|
|
20
|
+
import { createRequire } from 'module'
|
|
21
21
|
import type { ChatSession, Message, Contact } from '../types.js'
|
|
22
22
|
|
|
23
|
+
const require = createRequire(import.meta.url)
|
|
24
|
+
|
|
25
|
+
// lz4 是原生模块(仅 3.x WCDB 解压用),懒加载避免非 Windows 平台启动即崩溃
|
|
26
|
+
let lz4Lazy: any = null
|
|
27
|
+
function getLz4(): any {
|
|
28
|
+
if (lz4Lazy !== null) return lz4Lazy
|
|
29
|
+
try {
|
|
30
|
+
lz4Lazy = require('lz4')
|
|
31
|
+
} catch (e) {
|
|
32
|
+
lz4Lazy = undefined
|
|
33
|
+
}
|
|
34
|
+
return lz4Lazy
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
const PAGE_SIZE = 4096
|
|
24
38
|
const KEY_SIZE = 32
|
|
25
39
|
const DEFAULT_ITER = 64000
|
|
@@ -549,6 +563,8 @@ export class SqlcipherCore {
|
|
|
549
563
|
if (!buf) return null
|
|
550
564
|
|
|
551
565
|
try {
|
|
566
|
+
const lz4 = getLz4()
|
|
567
|
+
if (!lz4) return null
|
|
552
568
|
const maxSize = buf.length * 256
|
|
553
569
|
const outBuf = Buffer.alloc(maxSize)
|
|
554
570
|
const actualSize = lz4.decodeBlock(buf, outBuf)
|
package/src/core/wcdbCore.ts
CHANGED
|
@@ -3586,19 +3586,15 @@ export class WcdbCore {
|
|
|
3586
3586
|
if (!this.ensureReady()) {
|
|
3587
3587
|
return { success: false, error: 'WCDB 未连接' }
|
|
3588
3588
|
}
|
|
3589
|
+
if (params && params.length > 0) {
|
|
3590
|
+
return { success: false, error: 'PARAMETER_BINDING_UNSUPPORTED' }
|
|
3591
|
+
}
|
|
3589
3592
|
const startedAt = Date.now()
|
|
3590
3593
|
try {
|
|
3591
3594
|
if (!this.wcdbExecQuery) return { success: false, error: '接口未就绪' }
|
|
3592
3595
|
const fallbackFlag = /fallback|diag|diagnostic/i.test(String(sql || ''))
|
|
3593
3596
|
this.writeLog(`[audit:execQuery] kind=${kind} path=${path || ''} sql_len=${String(sql || '').length} fallback=${fallbackFlag ? 1 : 0}`)
|
|
3594
3597
|
|
|
3595
|
-
// 如果提供了参数,使用参数化查询(需要 C++ 层支持)
|
|
3596
|
-
// 注意:当前 wcdbExecQuery 可能不支持参数化,这是一个占位符实现
|
|
3597
|
-
// TODO: 需要更新 C++ 层的 wcdb_exec_query 以支持参数绑定
|
|
3598
|
-
if (params && params.length > 0) {
|
|
3599
|
-
console.warn('[wcdbCore] execQuery: 参数化查询暂未在 C++ 层实现,将使用原始 SQL(可能存在注入风险)')
|
|
3600
|
-
}
|
|
3601
|
-
|
|
3602
3598
|
const normalizedKind = String(kind || '').toLowerCase()
|
|
3603
3599
|
const isContactQuery = normalizedKind === 'contact' || /\bfrom\s+contact\b/i.test(String(sql))
|
|
3604
3600
|
let effectivePath = path || ''
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 助手守护进程管理 — start(后台常驻) / stop / status / run(前台)
|
|
3
|
+
* pid 文件: ~/.weflow-cli/assistant.pid, 日志: ~/.weflow-cli/assistant.log
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, appendFileSync, statSync } from 'fs'
|
|
7
|
+
import os from 'os'
|
|
8
|
+
import { spawn, type ChildProcess } from 'child_process'
|
|
9
|
+
import { resolvePackageRoot } from '../utils/packageRoot.js'
|
|
10
|
+
import { createPythonProcessEnv } from '../utils/pythonProcessEnv.js'
|
|
11
|
+
|
|
12
|
+
const DIR = join(os.homedir(), '.weflow-cli')
|
|
13
|
+
const PID_FILE = join(DIR, 'assistant.pid')
|
|
14
|
+
const LOG_FILE = join(DIR, 'assistant.log')
|
|
15
|
+
const MARKER = 'WEFLOW_ASSISTANT_DAEMON=1'
|
|
16
|
+
|
|
17
|
+
export function isDaemonAlive(): { alive: boolean; pid: number | null } {
|
|
18
|
+
if (!existsSync(PID_FILE)) return { alive: false, pid: null }
|
|
19
|
+
try {
|
|
20
|
+
const pid = parseInt(readFileSync(PID_FILE, 'utf8').trim())
|
|
21
|
+
// Windows: process.kill(pid, 0) 对不存在进程抛错; 对存在进程返回 undefined
|
|
22
|
+
process.kill(pid, 0)
|
|
23
|
+
return { alive: true, pid }
|
|
24
|
+
} catch {
|
|
25
|
+
rmSync(PID_FILE, { force: true })
|
|
26
|
+
return { alive: false, pid: null }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function appendLog(line: string): void {
|
|
31
|
+
try {
|
|
32
|
+
if (!existsSync(DIR)) mkdirSync(DIR, { recursive: true })
|
|
33
|
+
appendFileSync(LOG_FILE, line + '\n', 'utf8')
|
|
34
|
+
} catch { /* 日志失败不阻断 */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 日志轮转: 超过 1MB 保留尾部 256KB, 防止常驻进程日志无限增长 */
|
|
38
|
+
export function rotateLogIfNeeded(): void {
|
|
39
|
+
try {
|
|
40
|
+
if (!existsSync(LOG_FILE)) return
|
|
41
|
+
if (statSync(LOG_FILE).size < 1024 * 1024) return
|
|
42
|
+
const buf = readFileSync(LOG_FILE)
|
|
43
|
+
const tail = buf.subarray(Math.max(0, buf.length - 256 * 1024))
|
|
44
|
+
const cut = tail.indexOf('\n') // 对齐到行首
|
|
45
|
+
writeFileSync(LOG_FILE, `--- rotated at ${new Date().toLocaleString('zh-CN')} ---\n` + tail.subarray(cut + 1))
|
|
46
|
+
appendLog(`[轮转] 日志已截断 (原 ${statSync(LOG_FILE).size} 字节)`)
|
|
47
|
+
} catch { /* 轮转失败不阻断 */ }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function tailLog(n = 20): string {
|
|
51
|
+
try {
|
|
52
|
+
const lines = readFileSync(LOG_FILE, 'utf8').trim().split('\n')
|
|
53
|
+
return lines.slice(-n).join('\n')
|
|
54
|
+
} catch {
|
|
55
|
+
return '(暂无日志)'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 后台启动守护进程 (detached, 脱离终端生命周期) */
|
|
60
|
+
export function startDaemon(): { started: boolean; pid?: number; error?: string } {
|
|
61
|
+
const { alive, pid } = isDaemonAlive()
|
|
62
|
+
if (alive) return { started: false, pid: pid!, error: `已在运行 (pid ${pid})` }
|
|
63
|
+
|
|
64
|
+
const packageRoot = resolvePackageRoot(import.meta.url)
|
|
65
|
+
const compiledEntry = join(packageRoot, 'dist', 'bin', 'weflow-cli.js')
|
|
66
|
+
const sourceEntry = join(packageRoot, 'bin', 'weflow-cli.ts')
|
|
67
|
+
const entry = existsSync(compiledEntry) ? compiledEntry : sourceEntry
|
|
68
|
+
|
|
69
|
+
if (!existsSync(entry)) return { started: false, error: `入口不存在: ${entry}` }
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
appendLog(`--- ${new Date().toLocaleString('zh-CN')} daemon starting ---`)
|
|
73
|
+
const entryArgs = entry === sourceEntry ? ['--import', 'tsx', entry] : [entry]
|
|
74
|
+
const child: ChildProcess = spawn(process.execPath, [...entryArgs, 'assistant', 'run'], {
|
|
75
|
+
detached: true,
|
|
76
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
77
|
+
env: createPythonProcessEnv({ [MARKER]: '1' }),
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// 收集子进程输出写入日志
|
|
82
|
+
child.stdout?.on('data', (d: Buffer) => appendLog(d.toString().trim()))
|
|
83
|
+
child.stderr?.on('data', (d: Buffer) => appendLog('[stderr] ' + d.toString().trim()))
|
|
84
|
+
child.on('exit', (code) => {
|
|
85
|
+
appendLog(`--- daemon exited (code ${code}) ---`)
|
|
86
|
+
try { if (existsSync(PID_FILE)) rmSync(PID_FILE, { force: true }) } catch {}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (child.pid) {
|
|
90
|
+
writeFileSync(PID_FILE, String(child.pid), 'utf8')
|
|
91
|
+
return { started: true, pid: child.pid }
|
|
92
|
+
}
|
|
93
|
+
return { started: false, error: '无法获取子进程 pid' }
|
|
94
|
+
} catch (e: any) {
|
|
95
|
+
return { started: false, error: e.message }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function stopDaemon(): { stopped: boolean; message: string } {
|
|
100
|
+
const { alive, pid } = isDaemonAlive()
|
|
101
|
+
if (!alive) return { stopped: false, message: '守护进程未在运行' }
|
|
102
|
+
try {
|
|
103
|
+
// Windows 下 SIGTERM/SIGKILL 等价 terminate
|
|
104
|
+
process.kill(pid!, 'SIGTERM')
|
|
105
|
+
rmSync(PID_FILE, { force: true })
|
|
106
|
+
appendLog(`--- ${new Date().toLocaleString('zh-CN')} daemon stopped manually ---`)
|
|
107
|
+
return { stopped: true, message: `已停止 (pid ${pid})` }
|
|
108
|
+
} catch (e: any) {
|
|
109
|
+
return { stopped: false, message: `停止失败: ${e.message}` }
|
|
110
|
+
}
|
|
111
|
+
}
|