weflow-cli 1.17.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +3 -0
  3. package/bin/weflow-cli.ts +167 -0
  4. package/dist/bin/weflow-cli.js +199 -0
  5. package/dist/bin/weflow-cli.js.map +1 -1
  6. package/dist/src/services/diagnosticReport.d.ts +6 -0
  7. package/dist/src/services/diagnosticReport.d.ts.map +1 -1
  8. package/dist/src/services/diagnosticReport.js +16 -0
  9. package/dist/src/services/diagnosticReport.js.map +1 -1
  10. package/dist/src/services/wechatCompat.d.ts +29 -0
  11. package/dist/src/services/wechatCompat.d.ts.map +1 -0
  12. package/dist/src/services/wechatCompat.js +65 -0
  13. package/dist/src/services/wechatCompat.js.map +1 -0
  14. package/docs/DATA_CONTRACT.md +25 -25
  15. package/docs/DECISIONS.md +26 -0
  16. package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +365 -365
  17. package/docs/HEALTH-CHECK.md +104 -104
  18. package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -310
  19. package/docs/NEXT_ACTIONS.md +236 -236
  20. package/docs/NPM-PUBLISH.md +130 -130
  21. package/docs/NT_DATABASES.md +47 -0
  22. package/docs/POSITIONING.md +68 -68
  23. package/docs/PROJECT_STATE.md +2 -1
  24. package/docs/RELEASING.md +4 -0
  25. package/package.json +1 -1
  26. package/requirements-3x.txt +4 -4
  27. package/requirements-voice.txt +15 -15
  28. package/resources/js/graph3d/NOTICE.txt +35 -35
  29. package/resources/js/graph3d/d3-binarytree.min.js +2 -2
  30. package/resources/js/graph3d/d3-dispatch.min.js +2 -2
  31. package/resources/js/graph3d/d3-force-3d.min.js +2 -2
  32. package/resources/js/graph3d/d3-octree.min.js +2 -2
  33. package/resources/js/graph3d/d3-quadtree.min.js +2 -2
  34. package/resources/js/graph3d/d3-timer.min.js +2 -2
  35. package/resources/js/graph3d/three.min.js +6 -6
  36. package/resources/panel/quick-menu.cjs +197 -197
  37. package/scripts/_utils.py +1338 -1338
  38. package/scripts/annual_report.py +3 -1
  39. package/scripts/article_notes.py +423 -423
  40. package/scripts/contact_schema.py +339 -339
  41. package/scripts/create_reading_notes.py +11 -11
  42. package/scripts/daily_task.ps1 +213 -213
  43. package/scripts/extract_todos.py +3 -1
  44. package/scripts/fav_notes.py +321 -321
  45. package/scripts/health_check.py +473 -473
  46. package/scripts/imported_chats.py +68 -68
  47. package/scripts/install_tasks.ps1 +105 -105
  48. package/scripts/leads.py +179 -179
  49. package/scripts/mcp_bridge.py +3 -1
  50. package/scripts/media_index.py +150 -0
  51. package/scripts/network.py +206 -206
  52. package/scripts/nt_common.py +32 -0
  53. package/scripts/nt_decrypt.py +19 -15
  54. package/scripts/opinion_timeline.py +138 -138
  55. package/scripts/replay.py +803 -803
  56. package/scripts/run_hidden.vbs +24 -24
  57. package/scripts/semantic_search.py +5 -1
  58. package/scripts/user_notes.py +315 -315
  59. package/scripts/voice_transcribe.py +139 -139
  60. package/scripts/watch_issues.py +212 -212
  61. package/scripts/watch_mail.py +131 -131
  62. package/scripts/wechat_avatar.py +185 -0
  63. package/scripts/wechat_ledger.py +190 -0
  64. package/src/services/cursorProbe.ts +222 -222
  65. package/src/services/diagnosticReport.ts +22 -0
  66. package/src/services/qchatImport.ts +327 -327
  67. package/src/services/setupVenv.ts +238 -238
  68. package/src/services/syncService.ts +363 -363
  69. package/src/services/syncState.ts +275 -275
  70. package/src/services/wechatCompat.ts +67 -0
  71. package/src/services/wechatMedia.ts +166 -166
