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
@@ -46,6 +46,7 @@ from _utils import (CHAT_CARD_PREFIX, LinkNamer, SEPARATOR, # noqa: E402
46
46
  call_deepseek, decrypt_lock, get_api_key, line_concepts_path, load_config,
47
47
  note_path as _shared_note_path, plain, write_with_frontmatter)
48
48
  from reply_debt import collect_conversations, format_line # noqa: E402
49
+ import imported_chats # noqa: E402 (`weflow-cli import qchat` 落下的会话,单一读者见那边的模块注释)
49
50
 
50
51
  import nt_decrypt # noqa: E402
51
52
  # 语音转写:**只读它的缓存**(`output/.voice-cache`,内容寻址)。模型加载在 wechat_voice 内部,
@@ -336,11 +337,84 @@ def attach_voice_transcripts(messages, voice_map, cache):
336
337
  return enriched, pending
337
338
 
338
339
 
339
- def collect(out_root, days, limit, transcribe_voice=False):
340
- """读本地库:最近有动静的会话 + 每个会话窗口内的对话。**不调用任何模型。**"""
340
+ # QQ 非文本消息的占位词表:与微信侧 parsedContent 的显示形态同一套词汇
341
+ #(`[图片]`/`[语音]`…),format_line 不用知道消息来自哪个 IM。
342
+ QQ_TYPE_LABELS = {
343
+ 'image': '图片', 'voice': '语音', 'video': '视频', 'emoji': '表情',
344
+ 'location': '位置', 'card': '卡片', 'link': '链接', 'quote': '引用',
345
+ 'call': '通话', 'system': '系统消息', 'other': '非文本',
346
+ }
347
+
348
+
349
+ def qq_format_message(m):
350
+ """存储里的 QQ 消息 → format_line 认的形状。
351
+
352
+ `isSend` 缺省(导入时没给 --own-id)按 False 处理:这一行的标签落到
353
+ senderDisplay(群名片/昵称),私聊里就是对方的名字——分得清谁说的,
354
+ 只是"我自己说的"那句会标成名字。要精确就导入时给 --own-id。
355
+ """
356
+ text = (m.get('parsedContent') or '').strip()
357
+ if not text:
358
+ label = QQ_TYPE_LABELS.get(m.get('messageType'), '非文本')
359
+ text = '[%s]' % label
360
+ return {
361
+ 'createTime': m.get('createTime') or 0,
362
+ 'isSend': bool(m.get('isSend')),
363
+ 'senderDisplay': m.get('senderName') or '',
364
+ 'parsedContent': text,
365
+ }
366
+
367
+
368
+ def collect_imported(out_root, days, limit):
369
+ """导入的 QQ 会话 → 与微信会话同形状的卡片原料。**不调模型、不读微信库。**"""
370
+ cutoff = int((datetime.now(TZ) - timedelta(days=days)).timestamp())
371
+ cards, thin = [], []
372
+ for sess in imported_chats.sessions()[:limit]:
373
+ meta = sess.get('session') or {}
374
+ in_window = [m for m in sess.get('messages', [])
375
+ if isinstance(m, dict) and (m.get('createTime') or 0) >= cutoff]
376
+ if not in_window:
377
+ continue
378
+ # 名字带 QQ- 前缀:卡片文件名、概念页来源都靠它和微信会话区分开
379
+ name = 'QQ-%s' % (meta.get('name') or meta.get('id') or '未命名')
380
+ lines = [format_line(qq_format_message(m), NOTE_MSG_CHARS)
381
+ for m in reversed(in_window)]
382
+ lines, dropped = trim_to_budget(lines)
383
+ chars = len(chr(10).join(lines))
384
+ if not worth_a_card(len(lines), chars):
385
+ thin.append({'name': name, 'messages': len(lines), 'chars': chars})
386
+ continue
387
+ cards.append({'talker': 'qq:%s' % (meta.get('id') or ''),
388
+ 'name': name,
389
+ 'messages': len(lines), 'chars': chars, 'lines': lines,
390
+ 'truncated': False, 'dropped': dropped})
391
+ return cards, thin
392
+
393
+
394
+ def collect(out_root, days, limit, transcribe_voice=False, source='wechat'):
395
+ """读本地库:最近有动静的会话 + 每个会话窗口内的对话。**不调用任何模型。**
396
+
397
+ `source`:`wechat`(默认,行为与从前逐字一致)/ `qq`(只读 import qchat 落下的会话,
398
+ 不碰微信库)/ `all`(微信优先,微信库打不开时降级只跑 QQ 并说一声)。
399
+ """
400
+ if source == 'qq':
401
+ cards, thin = collect_imported(out_root, days, limit)
402
+ return {'cards': cards, 'skipped': thin, 'outRoot': out_root,
403
+ 'voice': {'enriched': 0, 'pending': 0}}, None
341
404
  config = load_config()
