weflow-cli 1.16.0 → 1.17.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.
Files changed (97) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +16 -4
  3. package/bin/weflow-cli.ts +348 -6
  4. package/dist/bin/weflow-cli.js +381 -8
  5. package/dist/bin/weflow-cli.js.map +1 -1
  6. package/dist/src/core/ntCore.d.ts +47 -0
  7. package/dist/src/core/ntCore.d.ts.map +1 -1
  8. package/dist/src/core/ntCore.js +24 -0
  9. package/dist/src/core/ntCore.js.map +1 -1
  10. package/dist/src/services/assistantService.d.ts +8 -1
  11. package/dist/src/services/assistantService.d.ts.map +1 -1
  12. package/dist/src/services/assistantService.js +47 -8
  13. package/dist/src/services/assistantService.js.map +1 -1
  14. package/dist/src/services/assistantTools.d.ts +8 -0
  15. package/dist/src/services/assistantTools.d.ts.map +1 -1
  16. package/dist/src/services/assistantTools.js +67 -11
  17. package/dist/src/services/assistantTools.js.map +1 -1
  18. package/dist/src/services/configService.d.ts.map +1 -1
  19. package/dist/src/services/configService.js +3 -2
  20. package/dist/src/services/configService.js.map +1 -1
  21. package/dist/src/services/cursorProbe.d.ts +44 -0
  22. package/dist/src/services/cursorProbe.d.ts.map +1 -0
  23. package/dist/src/services/cursorProbe.js +183 -0
  24. package/dist/src/services/cursorProbe.js.map +1 -0
  25. package/dist/src/services/qchatImport.d.ts +89 -0
  26. package/dist/src/services/qchatImport.d.ts.map +1 -0
  27. package/dist/src/services/qchatImport.js +273 -0
  28. package/dist/src/services/qchatImport.js.map +1 -0
  29. package/dist/src/services/setupVenv.d.ts +43 -0
  30. package/dist/src/services/setupVenv.d.ts.map +1 -0
  31. package/dist/src/services/setupVenv.js +198 -0
  32. package/dist/src/services/setupVenv.js.map +1 -0
  33. package/dist/src/utils/python.d.ts +10 -0
  34. package/dist/src/utils/python.d.ts.map +1 -1
  35. package/dist/src/utils/python.js +27 -4
  36. package/dist/src/utils/python.js.map +1 -1
  37. package/docs/DATA_CONTRACT.md +25 -0
  38. package/docs/DECISIONS.md +92 -30
  39. package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +365 -365
  40. package/docs/HEALTH-CHECK.md +104 -104
  41. package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -310
  42. package/docs/NEXT_ACTIONS.md +236 -236
  43. package/docs/NPM-PUBLISH.md +130 -130
  44. package/docs/POSITIONING.md +68 -68
  45. package/docs/PROJECT_STATE.md +6 -3
  46. package/docs/REPLAY.md +169 -0
  47. package/docs/SYNC_CONTRACT.md +197 -180
  48. package/package.json +89 -88
  49. package/requirements-3x.txt +4 -4
  50. package/requirements-voice.txt +15 -15
  51. package/resources/js/graph3d/NOTICE.txt +35 -35
  52. package/resources/js/graph3d/d3-binarytree.min.js +2 -2
  53. package/resources/js/graph3d/d3-dispatch.min.js +2 -2
  54. package/resources/js/graph3d/d3-force-3d.min.js +2 -2
  55. package/resources/js/graph3d/d3-octree.min.js +2 -2
  56. package/resources/js/graph3d/d3-quadtree.min.js +2 -2
  57. package/resources/js/graph3d/d3-timer.min.js +2 -2
  58. package/resources/js/graph3d/three.min.js +6 -6
  59. package/resources/js/graph3d/viewer2d.js +550 -448
  60. package/resources/panel/quick-menu.cjs +197 -197
  61. package/scripts/_utils.py +1338 -1338
  62. package/scripts/article_notes.py +423 -423
  63. package/scripts/chat_notes.py +86 -3
  64. package/scripts/check_readme_commands.py +427 -426
  65. package/scripts/contact_schema.py +339 -339
  66. package/scripts/create_reading_notes.py +11 -11
  67. package/scripts/daily_task.ps1 +213 -213
  68. package/scripts/fav_notes.py +321 -321
  69. package/scripts/graph_2d.py +156 -151
  70. package/scripts/graph_3d.py +672 -646
  71. package/scripts/health_check.py +473 -473
  72. package/scripts/imported_chats.py +68 -0
  73. package/scripts/install_tasks.ps1 +105 -105
  74. package/scripts/leads.py +179 -179
  75. package/scripts/network.py +206 -206
  76. package/scripts/nt_decrypt.py +135 -1
  77. package/scripts/on_this_day.py +178 -178
  78. package/scripts/opinion_timeline.py +138 -138
  79. package/scripts/promises.py +185 -185
  80. package/scripts/replay.py +803 -0
  81. package/scripts/run_hidden.vbs +25 -0
  82. package/scripts/semantic_search.py +73 -5
  83. package/scripts/user_notes.py +315 -315
  84. package/scripts/voice_transcribe.py +139 -139
  85. package/scripts/watch_issues.py +212 -212
  86. package/scripts/watch_mail.py +131 -131
  87. package/src/core/ntCore.ts +59 -0
  88. package/src/services/assistantService.ts +48 -8
  89. package/src/services/assistantTools.ts +73 -10
  90. package/src/services/configService.ts +3 -2
  91. package/src/services/cursorProbe.ts +222 -0
  92. package/src/services/qchatImport.ts +327 -0
  93. package/src/services/setupVenv.ts +238 -0
  94. package/src/services/syncService.ts +363 -363
  95. package/src/services/syncState.ts +275 -275
  96. package/src/services/wechatMedia.ts +166 -166
  97. package/src/utils/python.ts +33 -4
