weflow-cli 1.16.0 → 1.19.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 +72 -0
- package/README.md +19 -4
- package/bin/weflow-cli.ts +515 -6
- package/dist/bin/weflow-cli.js +580 -8
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/src/core/ntCore.d.ts +47 -0
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +24 -0
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/services/assistantService.d.ts +8 -1
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +47 -8
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +8 -0
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +67 -11
- package/dist/src/services/assistantTools.js.map +1 -1
- 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/cursorProbe.d.ts +44 -0
- package/dist/src/services/cursorProbe.d.ts.map +1 -0
- package/dist/src/services/cursorProbe.js +183 -0
- package/dist/src/services/cursorProbe.js.map +1 -0
- package/dist/src/services/diagnosticReport.d.ts +6 -0
- package/dist/src/services/diagnosticReport.d.ts.map +1 -1
- package/dist/src/services/diagnosticReport.js +16 -0
- package/dist/src/services/diagnosticReport.js.map +1 -1
- package/dist/src/services/qchatImport.d.ts +89 -0
- package/dist/src/services/qchatImport.d.ts.map +1 -0
- package/dist/src/services/qchatImport.js +273 -0
- package/dist/src/services/qchatImport.js.map +1 -0
- package/dist/src/services/setupVenv.d.ts +43 -0
- package/dist/src/services/setupVenv.d.ts.map +1 -0
- package/dist/src/services/setupVenv.js +198 -0
- package/dist/src/services/setupVenv.js.map +1 -0
- package/dist/src/services/wechatCompat.d.ts +29 -0
- package/dist/src/services/wechatCompat.d.ts.map +1 -0
- package/dist/src/services/wechatCompat.js +65 -0
- package/dist/src/services/wechatCompat.js.map +1 -0
- package/dist/src/utils/python.d.ts +10 -0
- package/dist/src/utils/python.d.ts.map +1 -1
- package/dist/src/utils/python.js +27 -4
- package/dist/src/utils/python.js.map +1 -1
- package/docs/DATA_CONTRACT.md +25 -0
- package/docs/DECISIONS.md +118 -30
- package/docs/NT_DATABASES.md +47 -0
- package/docs/PROJECT_STATE.md +7 -3
- package/docs/RELEASING.md +4 -0
- package/docs/REPLAY.md +169 -0
- package/docs/SYNC_CONTRACT.md +197 -180
- package/package.json +89 -88
- package/resources/js/graph3d/viewer2d.js +550 -448
- package/scripts/annual_report.py +3 -1
- package/scripts/chat_notes.py +86 -3
- package/scripts/check_readme_commands.py +427 -426
- package/scripts/extract_todos.py +3 -1
- package/scripts/graph_2d.py +156 -151
- package/scripts/graph_3d.py +672 -646
- package/scripts/imported_chats.py +68 -0
- package/scripts/mcp_bridge.py +3 -1
- package/scripts/media_index.py +150 -0
- package/scripts/nt_common.py +32 -0
- package/scripts/nt_decrypt.py +154 -16
- package/scripts/on_this_day.py +178 -178
- package/scripts/promises.py +185 -185
- package/scripts/replay.py +803 -0
- package/scripts/run_hidden.vbs +25 -0
- package/scripts/semantic_search.py +78 -6
- package/scripts/wechat_avatar.py +185 -0
- package/scripts/wechat_ledger.py +190 -0
- package/src/core/ntCore.ts +59 -0
- package/src/services/assistantService.ts +48 -8
- package/src/services/assistantTools.ts +73 -10
- package/src/services/configService.ts +3 -2
- package/src/services/cursorProbe.ts +222 -0
- package/src/services/diagnosticReport.ts +22 -0
- package/src/services/qchatImport.ts +327 -0
- package/src/services/setupVenv.ts +238 -0
- package/src/services/wechatCompat.ts +67 -0
- package/src/utils/python.ts +33 -4
package/scripts/annual_report.py
CHANGED
|
@@ -22,6 +22,7 @@ except ImportError:
|
|
|
22
22
|
|
|
23
23
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
24
24
|
from _utils import load_config, decrypt_lock, parse_frontmatter, call_deepseek, get_api_key
|
|
25
|
+
from nt_common import decode_content
|
|
25
26
|
|
|
26
27
|
TZ = timezone(timedelta(hours=8))
|
|
27
28
|
OUTPUT_ROOT = 'output'
|
|
@@ -89,7 +90,8 @@ def collect_chat_stats(conn, year, name_map):
|
|
|
89
90
|
))
|
|
90
91
|
|
|
91
92
|
for ts, sender_id, content in c.fetchall():
|
|
92
|
-
|
|
93
|
+
content = decode_content(content) # 压缩正文(issue #12)
|
|
94
|
+
if not content:
|
|
93
95
|
continue
|
|
94
96
|
dt = datetime.fromtimestamp(ts, tz=TZ)
|
|
95
97
|
if dt.year != year:
|
package/scripts/chat_notes.py
CHANGED
|
@@ -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
|
-
|
|
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}))
|