342
405
  db = config.get('ntDbPath', '')
406
+
407
+ def qq_only(reason):
408
+ # `--source all` 的降级:微信库打不开不该连 QQ 那半也一起没掉,但要说一声
409
+ print('微信库不可用(%s),--source all 降级为只处理导入的 QQ 会话' % reason,
410
+ file=sys.stderr)
411
+ cards, thin = collect_imported(out_root, days, limit)
412
+ return {'cards': cards, 'skipped': thin, 'outRoot': out_root,
413
+ 'voice': {'enriched': 0, 'pending': 0}}, None
414
+
343
415
  if not db:
416
+ if source == 'all':
417
+ return qq_only('配置里没有 ntDbPath')
344
418
  print('配置里没有 ntDbPath,先运行 weflow-cli init', file=sys.stderr)
345
419
  return None, 'no-db'
346
420
  conns = nt_decrypt.connect_message_shards(
@@ -348,6 +422,8 @@ def collect(out_root, days, limit, transcribe_voice=False):
348
422
  # 口令的回退链与 `draft_reply.py` 那条路逐字一致:两处读出不同的库才是怪事
349
423
  decrypt_lock(config.get('favPassphrase') or config.get('decryptKey') or ''))
350
424
  if not conns:
425
+ if source == 'all':
426
+ return qq_only('无法打开消息数据库')
351
427
  print('无法打开消息数据库,请检查密钥(weflow-cli check)', file=sys.stderr)
352
428
  return None, 'no-conn'
353
429
  try:
@@ -408,6 +484,10 @@ def collect(out_root, days, limit, transcribe_voice=False):
408
484
  cards.append({'talker': item['talker'], 'name': item['name'],
409
485
  'messages': len(lines), 'chars': chars, 'lines': lines,
410
486
  'truncated': truncated, 'dropped': dropped})
487
+ if source == 'all':
488
+ qq_cards, qq_thin = collect_imported(out_root, days, limit)
489
+ cards.extend(qq_cards)
490
+ thin.extend(qq_thin)
411
491
  return {'cards': cards, 'skipped': thin, 'outRoot': out_root, 'voice': voice_total}, None
412
492
  finally:
413
493
  for conn in conns:
@@ -428,6 +508,8 @@ def main():
428
508
  sys.stdout.reconfigure(encoding='utf-8', errors='replace')
429
509
  parser = argparse.ArgumentParser(description='聊天知识卡(把会话变成 Wiki 的输入;只产文本,不发送)')
430
510
  parser.add_argument('--days', type=int, default=DEFAULT_DAYS, help='看最近多少天(默认 30)')
511
+ parser.add_argument('--source', choices=['wechat', 'qq', 'all'], default='wechat',
512
+ help='聊天来源:wechat(默认)/ qq(只读 weflow-cli import qchat 导入的会话)/ all(都要)')
431
513
  parser.add_argument('--limit', type=int, default=MAX_CONVERSATIONS,
432
514
  help='最多处理几个会话(默认 %d)' % MAX_CONVERSATIONS)
433
515
  parser.add_argument('--out', default=OUTPUT_ROOT, help='卡片输出目录')
@@ -448,7 +530,8 @@ def main():
448
530
  return 1
449
531
 
450
532
  collected, failure = collect(args.out, args.days, args.limit,
451
- transcribe_voice=args.transcribe_voice)
533
+ transcribe_voice=args.transcribe_voice,
534
+ source=args.source)
452
535
  if collected is None:
453
536
  if args.json:
454
537
  print(json.dumps({'success': False, 'error': failure}))