weflow-cli 1.13.1 → 1.14.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/CHANGELOG.md +38 -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/docs/DECISIONS.md +30 -0
- package/package.json +1 -1
- package/scripts/_utils.py +21 -2
- package/scripts/daily_mail.py +472 -0
- package/scripts/daily_task.ps1 +45 -0
- package/scripts/jev_client.py +136 -2
- package/scripts/semantic_search.py +9 -1
- package/src/services/configService.ts +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,44 @@ 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.1
|
|
21
|
+
|
|
22
|
+
- **The daily run now keeps the retrieval indexes fresh.** On 2026-10-22 the semantic index was
|
|
23
|
+
still at 10-15 and the phrase index at 10-17, so `search` could not find anything newer than a
|
|
24
|
+
week ago - nothing had ever refreshed them. After the day's report is written, the wrapper runs
|
|
25
|
+
`semantic_search.py update` (embeds only what is missing: 4,061 new records on that first run)
|
|
26
|
+
and `semantic_search.py phrase-build` (local FTS, no API cost). Each has its own log and neither
|
|
27
|
+
can fail the day.
|
|
28
|
+
- **The noon pass mails too, and says it is half a day.** It used to generate silently, which read
|
|
29
|
+
as "it never ran" - the only evidence was a log file. The noon email is titled
|
|
30
|
+
「…(截至中午)」 and states that the complete version arrives the next morning, so the two daily
|
|
31
|
+
emails cannot be mistaken for duplicates.
|
|
32
|
+
- **`phrase-build` no longer fails when `VACUUM` cannot run.** `VACUUM` needs a temp file, and in a
|
|
33
|
+
restricted environment it reports `unable to open database file` *after* the index has been
|
|
34
|
+
written successfully - that turned a good rebuild into exit code 1.
|
|
35
|
+
|
|
36
|
+
## 1.14.0
|
|
37
|
+
|
|
38
|
+
- **A daily report you can receive by mail, cut down to the topics you actually read.**
|
|
39
|
+
`daily --email` mails one report with two sections - AI and 学术 - dropping 新闻 / 政治 / 投资 /
|
|
40
|
+
文学, and each section opens with an AI-written digest over that section's titles. Every article
|
|
41
|
+
keeps the one-line analysis `biz_daily` already produced (no extra calls), its source and time,
|
|
42
|
+
its link, and any GitHub repository mentioned in the body (14 of the 110 articles on 2026-10-18).
|
|
43
|
+
Recipient comes from `--mail-to`, then config `dailyMailTo`, then the SMTP account itself.
|
|
44
|
+
- **Each topic is clustered again before it is printed.** 68 AI articles in one list is a wall;
|
|
45
|
+
an AI pass groups them into 4-6 named sections (模型与产品 / 智能体与工程 / 科研工具 …), each with a
|
|
46
|
+
coloured dot. Tags were not usable: 68 articles carried 382 distinct tags, the most frequent
|
|
47
|
+
appearing 6 times. A degenerate answer (everything in one group) is retried with a stricter
|
|
48
|
+
instruction, because it silently collapses the layout back to a single wall - observed on
|
|
49
|
+
2026-10-21, where 91 articles ended up in one group.
|
|
50
|
+
- **Uniform cards, and no colour without a label.** Every article is the same card; only the badge
|
|
51
|
+
colour and summary length differ, so a section never looks like two layouts mixed together.
|
|
52
|
+
Relevance is a small tag that *says* 高 instead of a colour the reader has to guess.
|
|
53
|
+
- **The scheduled run mails the report once a day.** `scripts/daily_task.ps1` calls `daily_mail.py`
|
|
54
|
+
only after the day pass produced the report - the noon pass generates but does not mail, so the
|
|
55
|
+
inbox gets one complete day instead of a half day followed by a repeat. A mail failure is logged
|
|
56
|
+
separately and does not turn a successful day into a failed one.
|
|
57
|
+
|
|
20
58
|
## 1.13.1
|
|
21
59
|
|
|
22
60
|
- **Fix: a data directory containing non-ASCII characters was decoded wrong, so `check --report`
|
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 {
|