weflow-cli 1.13.0 → 1.14.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/CHANGELOG.md +33 -0
- package/OPERATIONS.md +1 -0
- package/README.md +6 -0
- package/bin/weflow-cli.ts +25 -2
- package/dist/bin/weflow-cli.js +28 -2
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/src/services/configService.d.ts +3 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +3 -2
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/wechatClient.d.ts +8 -0
- package/dist/src/services/wechatClient.d.ts.map +1 -1
- package/dist/src/services/wechatClient.js +14 -2
- package/dist/src/services/wechatClient.js.map +1 -1
- package/docs/DECISIONS.md +30 -0
- package/package.json +1 -1
- package/scripts/_utils.py +21 -2
- package/scripts/daily_mail.py +467 -0
- package/scripts/daily_task.ps1 +20 -0
- package/scripts/jev_client.py +136 -2
- package/src/services/configService.ts +8 -3
- package/src/services/wechatClient.ts +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,39 @@ purpose** - a hand-written "latest is X" is exactly the claim that goes stale he
|
|
|
17
17
|
|
|
18
18
|
All notable user-facing changes are recorded here. This project follows [Semantic Versioning](https://semver.org/).
|
|
19
19
|
|
|
20
|
+
## 1.14.0
|
|
21
|
+
|
|
22
|
+
- **A daily report you can receive by mail, cut down to the topics you actually read.**
|
|
23
|
+
`daily --email` mails one report with two sections - AI and 学术 - dropping 新闻 / 政治 / 投资 /
|
|
24
|
+
文学, and each section opens with an AI-written digest over that section's titles. Every article
|
|
25
|
+
keeps the one-line analysis `biz_daily` already produced (no extra calls), its source and time,
|
|
26
|
+
its link, and any GitHub repository mentioned in the body (14 of the 110 articles on 2026-10-18).
|
|
27
|
+
Recipient comes from `--mail-to`, then config `dailyMailTo`, then the SMTP account itself.
|
|
28
|
+
- **Each topic is clustered again before it is printed.** 68 AI articles in one list is a wall;
|
|
29
|
+
an AI pass groups them into 4-6 named sections (模型与产品 / 智能体与工程 / 科研工具 …), each with a
|
|
30
|
+
coloured dot. Tags were not usable: 68 articles carried 382 distinct tags, the most frequent
|
|
31
|
+
appearing 6 times. A degenerate answer (everything in one group) is retried with a stricter
|
|
32
|
+
instruction, because it silently collapses the layout back to a single wall - observed on
|
|
33
|
+
2026-10-21, where 91 articles ended up in one group.
|
|
34
|
+
- **Uniform cards, and no colour without a label.** Every article is the same card; only the badge
|
|
35
|
+
colour and summary length differ, so a section never looks like two layouts mixed together.
|
|
36
|
+
Relevance is a small tag that *says* 高 instead of a colour the reader has to guess.
|
|
37
|
+
- **The scheduled run mails the report once a day.** `scripts/daily_task.ps1` calls `daily_mail.py`
|
|
38
|
+
only after the day pass produced the report - the noon pass generates but does not mail, so the
|
|
39
|
+
inbox gets one complete day instead of a half day followed by a repeat. A mail failure is logged
|
|
40
|
+
separately and does not turn a successful day into a failed one.
|
|
41
|
+
|
|
42
|
+
## 1.13.1
|
|
43
|
+
|
|
44
|
+
- **Fix: a data directory containing non-ASCII characters was decoded wrong, so `check --report`
|
|
45
|
+
reported the wrong verdict.** Windows PowerShell writes to stdout in the console code page
|
|
46
|
+
(GBK on a Chinese system) while Node reads UTF-8, so `D:\微信\xwechat_files` arrived as
|
|
47
|
+
`D:\��\xwechat_files`. Nothing crashed - the comparison against the configured path simply
|
|
48
|
+
came out unequal, and the report told the user "you may be reading another account's database"
|
|
49
|
+
when they were reading exactly the right one. The PowerShell side now emits base64 (UTF-16LE)
|
|
50
|
+
and the Node side decodes it, so the code page cannot touch it. Verified by round-tripping a
|
|
51
|
+
Chinese path through the real `powershell.exe` call.
|
|
52
|
+
|
|
20
53
|
## 1.13.0
|
|
21
54
|
|
|
22
55
|
- **`sessions` shows readable previews instead of raw message bytes.** The preview column used to
|
package/OPERATIONS.md
CHANGED
|
@@ -1609,6 +1609,7 @@ weflow-cli check --report
|
|
|
1609
1609
|
|
|
1610
1610
|
| 现象 | 处理 |
|
|
1611
1611
|
| --- | --- |
|
|
1612
|
+
| `Error: Cannot find module …\npm\node_modules\weflow-cli\cli.cjs` | 全局安装**残缺**(目录里只剩零散依赖,没有 `package.json`、没有 `cli.cjs`)。`npm ls -g weflow-cli` 显示版本号为空就是它。删掉 `%APPDATA%\npm\node_modules\weflow-cli`,再 `npm i -g weflow-cli@latest`。2026-10-19 在本机遇到过一次,重装即恢复 |
|
|
1612
1613
|
| 找不到数据目录 | 先 `init --path`,再 `--search-drives`,最后 `--full-scan`。 |
|
|
1613
1614
|
| `Python not found` | 安装 Python 并确保当前 PowerShell 能执行 `python --version`。 |
|
|
1614
1615
|
| 缺少 `sqlcipher3` 等依赖 | 用同一个 Python 执行 `python -m pip install -r requirements.txt`,再 `weflow-cli check`。 |
|
package/README.md
CHANGED
|
@@ -118,6 +118,12 @@ npm install -g weflow-cli
|
|
|
118
118
|
weflow-cli check
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
> Windows 上如果这条报 **「无法加载文件 …\npm\weflow-cli.ps1,未进行数字签名」**,拦住它的是
|
|
122
|
+
> PowerShell 的执行策略,不是包本身。两条路,任选:
|
|
123
|
+
>
|
|
124
|
+
> - 马上能用:换成 `weflow-cli.cmd check`(`.cmd` 那份包装不走 PowerShell 策略);
|
|
125
|
+
> - 一次性解决:`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`,然后重开终端。
|
|
126
|
+
|
|
121
127
|
安装标准 Windows 4.x 工作流所需的 Python 依赖:
|
|
122
128
|
|
|
123
129
|
```powershell
|
package/bin/weflow-cli.ts
CHANGED
|
@@ -1169,13 +1169,13 @@ const configurableKeys = [
|
|
|
1169
1169
|
'ntDbPath', 'ntKey', 'ntSalt', 'contactDbPath', 'contactKey', 'contactSalt',
|
|
1170
1170
|
'vaultRepo', 'aiEngine', 'aiBaseUrl', 'aiModel', 'deepseekApiKey', 'typesafeApiKey',
|
|
1171
1171
|
'dashscopeApiKey', 'favPassphrase',
|
|
1172
|
-
'judgeProvider', 'openrouterApiKey', 'openrouterJudgeModel',
|
|
1172
|
+
'judgeProvider', 'openrouterApiKey', 'openrouterJudgeModel', 'judgeLocalUrl',
|
|
1173
1173
|
'wereadApiKey',
|
|
1174
1174
|
'assistantPrivacy', 'assistantWhitelist', 'assistantGroupWhitelist',
|
|
1175
1175
|
'assistantGroupRequireMention', 'assistantFastRoute', 'assistantPanelUser', 'quickReplyContacts',
|
|
1176
1176
|
'skillDirs', 'skillDisabled',
|
|
1177
1177
|
'dailySources', 'dailySourceCategories',
|
|
1178
|
-
'dailyExcludeTopics', 'dailyAiEnabled',
|
|
1178
|
+
'dailyExcludeTopics', 'dailyAiEnabled', 'dailyMailTo',
|
|
1179
1179
|
'wechatMediaDownload',
|
|
1180
1180
|
'emoticonSeed',
|
|
1181
1181
|
] as const
|
|
@@ -6359,12 +6359,15 @@ program
|
|
|
6359
6359
|
.option('--no-summary', '只判断不生成:不调 LLM 写摘要/标签/简报(主题与相关度仍由 Jev 判断),无需 DeepSeek key')
|
|
6360
6360
|
.option('--prefetch-bodies [n]', '抓取之前先并发把当天正文抓进缓存(n=并发数,默认 8):抓取阶段于是全部命中缓存、不再逐篇 sleep 8-12 秒(450 篇约 75 分钟 → 秒级)。对上游的请求次数不变,只是压进更短的时间窗')
|
|
6361
6361
|
.option('--json', '输出机器可读的最终结果;运行日志写入 stderr')
|
|
6362
|
+
.option('--email', '生成后把日报按主题(AI / 文学 / 学术)发到邮箱(每个主题附一段 AI 综述,逐篇给要点)')
|
|
6363
|
+
.option('--mail-to <address>', '邮件收件人;缺省用配置 dailyMailTo,再缺省发给自己')
|
|
6362
6364
|
.action(async (opts) => {
|
|
6363
6365
|
const { spawn } = await import('child_process')
|
|
6364
6366
|
const { existsSync, statSync } = await import('fs')
|
|
6365
6367
|
const pkgRoot = resolvePackageRoot()
|
|
6366
6368
|
const pipeline = join(pkgRoot, 'scripts', 'pipeline.py')
|
|
6367
6369
|
const bizDaily = join(pkgRoot, 'scripts', 'biz_daily.py')
|
|
6370
|
+
const dailyMail = join(pkgRoot, 'scripts', 'daily_mail.py')
|
|
6368
6371
|
|
|
6369
6372
|
const now = new Date()
|
|
6370
6373
|
const localDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
|
@@ -6489,6 +6492,24 @@ program
|
|
|
6489
6492
|
const code = await runPipeline(date)
|
|
6490
6493
|
if (code === 0) {
|
|
6491
6494
|
const complete = isComplete(date)
|
|
6495
|
+
// 邮件只依赖 `.articles.json`(逐篇摘要已经由 biz_daily 写好),所以放在生成之后、
|
|
6496
|
+
// 与"是否完整"无关:不完整时也先把已有的发出去,用户不至于什么都收不到。
|
|
6497
|
+
let mail: { ok: boolean; code: number } | null = null
|
|
6498
|
+
if (opts.email) {
|
|
6499
|
+
const mailArgs = [dailyMail, '--date', date]
|
|
6500
|
+
if (opts.mailTo) mailArgs.push('--to', String(opts.mailTo))
|
|
6501
|
+
dailyLog(chalk.cyan('\n📧 正在按主题整理并发邮件...'))
|
|
6502
|
+
mail = await new Promise((resolve) => {
|
|
6503
|
+
const child = spawn(getPythonCommand(), mailArgs, {
|
|
6504
|
+
windowsHide: true,
|
|
6505
|
+
stdio: 'inherit',
|
|
6506
|
+
env: pythonProcessEnv(apiKey),
|
|
6507
|
+
})
|
|
6508
|
+
child.on('error', () => resolve({ ok: false, code: 1 }))
|
|
6509
|
+
child.on('exit', (c) => resolve({ ok: (c || 0) === 0, code: c || 0 }))
|
|
6510
|
+
})
|
|
6511
|
+
if (mail && !mail.ok) dailyLog(chalk.yellow(' ! 邮件没发出去(日报本身已生成,见上面的报错)'))
|
|
6512
|
+
}
|
|
6492
6513
|
if (opts.json) {
|
|
6493
6514
|
console.log(JSON.stringify({
|
|
6494
6515
|
success: complete,
|
|
@@ -6497,11 +6518,13 @@ program
|
|
|
6497
6518
|
aiEnabled: !noAi,
|
|
6498
6519
|
output: `output/biz-daily/${date}`,
|
|
6499
6520
|
artifacts: artifactStatus(date),
|
|
6521
|
+
mail,
|
|
6500
6522
|
}, null, 2))
|
|
6501
6523
|
if (!complete) process.exit(1)
|
|
6502
6524
|
return
|
|
6503
6525
|
}
|
|
6504
6526
|
console.log(chalk.green(`\n✓ 日报已生成: output/biz-daily/${date}/`))
|
|
6527
|
+
if (mail) console.log(chalk.green(`✓ 邮件已发送${opts.mailTo ? ` → ${opts.mailTo}` : ''}`))
|
|
6505
6528
|
console.log(chalk.gray(` HTML 阅读器: weflow-cli daily-server --date ${date}`))
|
|
6506
6529
|
} else {
|
|
6507
6530
|
if (opts.json) console.log(JSON.stringify({ success: false, code: 'DAILY_PIPELINE_FAILED', date, aiEnabled: !noAi }))
|
package/dist/bin/weflow-cli.js
CHANGED
|
@@ -1141,13 +1141,13 @@ const configurableKeys = [
|
|
|
1141
1141
|
'ntDbPath', 'ntKey', 'ntSalt', 'contactDbPath', 'contactKey', 'contactSalt',
|
|
1142
1142
|
'vaultRepo', 'aiEngine', 'aiBaseUrl', 'aiModel', 'deepseekApiKey', 'typesafeApiKey',
|
|
1143
1143
|
'dashscopeApiKey', 'favPassphrase',
|
|
1144
|
-
'judgeProvider', 'openrouterApiKey', 'openrouterJudgeModel',
|
|
1144
|
+
'judgeProvider', 'openrouterApiKey', 'openrouterJudgeModel', 'judgeLocalUrl',
|
|
1145
1145
|
'wereadApiKey',
|
|
1146
1146
|
'assistantPrivacy', 'assistantWhitelist', 'assistantGroupWhitelist',
|
|
1147
1147
|
'assistantGroupRequireMention', 'assistantFastRoute', 'assistantPanelUser', 'quickReplyContacts',
|
|
1148
1148
|
'skillDirs', 'skillDisabled',
|
|
1149
1149
|
'dailySources', 'dailySourceCategories',
|
|
1150
|
-
'dailyExcludeTopics', 'dailyAiEnabled',
|
|
1150
|
+
'dailyExcludeTopics', 'dailyAiEnabled', 'dailyMailTo',
|
|
1151
1151
|
'wechatMediaDownload',
|
|
1152
1152
|
'emoticonSeed',
|
|
1153
1153
|
];
|
|
@@ -6627,12 +6627,15 @@ program
|
|
|
6627
6627
|
.option('--no-summary', '只判断不生成:不调 LLM 写摘要/标签/简报(主题与相关度仍由 Jev 判断),无需 DeepSeek key')
|
|
6628
6628
|
.option('--prefetch-bodies [n]', '抓取之前先并发把当天正文抓进缓存(n=并发数,默认 8):抓取阶段于是全部命中缓存、不再逐篇 sleep 8-12 秒(450 篇约 75 分钟 → 秒级)。对上游的请求次数不变,只是压进更短的时间窗')
|
|
6629
6629
|
.option('--json', '输出机器可读的最终结果;运行日志写入 stderr')
|
|
6630
|
+
.option('--email', '生成后把日报按主题(AI / 文学 / 学术)发到邮箱(每个主题附一段 AI 综述,逐篇给要点)')
|
|
6631
|
+
.option('--mail-to <address>', '邮件收件人;缺省用配置 dailyMailTo,再缺省发给自己')
|
|
6630
6632
|
.action(async (opts) => {
|
|
6631
6633
|
const { spawn } = await import('child_process');
|
|
6632
6634
|
const { existsSync, statSync } = await import('fs');
|
|
6633
6635
|
const pkgRoot = resolvePackageRoot();
|
|
6634
6636
|
const pipeline = join(pkgRoot, 'scripts', 'pipeline.py');
|
|
6635
6637
|
const bizDaily = join(pkgRoot, 'scripts', 'biz_daily.py');
|
|
6638
|
+
const dailyMail = join(pkgRoot, 'scripts', 'daily_mail.py');
|
|
6636
6639
|
const now = new Date();
|
|
6637
6640
|
const localDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
6638
6641
|
const isoDate = (when) => `${when.getFullYear()}-${String(when.getMonth() + 1).padStart(2, '0')}-${String(when.getDate()).padStart(2, '0')}`;
|
|
@@ -6761,6 +6764,26 @@ program
|
|
|
6761
6764
|
const code = await runPipeline(date);
|
|
6762
6765
|
if (code === 0) {
|
|
6763
6766
|
const complete = isComplete(date);
|
|
6767
|
+
// 邮件只依赖 `.articles.json`(逐篇摘要已经由 biz_daily 写好),所以放在生成之后、
|
|
6768
|
+
// 与"是否完整"无关:不完整时也先把已有的发出去,用户不至于什么都收不到。
|
|
6769
|
+
let mail = null;
|
|
6770
|
+
if (opts.email) {
|
|
6771
|
+
const mailArgs = [dailyMail, '--date', date];
|
|
6772
|
+
if (opts.mailTo)
|
|
6773
|
+
mailArgs.push('--to', String(opts.mailTo));
|
|
6774
|
+
dailyLog(chalk.cyan('\n📧 正在按主题整理并发邮件...'));
|
|
6775
|
+
mail = await new Promise((resolve) => {
|
|
6776
|
+
const child = spawn(getPythonCommand(), mailArgs, {
|
|
6777
|
+
windowsHide: true,
|
|
6778
|
+
stdio: 'inherit',
|
|
6779
|
+
env: pythonProcessEnv(apiKey),
|
|
6780
|
+
});
|
|
6781
|
+
child.on('error', () => resolve({ ok: false, code: 1 }));
|
|
6782
|
+
child.on('exit', (c) => resolve({ ok: (c || 0) === 0, code: c || 0 }));
|
|
6783
|
+
});
|
|
6784
|
+
if (mail && !mail.ok)
|
|
6785
|
+
dailyLog(chalk.yellow(' ! 邮件没发出去(日报本身已生成,见上面的报错)'));
|
|
6786
|
+
}
|
|
6764
6787
|
if (opts.json) {
|
|
6765
6788
|
console.log(JSON.stringify({
|
|
6766
6789
|
success: complete,
|
|
@@ -6769,12 +6792,15 @@ program
|
|
|
6769
6792
|
aiEnabled: !noAi,
|
|
6770
6793
|
output: `output/biz-daily/${date}`,
|
|
6771
6794
|
artifacts: artifactStatus(date),
|
|
6795
|
+
mail,
|
|
6772
6796
|
}, null, 2));
|
|
6773
6797
|
if (!complete)
|
|
6774
6798
|
process.exit(1);
|
|
6775
6799
|
return;
|
|
6776
6800
|
}
|
|
6777
6801
|
console.log(chalk.green(`\n✓ 日报已生成: output/biz-daily/${date}/`));
|
|
6802
|
+
if (mail)
|
|
6803
|
+
console.log(chalk.green(`✓ 邮件已发送${opts.mailTo ? ` → ${opts.mailTo}` : ''}`));
|
|
6778
6804
|
console.log(chalk.gray(` HTML 阅读器: weflow-cli daily-server --date ${date}`));
|
|
6779
6805
|
}
|
|
6780
6806
|
else {
|