weflow-cli 1.6.1 → 1.6.2
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/CHANGELOG.md +18 -0
- package/README.md +1 -0
- package/bin/weflow-cli.ts +36 -9
- package/dist/bin/weflow-cli.js +38 -9
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +4 -0
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/services/chatService.d.ts +7 -0
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +15 -5
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/exportService.d.ts.map +1 -1
- package/dist/src/services/exportService.js +15 -3
- package/dist/src/services/exportService.js.map +1 -1
- package/dist/src/utils/pythonProcessEnv.d.ts.map +1 -1
- package/dist/src/utils/pythonProcessEnv.js +16 -1
- package/dist/src/utils/pythonProcessEnv.js.map +1 -1
- package/docs/HEALTH-CHECK.md +98 -0
- package/docs/RELEASING.md +177 -124
- package/package.json +1 -1
- package/scripts/biz_daily.py +3 -1
- package/scripts/export_chat_html.py +9 -0
- package/scripts/health_check.py +336 -0
- package/scripts/nt_decrypt.py +1352 -1216
- package/scripts/watch_issues.py +212 -0
- package/scripts/watch_mail.py +131 -0
- package/src/core/ntCore.ts +4 -0
- package/src/services/chatService.ts +14 -5
- package/src/services/exportService.ts +15 -3
- package/src/utils/pythonProcessEnv.ts +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ The npm package is published separately from GitHub. It may lag behind the `mast
|
|
|
4
4
|
|
|
5
5
|
All notable user-facing changes are recorded here. This project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## 1.6.2
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Read every message shard, not just the configured one. WeChat rolls a conversation into a new `message_N.db` over time and encrypts each shard with its own PBKDF2-derived key. `sessions`, `messages`, `contacts`, `export json/txt/excel`, `evidence` and the MCP server opened only `message_0.db` with the single configured key, so anything written after the last shard roll was **invisible** - and the commands still reported success. On the machine this was found on, one conversation showed 31 messages ending 2026-08-30 instead of 1145 ending 2026-09-17. Only `export html` merged shards (its Python exporter always had), which is why the two read paths disagreed.
|
|
12
|
+
- `export <talker> html --limit N` now honours the limit. It reached only the fallback renderer; the primary path passed the flag nowhere, so a capped export of a busy chat silently produced the entire history.
|
|
13
|
+
- `export <talker> excel` works at all. `exceljs` is CJS-only, so `await import('exceljs')` yields a namespace whose only member is `default`; `new ExcelJS.Workbook()` on the namespace is a `TypeError`, which a bare `catch {}` turned into the uninformative "Excel 导出失败". The reason is now reported too.
|
|
14
|
+
- Group messages no longer show the sender's own `wxid_...:` prefix in the text (`波: [强]`, not `波: wxid_ogfiei1l1ye722: [强]`). Only `parsedContent` is normalised; `content`/`rawContent` keep the stored value.
|
|
15
|
+
- `contacts` no longer emits a blank row for `Name2Id`'s placeholder entry.
|
|
16
|
+
- `fav list` no longer blames the data channel when the actual blocker is a missing favorites key. Added `check --json` → `favoritesReady`, which distinguishes "database found" from "usable".
|
|
17
|
+
- Subprocess failures now include the last stderr line, redacted of key-shaped strings. A missing config key used to surface as `统计失败 (exit 1)` with no cause; it now names the cause and the command that fixes it.
|
|
18
|
+
- `daily-stats` / `daily` no longer tell users to run `config set bizKey`, which the CLI rejects as an unwritable key.
|
|
19
|
+
- `init --refresh` no longer fails with "密钥提取失败" when a perfectly good key is already configured. The hook only fires at the login moment, so a refresh run against an already-logged-in WeChat always times out; the fallback then consulted **only** `favPassphrase`, which `init` itself never writes (it writes `decryptKey`, and `favPassphrase` is set only when a usable `favorite.db` exists). Reads use `favPassphrase || decryptKey`, so the fallback rejected keys the rest of the tool was happily using. Reported as "最新版本的微信密钥解不开了" (Issue #9) after a WeChat upgrade, where `init --refresh` is exactly what the CLI tells users to run. The fallback now matches the read path, and the summary distinguishes a fresh capture ("密钥获取成功") from reusing what was configured ("已沿用配置中的密钥") instead of claiming success either way. Reaching that point also lets `enableFavorites` run, so favorites start working for users who previously died here.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `scripts/health_check.py` and `docs/HEALTH-CHECK.md`: a periodic, zero-token health check whose exit code is the verdict, covering version drift, shard read consistency, session freshness, export formats and the watcher tasks.
|
|
24
|
+
|
|
7
25
|
## 1.6.1
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -322,6 +322,7 @@ python scripts/fav_server.py --date YYYY-MM-DD
|
|
|
322
322
|
- [技术决策记录](./docs/DECISIONS.md):长期维护中的关键设计取舍。
|
|
323
323
|
- [分支与合并记录](./docs/BRANCHES.md):分支用途、合并时间线与并行开发的对齐约定。
|
|
324
324
|
- [发布清单](./docs/RELEASING.md):版本号、打包敏感内容检查、npm 凭据、国内镜像同步。
|
|
325
|
+
- [定期体检](./docs/HEALTH-CHECK.md):一条命令查出版本漂移、分片漏读、会话不更新与导出失败。
|
|
325
326
|
- [电子数据证据指南](./docs/EVIDENCE_GUIDE.md):证据保全、法律边界与使用注意事项。
|
|
326
327
|
- [贡献指南](./CONTRIBUTING.md) 与 [安全策略](./SECURITY.md):开发、反馈和敏感问题处理。
|
|
327
328
|
- [变更记录](./CHANGELOG.md):版本更新摘要。
|
package/bin/weflow-cli.ts
CHANGED
|
@@ -685,6 +685,8 @@ program
|
|
|
685
685
|
console.log(chalk.gray(' 尝试从 WeFlow 桌面版配置读取...'))
|
|
686
686
|
let extractedKey = opts.testMissingKeys ? '' : tryReadWeFlowKey()
|
|
687
687
|
let linuxNtKey = ''
|
|
688
|
+
/** 密钥是本次从 hook 捕获的, 还是沿用已有的 - 摘要要说准, 不能一律报"获取成功" */
|
|
689
|
+
let keyCaptured = false
|
|
688
690
|
|
|
689
691
|
if (extractedKey) {
|
|
690
692
|
console.log(chalk.green(' ✓ 从 WeFlow 配置读取到密钥'))
|
|
@@ -770,11 +772,17 @@ program
|
|
|
770
772
|
|
|
771
773
|
if (keyResult.success && keyResult.key) {
|
|
772
774
|
extractedKey = keyResult.key
|
|
775
|
+
keyCaptured = true
|
|
773
776
|
} else {
|
|
774
|
-
// hook 只在登录瞬间触发; 微信已登录时捕获不到 →
|
|
775
|
-
|
|
777
|
+
// hook 只在登录瞬间触发; 微信已登录时捕获不到 → 回退到已有密钥。
|
|
778
|
+
// decryptKey 也必须算数: init 成功时写入的就是它, 而 favPassphrase
|
|
779
|
+
// 仅在收藏库存在且验证通过时才会被设置。只看 favPassphrase 会让
|
|
780
|
+
// `init --refresh` 在一把完全可用的密钥面前报「密钥提取失败」——
|
|
781
|
+
// 而那正是 CLI 自己在微信迁移后建议用户执行的命令。
|
|
782
|
+
const fallback = configService.get('favPassphrase') || configService.get('decryptKey') || ''
|
|
776
783
|
if (fallback) {
|
|
777
|
-
console.log(chalk.gray(' Hook 未捕获到密钥 (微信已登录时不再触发密钥函数)
|
|
784
|
+
console.log(chalk.gray(' Hook 未捕获到密钥 (微信已登录时不再触发密钥函数)'))
|
|
785
|
+
console.log(chalk.gray(' 检测到已配置的密钥, 沿用它并重新验证各库...'))
|
|
778
786
|
extractedKey = fallback
|
|
779
787
|
} else {
|
|
780
788
|
console.log(chalk.red(`\n✗ 密钥提取失败: ${keyResult.error}`))
|
|
@@ -789,7 +797,8 @@ program
|
|
|
789
797
|
|
|
790
798
|
if (extractedKey) {
|
|
791
799
|
configService.set('decryptKey', extractedKey)
|
|
792
|
-
console.log(chalk.green('\n✓ 密钥获取成功!'))
|
|
800
|
+
if (keyCaptured) console.log(chalk.green('\n✓ 密钥获取成功!'))
|
|
801
|
+
else console.log(chalk.green('\n✓ 已沿用配置中的密钥 (本次未重新捕获)'))
|
|
793
802
|
} else {
|
|
794
803
|
console.log(chalk.green('\n✓ NT 密钥配置完成!'))
|
|
795
804
|
}
|
|
@@ -2519,14 +2528,26 @@ favCmd
|
|
|
2519
2528
|
else console.log(chalk.red(`\n❌ 数据库连接失败: ${conn.error}\n`))
|
|
2520
2529
|
process.exit(1)
|
|
2521
2530
|
}
|
|
2522
|
-
|
|
2531
|
+
const favReason = chatService.favUnavailableReason()
|
|
2532
|
+
if (favReason) {
|
|
2533
|
+
const detail = {
|
|
2534
|
+
channel: '当前数据通道不支持收藏查询 (需 4.x NT 连接)',
|
|
2535
|
+
path: '未找到收藏数据库 (favorite.db),请确认微信已登录并产生过收藏',
|
|
2536
|
+
key: '收藏数据库已找到,但缺少密钥',
|
|
2537
|
+
}[favReason]
|
|
2523
2538
|
if (opts.json) {
|
|
2524
|
-
console.log(JSON.stringify({ success: false, code: 'FAVORITES_UNAVAILABLE', error:
|
|
2539
|
+
console.log(JSON.stringify({ success: false, code: 'FAVORITES_UNAVAILABLE', reason: favReason, error: detail }))
|
|
2525
2540
|
process.exit(1)
|
|
2526
2541
|
}
|
|
2527
|
-
console.log(chalk.red(
|
|
2528
|
-
|
|
2529
|
-
|
|
2542
|
+
console.log(chalk.red(`\n❌ ${detail}`))
|
|
2543
|
+
if (favReason === 'key') {
|
|
2544
|
+
console.log(chalk.gray(' 请先配置收藏密钥: weflow-cli fav set-key <64位hex密钥>'))
|
|
2545
|
+
console.log(chalk.gray(' 或设置全库 passphrase: weflow-cli fav set-key --passphrase <64位hex>'))
|
|
2546
|
+
}
|
|
2547
|
+
if (favReason === 'path') {
|
|
2548
|
+
console.log(chalk.gray(' 可手动指定: weflow-cli config set favDbPath <favorite.db 路径>'))
|
|
2549
|
+
}
|
|
2550
|
+
console.log('')
|
|
2530
2551
|
process.exit(1)
|
|
2531
2552
|
}
|
|
2532
2553
|
|
|
@@ -5362,6 +5383,12 @@ program
|
|
|
5362
5383
|
messageDatabase: !!configService.get('ntDbPath') && existsSync(configService.get('ntDbPath')),
|
|
5363
5384
|
momentsDatabase: !!configService.get('snsDbPath') && existsSync(configService.get('snsDbPath')),
|
|
5364
5385
|
favoritesDatabase: !!configService.get('favDbPath') && existsSync(configService.get('favDbPath')),
|
|
5386
|
+
// `favoritesDatabase` only says the file is there. Reading it also
|
|
5387
|
+
// needs a key, and a caller that took the former for "usable" would
|
|
5388
|
+
// attempt favorites and always fail.
|
|
5389
|
+
favoritesReady: !!configService.get('favDbPath')
|
|
5390
|
+
&& existsSync(configService.get('favDbPath'))
|
|
5391
|
+
&& (!!configService.get('favKey') || !!configService.get('favPassphrase')),
|
|
5365
5392
|
},
|
|
5366
5393
|
assistant: {
|
|
5367
5394
|
engine,
|
package/dist/bin/weflow-cli.js
CHANGED
|
@@ -658,6 +658,8 @@ program
|
|
|
658
658
|
console.log(chalk.gray(' 尝试从 WeFlow 桌面版配置读取...'));
|
|
659
659
|
let extractedKey = opts.testMissingKeys ? '' : tryReadWeFlowKey();
|
|
660
660
|
let linuxNtKey = '';
|
|
661
|
+
/** 密钥是本次从 hook 捕获的, 还是沿用已有的 - 摘要要说准, 不能一律报"获取成功" */
|
|
662
|
+
let keyCaptured = false;
|
|
661
663
|
if (extractedKey) {
|
|
662
664
|
console.log(chalk.green(' ✓ 从 WeFlow 配置读取到密钥'));
|
|
663
665
|
}
|
|
@@ -744,12 +746,18 @@ program
|
|
|
744
746
|
});
|
|
745
747
|
if (keyResult.success && keyResult.key) {
|
|
746
748
|
extractedKey = keyResult.key;
|
|
749
|
+
keyCaptured = true;
|
|
747
750
|
}
|
|
748
751
|
else {
|
|
749
|
-
// hook 只在登录瞬间触发; 微信已登录时捕获不到 →
|
|
750
|
-
|
|
752
|
+
// hook 只在登录瞬间触发; 微信已登录时捕获不到 → 回退到已有密钥。
|
|
753
|
+
// decryptKey 也必须算数: init 成功时写入的就是它, 而 favPassphrase
|
|
754
|
+
// 仅在收藏库存在且验证通过时才会被设置。只看 favPassphrase 会让
|
|
755
|
+
// `init --refresh` 在一把完全可用的密钥面前报「密钥提取失败」——
|
|
756
|
+
// 而那正是 CLI 自己在微信迁移后建议用户执行的命令。
|
|
757
|
+
const fallback = configService.get('favPassphrase') || configService.get('decryptKey') || '';
|
|
751
758
|
if (fallback) {
|
|
752
|
-
console.log(chalk.gray(' Hook 未捕获到密钥 (微信已登录时不再触发密钥函数)
|
|
759
|
+
console.log(chalk.gray(' Hook 未捕获到密钥 (微信已登录时不再触发密钥函数)'));
|
|
760
|
+
console.log(chalk.gray(' 检测到已配置的密钥, 沿用它并重新验证各库...'));
|
|
753
761
|
extractedKey = fallback;
|
|
754
762
|
}
|
|
755
763
|
else {
|
|
@@ -764,7 +772,10 @@ program
|
|
|
764
772
|
}
|
|
765
773
|
if (extractedKey) {
|
|
766
774
|
configService.set('decryptKey', extractedKey);
|
|
767
|
-
|
|
775
|
+
if (keyCaptured)
|
|
776
|
+
console.log(chalk.green('\n✓ 密钥获取成功!'));
|
|
777
|
+
else
|
|
778
|
+
console.log(chalk.green('\n✓ 已沿用配置中的密钥 (本次未重新捕获)'));
|
|
768
779
|
}
|
|
769
780
|
else {
|
|
770
781
|
console.log(chalk.green('\n✓ NT 密钥配置完成!'));
|
|
@@ -2588,14 +2599,26 @@ favCmd
|
|
|
2588
2599
|
console.log(chalk.red(`\n❌ 数据库连接失败: ${conn.error}\n`));
|
|
2589
2600
|
process.exit(1);
|
|
2590
2601
|
}
|
|
2591
|
-
|
|
2602
|
+
const favReason = chatService.favUnavailableReason();
|
|
2603
|
+
if (favReason) {
|
|
2604
|
+
const detail = {
|
|
2605
|
+
channel: '当前数据通道不支持收藏查询 (需 4.x NT 连接)',
|
|
2606
|
+
path: '未找到收藏数据库 (favorite.db),请确认微信已登录并产生过收藏',
|
|
2607
|
+
key: '收藏数据库已找到,但缺少密钥',
|
|
2608
|
+
}[favReason];
|
|
2592
2609
|
if (opts.json) {
|
|
2593
|
-
console.log(JSON.stringify({ success: false, code: 'FAVORITES_UNAVAILABLE', error:
|
|
2610
|
+
console.log(JSON.stringify({ success: false, code: 'FAVORITES_UNAVAILABLE', reason: favReason, error: detail }));
|
|
2594
2611
|
process.exit(1);
|
|
2595
2612
|
}
|
|
2596
|
-
console.log(chalk.red(
|
|
2597
|
-
|
|
2598
|
-
|
|
2613
|
+
console.log(chalk.red(`\n❌ ${detail}`));
|
|
2614
|
+
if (favReason === 'key') {
|
|
2615
|
+
console.log(chalk.gray(' 请先配置收藏密钥: weflow-cli fav set-key <64位hex密钥>'));
|
|
2616
|
+
console.log(chalk.gray(' 或设置全库 passphrase: weflow-cli fav set-key --passphrase <64位hex>'));
|
|
2617
|
+
}
|
|
2618
|
+
if (favReason === 'path') {
|
|
2619
|
+
console.log(chalk.gray(' 可手动指定: weflow-cli config set favDbPath <favorite.db 路径>'));
|
|
2620
|
+
}
|
|
2621
|
+
console.log('');
|
|
2599
2622
|
process.exit(1);
|
|
2600
2623
|
}
|
|
2601
2624
|
let favType;
|
|
@@ -5563,6 +5586,12 @@ program
|
|
|
5563
5586
|
messageDatabase: !!configService.get('ntDbPath') && existsSync(configService.get('ntDbPath')),
|
|
5564
5587
|
momentsDatabase: !!configService.get('snsDbPath') && existsSync(configService.get('snsDbPath')),
|
|
5565
5588
|
favoritesDatabase: !!configService.get('favDbPath') && existsSync(configService.get('favDbPath')),
|
|
5589
|
+
// `favoritesDatabase` only says the file is there. Reading it also
|
|
5590
|
+
// needs a key, and a caller that took the former for "usable" would
|
|
5591
|
+
// attempt favorites and always fail.
|
|
5592
|
+
favoritesReady: !!configService.get('favDbPath')
|
|
5593
|
+
&& existsSync(configService.get('favDbPath'))
|
|
5594
|
+
&& (!!configService.get('favKey') || !!configService.get('favPassphrase')),
|
|
5566
5595
|
},
|
|
5567
5596
|
assistant: {
|
|
5568
5597
|
engine,
|