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
package/mcp-server/index.ts
CHANGED
|
@@ -11,11 +11,35 @@ import {
|
|
|
11
11
|
CallToolRequestSchema,
|
|
12
12
|
ListToolsRequestSchema,
|
|
13
13
|
} from '@modelcontextprotocol/sdk/types.js'
|
|
14
|
-
import { readFileSync, readdirSync, existsSync, statSync
|
|
15
|
-
import { join
|
|
16
|
-
import {
|
|
14
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'fs'
|
|
15
|
+
import { join } from 'path'
|
|
16
|
+
import { isAllowedWeChatArticleUrl, safeChildPath, safeDate } from '../src/utils/mcpSecurity.js'
|
|
17
|
+
import { resolvePackageRoot } from '../src/utils/packageRoot.js'
|
|
18
|
+
import { chatService } from '../src/services/chatService.js'
|
|
19
|
+
import { createWeFlowEnvelope } from '../src/services/messageContract.js'
|
|
17
20
|
|
|
18
21
|
// ---- 微信公众号文章抓取 ----
|
|
22
|
+
async function readResponseTextLimited(response: Response, maximumBytes: number): Promise<string> {
|
|
23
|
+
const contentLength = Number(response.headers.get('content-length') || 0)
|
|
24
|
+
if (contentLength > maximumBytes) throw new Error('RESPONSE_TOO_LARGE')
|
|
25
|
+
if (!response.body) return ''
|
|
26
|
+
|
|
27
|
+
const reader = response.body.getReader()
|
|
28
|
+
const chunks: Uint8Array[] = []
|
|
29
|
+
let size = 0
|
|
30
|
+
while (true) {
|
|
31
|
+
const { done, value } = await reader.read()
|
|
32
|
+
if (done) break
|
|
33
|
+
size += value.byteLength
|
|
34
|
+
if (size > maximumBytes) {
|
|
35
|
+
await reader.cancel()
|
|
36
|
+
throw new Error('RESPONSE_TOO_LARGE')
|
|
37
|
+
}
|
|
38
|
+
chunks.push(value)
|
|
39
|
+
}
|
|
40
|
+
return Buffer.concat(chunks).toString('utf8')
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
async function fetchWeChatArticle(url: string): Promise<{
|
|
20
44
|
title: string
|
|
21
45
|
author: string
|
|
@@ -23,16 +47,28 @@ async function fetchWeChatArticle(url: string): Promise<{
|
|
|
23
47
|
content: string
|
|
24
48
|
images: string[]
|
|
25
49
|
}> {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
let currentUrl = url
|
|
51
|
+
let resp: Response | null = null
|
|
52
|
+
for (let redirectCount = 0; redirectCount <= 3; redirectCount++) {
|
|
53
|
+
if (!isAllowedWeChatArticleUrl(currentUrl)) throw new Error('URL_NOT_ALLOWED')
|
|
54
|
+
resp = await fetch(currentUrl, {
|
|
55
|
+
headers: {
|
|
56
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
57
|
+
'Accept': 'text/html,application/xhtml+xml',
|
|
58
|
+
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
59
|
+
},
|
|
60
|
+
redirect: 'manual',
|
|
61
|
+
signal: AbortSignal.timeout(20_000),
|
|
62
|
+
})
|
|
63
|
+
if (resp.status < 300 || resp.status >= 400) break
|
|
64
|
+
const location = resp.headers.get('location')
|
|
65
|
+
if (!location) throw new Error('REDIRECT_WITHOUT_LOCATION')
|
|
66
|
+
currentUrl = new URL(location, currentUrl).toString()
|
|
67
|
+
resp = null
|
|
68
|
+
}
|
|
69
|
+
if (!resp) throw new Error('TOO_MANY_REDIRECTS')
|
|
34
70
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText}`)
|
|
35
|
-
const html = await resp
|
|
71
|
+
const html = await readResponseTextLimited(resp, 5 * 1024 * 1024)
|
|
36
72
|
|
|
37
73
|
// 提取 meta 信息
|
|
38
74
|
const title = html.match(/<meta[^>]*property="og:title"[^>]*content="([^"]+)"/i)?.[1]
|
|
@@ -110,103 +146,75 @@ function htmlToMarkdown(html: string): string {
|
|
|
110
146
|
return md
|
|
111
147
|
}
|
|
112
148
|
import { formatWeChatArticle, listThemes } from '../src/services/wechat-formatter.js'
|
|
149
|
+
import { boundedToolInteger, MCP_READ_ONLY_TOOL_DEFS, executeTool } from '../src/services/assistantTools.js'
|
|
150
|
+
import { AssistantMemory } from '../src/services/assistantMemory.js'
|
|
151
|
+
|
|
152
|
+
// ---- 本地微信数据工具 (复用 assistant 工具层, 微信 bot / MCP server 同源) ----
|
|
153
|
+
// MCP 无微信用户身份, 记忆固定挂 'mcp' 用户下; 与 assistant 守护进程共用同一持久化文件
|
|
154
|
+
const mcpMemory = new AssistantMemory()
|
|
155
|
+
const MCP_TOOL_CTX = { userId: 'mcp', memory: mcpMemory }
|
|
156
|
+
|
|
157
|
+
/** assistant 工具 (OpenAI function 格式) → MCP 工具格式; get_stats 并入现有 wechat.get_stats */
|
|
158
|
+
const ASSISTANT_TOOLS = MCP_READ_ONLY_TOOL_DEFS
|
|
159
|
+
.filter(t => t.function.name !== 'get_stats')
|
|
160
|
+
.map(t => ({
|
|
161
|
+
name: `wechat.${t.function.name}`,
|
|
162
|
+
description: t.function.description,
|
|
163
|
+
inputSchema: t.function.parameters,
|
|
164
|
+
}))
|
|
113
165
|
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
throw new Error(
|
|
130
|
-
}
|
|
131
|
-
const resp = await fetch(
|
|
132
|
-
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}`
|
|
133
|
-
)
|
|
134
|
-
const data = await resp.json() as any
|
|
135
|
-
if (data.errcode) {
|
|
136
|
-
throw new Error(`微信 Token 获取失败: ${data.errmsg} (${data.errcode})`)
|
|
137
|
-
}
|
|
138
|
-
_tokenCache = {
|
|
139
|
-
token: data.access_token,
|
|
140
|
-
expiresAt: Date.now() + (data.expires_in - 60) * 1000,
|
|
166
|
+
const PKG_ROOT = resolvePackageRoot(import.meta.url)
|
|
167
|
+
const BIZ_DAILY = join(PKG_ROOT, 'output', 'biz-daily')
|
|
168
|
+
const VAULT_WIKI = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', 'Concepts')
|
|
169
|
+
const VAULT_INDEX = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', '00-Overview.md')
|
|
170
|
+
const REVIEWS = join(PKG_ROOT, 'output', 'reviews', 'Daily')
|
|
171
|
+
const MCP_MESSAGE_LIMIT_DEFAULT = 100
|
|
172
|
+
const MCP_MESSAGE_LIMIT_MAX = 1000
|
|
173
|
+
|
|
174
|
+
function parseMessageDate(value: unknown, label: string): number | undefined {
|
|
175
|
+
if (value === undefined || value === null || value === '') return undefined
|
|
176
|
+
const date = safeDate(value)
|
|
177
|
+
if (!date) throw new Error(`${label} 必须是有效的 YYYY-MM-DD 日期`)
|
|
178
|
+
const [year, month, day] = date.split('-').map(Number)
|
|
179
|
+
const start = new Date(year, month - 1, day)
|
|
180
|
+
if (start.getFullYear() !== year || start.getMonth() !== month - 1 || start.getDate() !== day) {
|
|
181
|
+
throw new Error(`${label} 必须是有效的日历日期`)
|
|
141
182
|
}
|
|
142
|
-
return
|
|
183
|
+
return Math.floor(start.getTime() / 1000)
|
|
143
184
|
}
|
|
144
185
|
|
|
145
|
-
async function
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
'', '',
|
|
157
|
-
].join('\r\n')
|
|
158
|
-
const trailer = `\r\n--${boundary}--\r\n`
|
|
159
|
-
|
|
160
|
-
const headerBytes = new TextEncoder().encode(header)
|
|
161
|
-
const trailerBytes = new TextEncoder().encode(trailer)
|
|
162
|
-
const body = new Uint8Array(headerBytes.length + buf.length + trailerBytes.length)
|
|
163
|
-
body.set(headerBytes, 0)
|
|
164
|
-
body.set(buf, headerBytes.length)
|
|
165
|
-
body.set(trailerBytes, headerBytes.length + buf.length)
|
|
166
|
-
|
|
167
|
-
const resp = await fetch(
|
|
168
|
-
`https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${token}&type=image`,
|
|
169
|
-
{
|
|
170
|
-
method: 'POST',
|
|
171
|
-
headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` },
|
|
172
|
-
body,
|
|
173
|
-
}
|
|
174
|
-
)
|
|
175
|
-
const data = await resp.json() as any
|
|
176
|
-
if (data.errcode) {
|
|
177
|
-
throw new Error(`上传封面图失败: ${data.errmsg} (${data.errcode})`)
|
|
178
|
-
}
|
|
179
|
-
return data.media_id
|
|
186
|
+
async function resolveMcpTalker(input: unknown): Promise<string> {
|
|
187
|
+
const contact = String(input || '').trim()
|
|
188
|
+
if (!contact) throw new Error('contact 不能为空')
|
|
189
|
+
if (contact.startsWith('wxid_') || contact.includes('@chatroom') || contact.includes('@openim')) return contact
|
|
190
|
+
|
|
191
|
+
const sessions = await chatService.listSessions(contact, 20)
|
|
192
|
+
const exact = sessions.filter(session => session.username === contact || session.displayName === contact)
|
|
193
|
+
if (exact.length === 1) return exact[0].username
|
|
194
|
+
if (exact.length > 1 || sessions.length > 1) throw new Error('contact 匹配到多个会话,请使用会话 ID')
|
|
195
|
+
if (sessions.length === 1) return sessions[0].username
|
|
196
|
+
throw new Error('未找到对应会话,请使用会话 ID 或准确的会话名称')
|
|
180
197
|
}
|
|
181
198
|
|
|
182
|
-
async function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
content: string
|
|
187
|
-
thumb_media_id?: string
|
|
188
|
-
}>): Promise<string> {
|
|
189
|
-
const resp = await fetch(
|
|
190
|
-
`https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${token}`,
|
|
191
|
-
{
|
|
192
|
-
method: 'POST',
|
|
193
|
-
headers: { 'Content-Type': 'application/json' },
|
|
194
|
-
body: JSON.stringify({ articles }),
|
|
195
|
-
}
|
|
196
|
-
)
|
|
197
|
-
const data = await resp.json() as any
|
|
198
|
-
if (data.errcode) {
|
|
199
|
-
throw new Error(`创建草稿失败: ${data.errmsg} (${data.errcode})`)
|
|
199
|
+
async function exportMessagesForMcp(args: Record<string, any>): Promise<string> {
|
|
200
|
+
const limit = args.limit === undefined ? MCP_MESSAGE_LIMIT_DEFAULT : Number(args.limit)
|
|
201
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MCP_MESSAGE_LIMIT_MAX) {
|
|
202
|
+
throw new Error(`limit 必须是 1-${MCP_MESSAGE_LIMIT_MAX} 的整数`)
|
|
200
203
|
}
|
|
201
|
-
|
|
204
|
+
const from = parseMessageDate(args.from, 'from')
|
|
205
|
+
const toStart = parseMessageDate(args.to, 'to')
|
|
206
|
+
const to = toStart === undefined ? undefined : toStart + 24 * 60 * 60 - 1
|
|
207
|
+
if (from !== undefined && to !== undefined && from > to) throw new Error('from 不能晚于 to')
|
|
208
|
+
|
|
209
|
+
const talker = await resolveMcpTalker(args.contact)
|
|
210
|
+
const messages = await chatService.getMessagesInRange(talker, limit, from, to)
|
|
211
|
+
return JSON.stringify(createWeFlowEnvelope(messages, new Date().toISOString(), {
|
|
212
|
+
requestedFrom: from,
|
|
213
|
+
requestedTo: to,
|
|
214
|
+
requestedLimit: limit,
|
|
215
|
+
}), null, 2)
|
|
202
216
|
}
|
|
203
217
|
|
|
204
|
-
const PKG_ROOT = resolve(join(import.meta.dirname || '.', '..'))
|
|
205
|
-
const BIZ_DAILY = join(PKG_ROOT, 'output', 'biz-daily')
|
|
206
|
-
const VAULT_WIKI = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', 'Concepts')
|
|
207
|
-
const VAULT_INDEX = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', '00-Overview.md')
|
|
208
|
-
const REVIEWS = join(PKG_ROOT, 'output', 'reviews', 'Daily')
|
|
209
|
-
|
|
210
218
|
function parseFrontmatter(text: string): Record<string, any> {
|
|
211
219
|
if (!text.startsWith('---')) return {}
|
|
212
220
|
const end = text.indexOf('---', 3)
|
|
@@ -268,7 +276,7 @@ function searchArticles(args: Record<string, any>): string {
|
|
|
268
276
|
if (args.date) {
|
|
269
277
|
articles = articles.filter(a => a.dateDir === args.date)
|
|
270
278
|
}
|
|
271
|
-
const limit =
|
|
279
|
+
const limit = boundedToolInteger(args.limit, 20, 100)
|
|
272
280
|
const top = articles.slice(0, limit)
|
|
273
281
|
if (!top.length) return '未找到匹配文章'
|
|
274
282
|
return top.map(a =>
|
|
@@ -276,66 +284,6 @@ function searchArticles(args: Record<string, any>): string {
|
|
|
276
284
|
).join('\n\n')
|
|
277
285
|
}
|
|
278
286
|
|
|
279
|
-
/** 自动生成封面图(纯色主题渐变 PNG,无外部依赖) */
|
|
280
|
-
function generateCoverImage(title: string, theme: string): string {
|
|
281
|
-
const colors: Record<string, [number, number, number]> = {
|
|
282
|
-
warm: [230, 126, 34],
|
|
283
|
-
default: [52, 152, 219],
|
|
284
|
-
minimal: [51, 51, 51],
|
|
285
|
-
green: [39, 174, 96],
|
|
286
|
-
}
|
|
287
|
-
const [r, g, b] = colors[theme] || colors.default
|
|
288
|
-
const w = 900, h = 500
|
|
289
|
-
|
|
290
|
-
function crc32(buf: Buffer): number {
|
|
291
|
-
let c = 0xffffffff
|
|
292
|
-
for (let i = 0; i < buf.length; i++) {
|
|
293
|
-
c ^= buf[i]
|
|
294
|
-
for (let j = 0; j < 8; j++) c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0)
|
|
295
|
-
}
|
|
296
|
-
return (c ^ 0xffffffff) >>> 0
|
|
297
|
-
}
|
|
298
|
-
function chunk(type: string, data: Buffer): Buffer {
|
|
299
|
-
const head = Buffer.alloc(8)
|
|
300
|
-
head.writeUInt32BE(data.length, 0)
|
|
301
|
-
head.write(type, 4)
|
|
302
|
-
const crcBuf = Buffer.alloc(4)
|
|
303
|
-
crcBuf.writeUInt32BE(crc32(Buffer.concat([head.slice(4, 8), data])), 0)
|
|
304
|
-
return Buffer.concat([head, data, crcBuf])
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
|
308
|
-
const ihdr = Buffer.alloc(13)
|
|
309
|
-
ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4)
|
|
310
|
-
ihdr[8] = 8; ihdr[9] = 2
|
|
311
|
-
|
|
312
|
-
const raw = Buffer.alloc(h * (1 + w * 3))
|
|
313
|
-
for (let y = 0; y < h; y++) {
|
|
314
|
-
const offset = y * (1 + w * 3)
|
|
315
|
-
raw[offset] = 0
|
|
316
|
-
for (let x = 0; x < w; x++) {
|
|
317
|
-
const px = offset + 1 + x * 3
|
|
318
|
-
const darken = 0.9 + 0.1 * (y / h)
|
|
319
|
-
raw[px] = Math.floor(r * darken)
|
|
320
|
-
raw[px + 1] = Math.floor(g * darken)
|
|
321
|
-
raw[px + 2] = Math.floor(b * darken)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const png = Buffer.concat([
|
|
326
|
-
signature,
|
|
327
|
-
chunk('IHDR', ihdr),
|
|
328
|
-
chunk('IDAT', deflateSync(raw)),
|
|
329
|
-
chunk('IEND', Buffer.alloc(0)),
|
|
330
|
-
])
|
|
331
|
-
|
|
332
|
-
const tmpDir = join(PKG_ROOT, 'output', '.tmp')
|
|
333
|
-
if (!existsSync(tmpDir)) mkdirSync(tmpDir, { recursive: true })
|
|
334
|
-
const path = join(tmpDir, `cover-${Date.now()}.png`)
|
|
335
|
-
writeFileSync(path, png)
|
|
336
|
-
return path
|
|
337
|
-
}
|
|
338
|
-
|
|
339
287
|
async function main() {
|
|
340
288
|
const server = new Server(
|
|
341
289
|
{ name: 'weflow-mcp', version: '1.0.0' },
|
|
@@ -395,7 +343,7 @@ async function main() {
|
|
|
395
343
|
},
|
|
396
344
|
{
|
|
397
345
|
name: 'wechat.get_stats',
|
|
398
|
-
description: '
|
|
346
|
+
description: '统计概览: 知识库文章/概念页 + 本地微信数据(会话数/收藏总数)',
|
|
399
347
|
inputSchema: { type: 'object', properties: {} },
|
|
400
348
|
},
|
|
401
349
|
{
|
|
@@ -410,22 +358,6 @@ async function main() {
|
|
|
410
358
|
required: ['content'],
|
|
411
359
|
},
|
|
412
360
|
},
|
|
413
|
-
{
|
|
414
|
-
name: 'wechat.publish_article',
|
|
415
|
-
description: '排版并发布 Markdown 文章到微信公众号草稿箱。需要设置环境变量 WECHAT_APPID 和 WECHAT_APPSECRET',
|
|
416
|
-
inputSchema: {
|
|
417
|
-
type: 'object',
|
|
418
|
-
properties: {
|
|
419
|
-
title: { type: 'string', description: '文章标题(≤64字符)' },
|
|
420
|
-
content: { type: 'string', description: 'Markdown 格式的文章内容' },
|
|
421
|
-
author: { type: 'string', description: '作者名(≤8字符),默认 "AI Assistant"' },
|
|
422
|
-
theme: { type: 'string', description: '排版主题: default | warm | minimal | green,默认 default' },
|
|
423
|
-
cover_image: { type: 'string', description: '封面图本地路径(可选)' },
|
|
424
|
-
preview_only: { type: 'boolean', description: '仅排版预览不发布草稿,默认 false' },
|
|
425
|
-
},
|
|
426
|
-
required: ['title', 'content'],
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
361
|
{
|
|
430
362
|
name: 'wechat.list_themes',
|
|
431
363
|
description: '列出微信公众号排版可用的所有主题',
|
|
@@ -454,6 +386,22 @@ async function main() {
|
|
|
454
386
|
required: ['url'],
|
|
455
387
|
},
|
|
456
388
|
},
|
|
389
|
+
{
|
|
390
|
+
name: 'wechat.export_messages',
|
|
391
|
+
description: '以 weflow-message/v1 JSON 返回指定会话的本地消息。只读,不返回数据库路径、密钥或配置。',
|
|
392
|
+
inputSchema: {
|
|
393
|
+
type: 'object',
|
|
394
|
+
properties: {
|
|
395
|
+
contact: { type: 'string', description: '会话 ID,或唯一匹配的会话名称' },
|
|
396
|
+
limit: { type: 'number', description: '返回上限,默认 100,最大 1000' },
|
|
397
|
+
from: { type: 'string', description: '起始日期 YYYY-MM-DD(含当天)' },
|
|
398
|
+
to: { type: 'string', description: '结束日期 YYYY-MM-DD(含当天)' },
|
|
399
|
+
},
|
|
400
|
+
required: ['contact'],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
// ---- 本地微信数据 (与 weflow-cli assistant / 微信 bot 共享工具层) ----
|
|
404
|
+
...ASSISTANT_TOOLS,
|
|
457
405
|
],
|
|
458
406
|
}))
|
|
459
407
|
|
|
@@ -467,9 +415,10 @@ async function main() {
|
|
|
467
415
|
|
|
468
416
|
case 'wechat.get_daily': {
|
|
469
417
|
const dates = existsSync(BIZ_DAILY) ? readdirSync(BIZ_DAILY).sort().reverse() : []
|
|
470
|
-
|
|
471
|
-
const
|
|
472
|
-
|
|
418
|
+
if (args.date && !safeDate(args.date)) return { content: [{ type: 'text', text: '日期无效,请使用 YYYY-MM-DD' }] }
|
|
419
|
+
const date = safeDate(args.date) || dates.find(d => /^\d{4}-\d{2}-\d{2}$/.test(d)) || ''
|
|
420
|
+
const readme = safeChildPath(BIZ_DAILY, join(date, 'README.md'))
|
|
421
|
+
if (!readme || !existsSync(readme)) return { content: [{ type: 'text', text: `未找到 ${date} 的日报` }] }
|
|
473
422
|
return { content: [{ type: 'text', text: readFileSync(readme, 'utf-8') }] }
|
|
474
423
|
}
|
|
475
424
|
|
|
@@ -480,9 +429,12 @@ async function main() {
|
|
|
480
429
|
|
|
481
430
|
case 'wechat.get_concept': {
|
|
482
431
|
const name = String(args.name)
|
|
432
|
+
if (!name || name.includes('/') || name.includes('\\') || name.includes('..')) {
|
|
433
|
+
return { content: [{ type: 'text', text: '概念名无效' }] }
|
|
434
|
+
}
|
|
483
435
|
for (const ext of ['', '.md']) {
|
|
484
|
-
const path =
|
|
485
|
-
if (existsSync(path)) {
|
|
436
|
+
const path = safeChildPath(VAULT_WIKI, name + ext)
|
|
437
|
+
if (path && existsSync(path)) {
|
|
486
438
|
return { content: [{ type: 'text', text: readFileSync(path, 'utf-8') }] }
|
|
487
439
|
}
|
|
488
440
|
}
|
|
@@ -490,7 +442,8 @@ async function main() {
|
|
|
490
442
|
if (existsSync(VAULT_WIKI)) {
|
|
491
443
|
for (const f of readdirSync(VAULT_WIKI)) {
|
|
492
444
|
if (f.includes(name)) {
|
|
493
|
-
|
|
445
|
+
const path = safeChildPath(VAULT_WIKI, f)
|
|
446
|
+
if (path) return { content: [{ type: 'text', text: readFileSync(path, 'utf-8') }] }
|
|
494
447
|
}
|
|
495
448
|
}
|
|
496
449
|
}
|
|
@@ -502,7 +455,7 @@ async function main() {
|
|
|
502
455
|
const date = String(args.date || '')
|
|
503
456
|
let path: string
|
|
504
457
|
if (date) {
|
|
505
|
-
path = join(REVIEWS, `Daily-${date}.md`)
|
|
458
|
+
path = safeDate(date) ? join(REVIEWS, `Daily-${date}.md`) : ''
|
|
506
459
|
} else {
|
|
507
460
|
path = files.length ? join(REVIEWS, files[0]) : ''
|
|
508
461
|
}
|
|
@@ -540,78 +493,6 @@ async function main() {
|
|
|
540
493
|
}
|
|
541
494
|
}
|
|
542
495
|
|
|
543
|
-
case 'wechat.publish_article': {
|
|
544
|
-
const title = String(args.title || '').slice(0, 64)
|
|
545
|
-
const content = String(args.content || '')
|
|
546
|
-
const author = String(args.author || 'AI Assistant').slice(0, 8)
|
|
547
|
-
const theme = String(args.theme || 'default')
|
|
548
|
-
const coverImage = args.cover_image ? String(args.cover_image) : ''
|
|
549
|
-
const previewOnly = args.preview_only === true || args.preview_only === 'true'
|
|
550
|
-
|
|
551
|
-
if (!title || !content) {
|
|
552
|
-
return { content: [{ type: 'text', text: '错误: title 和 content 不能为空' }] }
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// 1. 排版
|
|
556
|
-
const html = formatWeChatArticle(content, { theme: theme as any })
|
|
557
|
-
const wordCount = html.replace(/<[^>]*>/g, '').replace(/\s+/g, '').length
|
|
558
|
-
|
|
559
|
-
if (previewOnly) {
|
|
560
|
-
return {
|
|
561
|
-
content: [{
|
|
562
|
-
type: 'text',
|
|
563
|
-
text: [
|
|
564
|
-
`[仅预览] 标题: ${title}`,
|
|
565
|
-
`作者: ${author} | 主题: ${theme} | 字数: ${wordCount}`,
|
|
566
|
-
`---`,
|
|
567
|
-
html,
|
|
568
|
-
].join('\n')
|
|
569
|
-
}]
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// 2. 发布到草稿箱
|
|
574
|
-
try {
|
|
575
|
-
const token = await getWeChatToken()
|
|
576
|
-
|
|
577
|
-
// 上传封面图(如果提供,否则自动生成)
|
|
578
|
-
let thumbMediaId = ''
|
|
579
|
-
if (coverImage && existsSync(coverImage)) {
|
|
580
|
-
thumbMediaId = await uploadWeChatImage(token, coverImage)
|
|
581
|
-
} else {
|
|
582
|
-
// 自动生成封面图
|
|
583
|
-
const autoCover = generateCoverImage(title, theme)
|
|
584
|
-
thumbMediaId = await uploadWeChatImage(token, autoCover)
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// 创建草稿
|
|
588
|
-
const mediaId = await createWeChatDraft(token, [{
|
|
589
|
-
title,
|
|
590
|
-
author,
|
|
591
|
-
digest: html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').slice(0, 120),
|
|
592
|
-
content: html,
|
|
593
|
-
thumb_media_id: thumbMediaId || undefined,
|
|
594
|
-
}])
|
|
595
|
-
|
|
596
|
-
return {
|
|
597
|
-
content: [{
|
|
598
|
-
type: 'text',
|
|
599
|
-
text: [
|
|
600
|
-
`✅ 已发布到公众号草稿箱`,
|
|
601
|
-
`media_id: ${mediaId}`,
|
|
602
|
-
`标题: ${title}`,
|
|
603
|
-
`作者: ${author} | 主题: ${theme} | 字数: ${wordCount}`,
|
|
604
|
-
`封面图: ${thumbMediaId ? '已上传' : '未提供(使用默认)'}`,
|
|
605
|
-
'',
|
|
606
|
-
`请登录 mp.weixin.qq.com 草稿箱查看和正式发布。`,
|
|
607
|
-
].join('\n')
|
|
608
|
-
}]
|
|
609
|
-
}
|
|
610
|
-
} catch (e: any) {
|
|
611
|
-
return { content: [{ type: 'text', text: `发布失败: ${e.message}\n\n——以下为排版预览——\n\n${html}` }] }
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
496
|
case 'wechat.get_stats': {
|
|
616
497
|
const articles = scanArticles()
|
|
617
498
|
const dates = [...new Set(articles.map(a => a.dateDir))].sort()
|
|
@@ -626,13 +507,21 @@ async function main() {
|
|
|
626
507
|
const m = idx.match(/共 (\d+) 个概念/)
|
|
627
508
|
if (m) stats += `- 概念页: ${m[1]} 个\n`
|
|
628
509
|
}
|
|
510
|
+
// 追加本地微信数据统计 (会话/收藏; 数据库未配置时返回提示)
|
|
511
|
+
const chatStats = await executeTool('get_stats', {}, MCP_TOOL_CTX)
|
|
512
|
+
stats += `\n## 本地微信数据\n\n${chatStats}\n`
|
|
629
513
|
return { content: [{ type: 'text', text: stats }] }
|
|
630
514
|
}
|
|
631
515
|
|
|
632
516
|
case 'wechat.search_public': {
|
|
633
517
|
const keyword = String(args.keyword || '').trim()
|
|
634
518
|
if (!keyword) return { content: [{ type: 'text', text: '错误: keyword 不能为空' }] }
|
|
635
|
-
|
|
519
|
+
let limit = 10
|
|
520
|
+
try {
|
|
521
|
+
limit = boundedToolInteger(args.limit, 10, 20)
|
|
522
|
+
} catch {
|
|
523
|
+
return { content: [{ type: 'text', text: '错误: limit 必须是 1-20 的整数' }] }
|
|
524
|
+
}
|
|
636
525
|
|
|
637
526
|
try {
|
|
638
527
|
// 使用搜狗微信搜索
|
|
@@ -643,11 +532,12 @@ async function main() {
|
|
|
643
532
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
644
533
|
'Referer': 'https://weixin.sogou.com/',
|
|
645
534
|
'Accept': 'text/html',
|
|
646
|
-
|
|
535
|
+
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
647
536
|
},
|
|
537
|
+
signal: AbortSignal.timeout(20_000),
|
|
648
538
|
}
|
|
649
539
|
)
|
|
650
|
-
const html = await resp
|
|
540
|
+
const html = await readResponseTextLimited(resp, 2 * 1024 * 1024)
|
|
651
541
|
|
|
652
542
|
// 从搜狗结果中提取标题、公众号名、摘要
|
|
653
543
|
// 搜狗将结果放在 li 标签中,每篇文章包含标题链接 + 摘要
|
|
@@ -711,16 +601,16 @@ async function main() {
|
|
|
711
601
|
].join('\n')
|
|
712
602
|
}]
|
|
713
603
|
}
|
|
714
|
-
} catch
|
|
715
|
-
return { content: [{ type: 'text', text:
|
|
604
|
+
} catch {
|
|
605
|
+
return { content: [{ type: 'text', text: '搜索失败,请检查网络连接后重试' }] }
|
|
716
606
|
}
|
|
717
607
|
}
|
|
718
608
|
|
|
719
609
|
case 'wechat.fetch_article': {
|
|
720
610
|
const url = String(args.url || '').trim()
|
|
721
611
|
if (!url) return { content: [{ type: 'text', text: '错误: url 不能为空' }] }
|
|
722
|
-
if (!url
|
|
723
|
-
return { content: [{ type: 'text', text: '错误:
|
|
612
|
+
if (!isAllowedWeChatArticleUrl(url)) {
|
|
613
|
+
return { content: [{ type: 'text', text: '错误: 仅支持 HTTPS 微信公众号文章链接 (mp.weixin.qq.com)' }] }
|
|
724
614
|
}
|
|
725
615
|
|
|
726
616
|
try {
|
|
@@ -737,16 +627,26 @@ async function main() {
|
|
|
737
627
|
].join('\n')
|
|
738
628
|
}]
|
|
739
629
|
}
|
|
740
|
-
} catch
|
|
741
|
-
return { content: [{ type: 'text', text:
|
|
630
|
+
} catch {
|
|
631
|
+
return { content: [{ type: 'text', text: '抓取失败,请检查链接和网络连接。部分文章需要微信客户端环境才能访问。' }] }
|
|
742
632
|
}
|
|
743
633
|
}
|
|
744
634
|
|
|
745
|
-
|
|
635
|
+
case 'wechat.export_messages': {
|
|
636
|
+
return { content: [{ type: 'text', text: await exportMessagesForMcp(args) }] }
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
default: {
|
|
640
|
+
// assistant 工具层统一分发 (wechat.list_sessions / wechat.get_messages / ...)
|
|
641
|
+
if (name.startsWith('wechat.')) {
|
|
642
|
+
const text = await executeTool(name.slice('wechat.'.length), args, MCP_TOOL_CTX)
|
|
643
|
+
return { content: [{ type: 'text', text }] }
|
|
644
|
+
}
|
|
746
645
|
return { content: [{ type: 'text', text: `未知工具: ${name}` }] }
|
|
646
|
+
}
|
|
747
647
|
}
|
|
748
|
-
} catch
|
|
749
|
-
return { content: [{ type: 'text', text:
|
|
648
|
+
} catch {
|
|
649
|
+
return { content: [{ type: 'text', text: '工具执行失败,请检查输入、本地配置或运行状态' }] }
|
|
750
650
|
}
|
|
751
651
|
})
|
|
752
652
|
|