@@ -0,0 +1,25 @@
1
+ ' Silent launcher for the WeFlow daily task.
2
+ '
3
+ ' Why this file exists: a scheduled action that runs "powershell.exe -File ..." starts a
4
+ ' CONSOLE program, so Windows creates a console window before PowerShell can hide it - you
5
+ ' see a black box appear, and it is especially intrusive over a full-screen game. wscript.exe
6
+ ' is a GUI process with no console at all, and WshShell.Run with window style 0 keeps the
7
+ ' child hidden too.
8
+ '
9
+ ' Arguments are forwarded verbatim, so "run_hidden.vbs noon" is the noon pass.
10
+ '
11
+ ' ASCII only on purpose: Windows Script Host reads this file with the ANSI code page.
12
+
13
+ Dim fso, here, script, cmd, sh
14
+ Set fso = CreateObject("Scripting.FileSystemObject")
15
+ here = fso.GetParentFolderName(WScript.ScriptFullName)
16
+ script = fso.BuildPath(here, "daily_task.ps1")
17
+
18
+ cmd = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & script & """"
19
+ If WScript.Arguments.Count > 0 Then
20
+ cmd = cmd & " " & WScript.Arguments(0)
21
+ End If
22
+
23
+ Set sh = CreateObject("WScript.Shell")
24
+ ' 0 = hidden window, True = wait for the child so the task's last result reflects the run.
25
+ sh.Run cmd, 0, True
@@ -268,6 +268,36 @@ def collect_chat_messages(conns, name_map, days=90, per_talker=500):
268
268
  return items
269
269
 
270
270
 
271
+ def collect_imported_chat_items(days=90):
272
+ """导入的 QQ 会话 → 与微信聊天同形状的索引记录(--include-imports)。
273
+
274
+ 形状必须与 collect_chat_messages 逐字段一致(id/type/talker/sender/time/text),
275
+ 否则同一条记录在两半索引里语义不同。id 带 qq: 段:与微信记录的
276
+ chat:<talker>:<ts>:<rid> 天然不撞,也永远可分辨来源。文本阈值(>=10 字)与
277
+ 截断(500 字)同微信侧——索引是要花钱嵌的,两侧口径不同就是两种相关性。
278
+ """
279
+ import imported_chats
280
+
281
+ items = []
282
+ for sess in imported_chats.sessions():
283
+ meta = sess.get('session') or {}
284
+ talker = 'QQ-%s' % (meta.get('name') or meta.get('id') or '未命名')
285
+ for m in imported_chats.session_messages(sess, days=days):
286
+ text = (m.get('parsedContent') or '').strip()
287
+ if len(text) < 10:
288
+ continue
289
+ dt = datetime.fromtimestamp(m.get('createTime') or 0, tz=TZ)
290
+ items.append({
291
+ 'id': 'chat:qq:%s:%s' % (meta.get('id') or '', m.get('serverId') or ''),
292
+ 'type': 'chat',
293
+ 'talker': talker,
294
+ 'sender': m.get('senderName') or '',
295
+ 'time': dt.strftime('%Y-%m-%d %H:%M'),
296
+ 'text': text[:500],
297
+ })
298
+ return items
299
+
300
+
271
301
  _ARTICLE_BOILERPLATE = frozenset({
272
302
  '在小说阅读器读本章', '去阅读', '在公众号小说中沉浸阅读',
273
303
  })
@@ -396,7 +426,7 @@ def _save_index(base_vectors, base_meta, pairs):
396
426
  os.replace(tmp_m, META_FILE)
397
427
  return len(meta)
398
428
 
399
- def build_index(api_key: str, full: bool = False, days: int = 90, article_days: int = 30):
429
+ def build_index(api_key: str, full: bool = False, days: int = 90, article_days: int = 30, include_imports: bool = False):
400
430
  """Build or update the semantic index.
401
431
 
402
432
  `days` / `article_days` 决定这个索引**记多久**:它们原先写死在两处调用里
