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 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 }))
@@ -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 {