@@ -1,138 +1,138 @@
1
- """某个号的观点时间线:它这一年都说过什么,什么时候开始变了。
2
-
3
- 日报每天把文章存成 `output/biz-daily/<date>/<topic>/*.md`(frontmatter 带 `source`、
4
- `date`,正文有 `## AI 摘要`),所以"这个号的观点怎么变"是可以按时间排出来的 ——
5
- 而微信里你只能一篇篇点开、还没有横轴。
6
-
7
- 用法:
8
- python scripts/opinion_timeline.py --list # 哪些号存了多少篇
9
- python scripts/opinion_timeline.py --source 某号 # 时间线
10
- python scripts/opinion_timeline.py --source 某号 --months 3
11
- python scripts/opinion_timeline.py --source 某号 --ai # 让模型总结观点变化
12
- """
13
- from __future__ import annotations
14
-
15
- import argparse
16
- import json
17
- import pathlib
18
- import re
19
- import sys
20
- from collections import defaultdict
21
-
22
- SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
23
- sys.path.insert(0, str(SCRIPT_DIR))
24
-
25
- # 摘要复用日报那套清洗:原始文本是「【核心观点】…【关键细节】1.…」,直接打出来是噪音。
26
- from daily_mail import clean_summary as _clean # noqa: E402
27
-
28
- ROOT = SCRIPT_DIR.parent
29
- DAILY_DIR = ROOT / 'output' / 'biz-daily'
30
-
31
-
32
- def parse_md(path):
33
- """frontmatter + `## AI 摘要` 那一段。解析不出来就跳过,不让一篇坏文件毁掉整条时间线。"""
34
- try:
35
- text = path.read_text(encoding='utf-8')
36
- except Exception:
37
- return None
38
- meta = {}
39
- body = text
40
- if text.startswith('---'):
41
- end = text.find('\n---', 3)
42
- if end > 0:
43
- for line in text[3:end].splitlines():
44
- if ':' in line:
45
- k, v = line.split(':', 1)
46
- meta[k.strip()] = v.strip().strip('"')
47
- body = text[end + 4:]
48
- summary = ''
49
- m = re.search(r'##\s*AI 摘要\s*\n+(.+?)(?:\n#|\n---|$)', body, re.S)
50
- if m:
51
- summary = ' '.join(m.group(1).split())[:200]
52
- return {
53
- 'title': meta.get('title', ''),
54
- 'source': meta.get('source', ''),
55
- 'date': meta.get('date', ''),
56
- 'relevance': meta.get('relevance', ''),
57
- 'topic': meta.get('topic', ''),
58
- 'url': meta.get('url', ''),
59
- 'summary': summary,
60
- }
61
-
62
-
63
- def load_all():
64
- """所有日期目录里的文章。"""
65
- items = []
66
- if not DAILY_DIR.is_dir():
67
- return items
68
- for day in sorted(DAILY_DIR.iterdir()):
69
- if not day.is_dir():
70
- continue
71
- for md in day.rglob('*.md'):
72
- rec = parse_md(md)
73
- if rec and rec['source']:
74
- items.append(rec)
75
- return items
76
-
77
-
78
- def main():
79
- ap = argparse.ArgumentParser(description='某个号的观点时间线')
80
- ap.add_argument('--source', default='', help='公众号名(子串匹配)')
81
- ap.add_argument('--months', type=int, default=0, help='只看最近 N 个月(0 = 全部)')
82
- ap.add_argument('--limit', type=int, default=30, help='最多列几篇(默认 30,最近的在前)')
83
- ap.add_argument('--list', action='store_true', help='列出存了哪些号、各多少篇')
84
- ap.add_argument('--ai', action='store_true', help='让模型总结观点变化(要 API key)')
85
- ap.add_argument('--json', action='store_true')
86
- args = ap.parse_args()
87
-
88
- items = load_all()
89
- if not items:
90
- raise SystemExit(f'{DAILY_DIR} 下没有可解析的文章')
91
-
92
- if args.list:
93
- counter = defaultdict(int)
94
- for it in items:
95
- counter[it['source']] += 1
96
- rows = sorted(counter.items(), key=lambda kv: -kv[1])
97
- print(f'共 {len(rows)} 个号,{len(items)} 篇\n')
98
- for name, n in rows[:40]:
99
- print(f' {n:>4} 篇 {name}')
100
- return
101
-
102
- if not args.source:
103
- ap.print_help()
104
- return
105
-
106
- picked = [it for it in items if args.source in it['source']]
107
- if not picked:
108
- raise SystemExit(f'没有匹配「{args.source}」的文章;用 --list 看有哪些号')
109
- picked.sort(key=lambda x: x['date'], reverse=True)
110
- if args.limit and args.limit > 0:
111
- picked = picked[:args.limit]
112
-
113
- dates = sorted({it['date'] for it in picked})
114
- print(f"{picked[0]['source']}:{len(picked)} 篇,{dates[0]} ~ {dates[-1]}\n")
115
- for it in picked:
116
- rel = f"({it['relevance']})" if it['relevance'] else ''
117
- print(f"{it['date']} {it['title'][:60]}{rel}")
118
- if it['summary']:
119
- print(f" {_clean(it['summary'], 160)}")
120
-
121
- if args.ai:
122
- from _utils import load_config, get_api_key, call_ai
123
- api_key = get_api_key(load_config())
124
- if not api_key:
125
- print('\n[WARN] 没有可用的 API key,跳过观点总结', file=sys.stderr)
126
- return
127
- lines = [f"{it['date']} {it['title']}:{it['summary']}" for it in picked][-40:]
128
- prompt = (
129
- f"下面是同一个公众号「{picked[0]['source']}」按时间排列的文章与要点。\n"
130
- '请用中文回答:它的关注点或立场有没有随时间变化?如果有,指出从什么变成什么,'
131
- '并给出对应的日期作为证据;如果没有明显变化,就说明它一直关注什么。'
132
- '不要罗列所有篇目,不要空话。\n\n' + '\n'.join(lines))
133
- print('\n--- 观点变化(AI 总结)---')
134
- print(call_ai(prompt, 'deepseek', api_key, max_tokens=800))
135
-
136
-
137
- if __name__ == '__main__':
138
- main()
1
+ """某个号的观点时间线:它这一年都说过什么,什么时候开始变了。
2
+
3
+ 日报每天把文章存成 `output/biz-daily/<date>/<topic>/*.md`(frontmatter 带 `source`、
4
+ `date`,正文有 `## AI 摘要`),所以"这个号的观点怎么变"是可以按时间排出来的 ——
5
+ 而微信里你只能一篇篇点开、还没有横轴。
6
+
7
+ 用法:
8
+ python scripts/opinion_timeline.py --list # 哪些号存了多少篇
9
+ python scripts/opinion_timeline.py --source 某号 # 时间线
10
+ python scripts/opinion_timeline.py --source 某号 --months 3
11
+ python scripts/opinion_timeline.py --source 某号 --ai # 让模型总结观点变化
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import pathlib
18
+ import re
19
+ import sys
20
+ from collections import defaultdict
21
+
22
+ SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
23
+ sys.path.insert(0, str(SCRIPT_DIR))
24
+
25
+ # 摘要复用日报那套清洗:原始文本是「【核心观点】…【关键细节】1.…」,直接打出来是噪音。
26
+ from daily_mail import clean_summary as _clean # noqa: E402
27
+
28
+ ROOT = SCRIPT_DIR.parent
29
+ DAILY_DIR = ROOT / 'output' / 'biz-daily'
30
+
31
+
32
+ def parse_md(path):
33
+ """frontmatter + `## AI 摘要` 那一段。解析不出来就跳过,不让一篇坏文件毁掉整条时间线。"""
34
+ try:
35
+ text = path.read_text(encoding='utf-8')
36
+ except Exception:
37
+ return None
38
+ meta = {}
39
+ body = text
40
+ if text.startswith('---'):
41
+ end = text.find('\n---', 3)
42
+ if end > 0:
43
+ for line in text[3:end].splitlines():
44
+ if ':' in line:
45
+ k, v = line.split(':', 1)
46
+ meta[k.strip()] = v.strip().strip('"')
47
+ body = text[end + 4:]
48
+ summary = ''
49
+ m = re.search(r'##\s*AI 摘要\s*\n+(.+?)(?:\n#|\n---|$)', body, re.S)
50
+ if m:
51
+ summary = ' '.join(m.group(1).split())[:200]
52
+ return {
53
+ 'title': meta.get('title', ''),
54
+ 'source': meta.get('source', ''),
55
+ 'date': meta.get('date', ''),
56
+ 'relevance': meta.get('relevance', ''),
57
+ 'topic': meta.get('topic', ''),
58
+ 'url': meta.get('url', ''),
59
+ 'summary': summary,
60
+ }
61
+
62
+
63
+ def load_all():
64
+ """所有日期目录里的文章。"""
65
+ items = []
66
+ if not DAILY_DIR.is_dir():
67
+ return items
68
+ for day in sorted(DAILY_DIR.iterdir()):
69
+ if not day.is_dir():
70
+ continue
71
+ for md in day.rglob('*.md'):
72
+ rec = parse_md(md)
73
+ if rec and rec['source']:
74
+ items.append(rec)
75
+ return items
76
+
77
+
78
+ def main():
79
+ ap = argparse.ArgumentParser(description='某个号的观点时间线')
80
+ ap.add_argument('--source', default='', help='公众号名(子串匹配)')
81
+ ap.add_argument('--months', type=int, default=0, help='只看最近 N 个月(0 = 全部)')
82
+ ap.add_argument('--limit', type=int, default=30, help='最多列几篇(默认 30,最近的在前)')
83
+ ap.add_argument('--list', action='store_true', help='列出存了哪些号、各多少篇')
84
+ ap.add_argument('--ai', action='store_true', help='让模型总结观点变化(要 API key)')
85
+ ap.add_argument('--json', action='store_true')
86
+ args = ap.parse_args()
87
+
88
+ items = load_all()
89
+ if not items:
90
+ raise SystemExit(f'{DAILY_DIR} 下没有可解析的文章')
91
+
92
+ if args.list:
93
+ counter = defaultdict(int)
94
+ for it in items:
95
+ counter[it['source']] += 1
96
+ rows = sorted(counter.items(), key=lambda kv: -kv[1])
97
+ print(f'共 {len(rows)} 个号,{len(items)} 篇\n')
98
+ for name, n in rows[:40]:
99
+ print(f' {n:>4} 篇 {name}')
100
+ return
101
+
102
+ if not args.source:
103
+ ap.print_help()
104
+ return
105
+
106
+ picked = [it for it in items if args.source in it['source']]
107
+ if not picked:
108
+ raise SystemExit(f'没有匹配「{args.source}」的文章;用 --list 看有哪些号')
109
+ picked.sort(key=lambda x: x['date'], reverse=True)
110
+ if args.limit and args.limit > 0:
111
+ picked = picked[:args.limit]
112
+
113
+ dates = sorted({it['date'] for it in picked})
114
+ print(f"{picked[0]['source']}:{len(picked)} 篇,{dates[0]} ~ {dates[-1]}\n")
115
+ for it in picked:
116
+ rel = f"({it['relevance']})" if it['relevance'] else ''
117
+ print(f"{it['date']} {it['title'][:60]}{rel}")
118
+ if it['summary']:
119
+ print(f" {_clean(it['summary'], 160)}")
120
+
121
+ if args.ai:
122
+ from _utils import load_config, get_api_key, call_ai
123
+ api_key = get_api_key(load_config())
124
+ if not api_key:
125
+ print('\n[WARN] 没有可用的 API key,跳过观点总结', file=sys.stderr)
126
+ return
127
+ lines = [f"{it['date']} {it['title']}:{it['summary']}" for it in picked][-40:]
128
+ prompt = (
129
+ f"下面是同一个公众号「{picked[0]['source']}」按时间排列的文章与要点。\n"
130
+ '请用中文回答:它的关注点或立场有没有随时间变化?如果有,指出从什么变成什么,'
131
+ '并给出对应的日期作为证据;如果没有明显变化,就说明它一直关注什么。'
132
+ '不要罗列所有篇目,不要空话。\n\n' + '\n'.join(lines))
133
+ print('\n--- 观点变化(AI 总结)---')
134
+ print(call_ai(prompt, 'deepseek', api_key, max_tokens=800))
135
+
136
+
137
+ if __name__ == '__main__':
138
+ main()