@@ -421,8 +451,12 @@ def build_index(api_key: str, full: bool = False, days: int = 90, article_days:
421
451
  # Load DB
422
452
  config = load_config()
423
453
  nt_db = config.get('ntDbPath', '')
424
- if not nt_db:
454
+ # --include-imports 时没有微信库也能建索引(纯 QQ 用户是真实场景);
455
+ # 但要说出"聊天半边只有导入的",不然窗口行读起来像微信也收了。
456
+ if not nt_db and not include_imports:
425
457
  return {"error": "未初始化,请先运行 weflow-cli init"}
458
+ if not nt_db and include_imports:
459
+ print('[WARN] 未配置微信库:聊天半边只用导入的会话', file=sys.stderr)
426
460
 
427
461
  nt_key = decrypt_lock(config.get('ntKey', ''))
428
462
  nt_salt = config.get('ntSalt', '')
@@ -439,6 +473,16 @@ def build_index(api_key: str, full: bool = False, days: int = 90, article_days:
439
473
  # Collect items
440
474
  items = []
441
475
  shards_failed = []
476
+ if not nt_db:
477
+ # 没有微信库:跳过 NT 那半(name_map/分片都不存在),导入与文章照收。
478
+ # 收完直接走同一个收尾(嵌入与写盘),不在这里复制粘贴后半段。
479
+ if include_imports:
480
+ items.extend(collect_imported_chat_items(days=days))
481
+ items.extend(collect_articles(article_days=article_days))
482
+ new_items = [item for item in items if item['id'] not in existing_ids]
483
+ print(f"总数据: {len(items)} 条, 新数据: {len(new_items)} 条", file=sys.stderr)
484
+ return _build_embed_and_write(api_key, items, new_items, existing_ids,
485
+ full, days, article_days, shards_failed=[])
442
486
  try:
443
487
  import nt_decrypt # 懒加载:纯逻辑路径(关键词/重排/测试)不该被它拖住
444
488
  passphrase = decrypt_lock(config.get('favPassphrase') or config.get('decryptKey') or '')
@@ -460,13 +504,29 @@ def build_index(api_key: str, full: bool = False, days: int = 90, article_days:
460
504
  except Exception as e:
461
505
  print(f"[WARN] 聊天消息收集失败: {e}", file=sys.stderr)
462
506
 
507
+ if include_imports:
508
+ imported = collect_imported_chat_items(days=days)
509
+ items.extend(imported)
510
+ print(f"导入会话: +{len(imported)} 条", file=sys.stderr)
511
+
463
512
  article_items = collect_articles(article_days=article_days)
464
513
  items.extend(article_items)
465
514
 
466
- # Filter new items
467
515
  new_items = [item for item in items if item['id'] not in existing_ids]
468
516
  print(f"总数据: {len(items)} 条, 新数据: {len(new_items)} 条", file=sys.stderr)
469
517
 
518
+ return _build_embed_and_write(api_key, items, new_items, existing_ids,
519
+ full, days, article_days, shards_failed)
520
+
521
+
522
+
523
+ def _build_embed_and_write(api_key, items, new_items, existing_ids, full,
524
+ days, article_days, shards_failed):
525
+ """build_index 的收尾:去重过滤在调用方做完了,这里管嵌入、断点落盘与返回。
526
+
527
+ 抽出来是因为聊天半边现在有两条收集路(微信库 / 导入会话),尾巴必须只有一份——
528
+ 复制一份的代价是断点续跑、全零批次不落盘这些坑只在一半路径上修过。
529
+ """
470
530
  if not new_items:
471
531
  return {"status": "up_to_date", "total": len(items),
472
532
  "chatDays": days, "articleDays": article_days,
@@ -1522,11 +1582,15 @@ def main():
1522
1582
  p = subparsers.add_parser('build')
1523
1583
  p.add_argument('--api-key', help='DeepSeek API key(优先从 config 读取)')
1524
1584
  p.add_argument('--full', action='store_true', help='全量重建')
1585
+ p.add_argument('--include-imports', action='store_true',
1586
+ help='把 weflow-cli import qchat 导入的 QQ 会话也收进索引(聊天半边;id 带 qq: 段)')
1525
1587
  add_window_args(p)
1526
1588
 
1527
1589
  # update
1528
1590
  p = subparsers.add_parser('update')
1529
1591
  p.add_argument('--api-key', help='DeepSeek API key(优先从 config 读取)')
1592
+ p.add_argument('--include-imports', action='store_true',
1593
+ help='同 build:把导入的 QQ 会话收进增量')
1530
1594
  add_window_args(p)
1531
1595
 
1532
1596
  # search
@@ -1582,9 +1646,13 @@ def main():
1582
1646
  or get_dashscope_key(config))
1583
1647
 
1584
1648
  if args.command == 'build':
1585
- result = build_index(api_key, full=args.full, days=args.days, article_days=args.article_days)
1649
+ result = build_index(api_key, full=args.full, days=args.days,
1650
+ article_days=args.article_days,
1651
+ include_imports=getattr(args, 'include_imports', False))
1586
1652
  elif args.command == 'update':
1587
- result = build_index(api_key, full=False, days=args.days, article_days=args.article_days)
1653
+ result = build_index(api_key, full=False, days=args.days,
1654
+ article_days=args.article_days,
1655
+ include_imports=getattr(args, 'include_imports', False))
1588
1656
  elif args.command == 'phrase-build':
1589
1657
  result = build_phrase_index(line=args.line)
1590
1658
  result['success'] = 'error' not in result