weflow-cli 1.17.0 → 1.19.1
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 +46 -0
- package/README.md +3 -0
- package/bin/weflow-cli.ts +167 -0
- package/dist/bin/weflow-cli.js +199 -0
- package/dist/bin/weflow-cli.js.map +1 -1
- 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/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/docs/DATA_CONTRACT.md +25 -25
- package/docs/DECISIONS.md +26 -0
- package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +365 -365
- package/docs/HEALTH-CHECK.md +104 -104
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -310
- package/docs/NEXT_ACTIONS.md +236 -236
- package/docs/NPM-PUBLISH.md +130 -130
- package/docs/NT_DATABASES.md +47 -0
- package/docs/POSITIONING.md +68 -68
- package/docs/PROJECT_STATE.md +3 -2
- package/docs/RELEASING.md +4 -0
- package/package.json +1 -1
- package/requirements-3x.txt +4 -4
- package/requirements-voice.txt +15 -15
- package/resources/js/graph3d/NOTICE.txt +35 -35
- package/resources/js/graph3d/d3-binarytree.min.js +2 -2
- package/resources/js/graph3d/d3-dispatch.min.js +2 -2
- package/resources/js/graph3d/d3-force-3d.min.js +2 -2
- package/resources/js/graph3d/d3-octree.min.js +2 -2
- package/resources/js/graph3d/d3-quadtree.min.js +2 -2
- package/resources/js/graph3d/d3-timer.min.js +2 -2
- package/resources/js/graph3d/three.min.js +6 -6
- package/resources/panel/quick-menu.cjs +197 -197
- package/scripts/_utils.py +1338 -1338
- package/scripts/annual_report.py +3 -1
- package/scripts/article_notes.py +423 -423
- package/scripts/contact_schema.py +339 -339
- package/scripts/create_reading_notes.py +11 -11
- package/scripts/daily_mail.py +51 -5
- package/scripts/daily_task.ps1 +216 -213
- package/scripts/extract_todos.py +3 -1
- package/scripts/fav_notes.py +321 -321
- package/scripts/health_check.py +473 -473
- package/scripts/imported_chats.py +68 -68
- package/scripts/install_tasks.ps1 +105 -105
- package/scripts/key_cache.py +232 -0
- package/scripts/leads.py +179 -179
- package/scripts/mcp_bridge.py +3 -1
- package/scripts/media_index.py +150 -0
- package/scripts/network.py +206 -206
- package/scripts/nt_common.py +32 -0
- package/scripts/nt_decrypt.py +2023 -1954
- package/scripts/opinion_timeline.py +138 -138
- package/scripts/replay.py +803 -803
- package/scripts/run_hidden.vbs +24 -24
- package/scripts/semantic_search.py +5 -1
- package/scripts/user_notes.py +315 -315
- package/scripts/voice_transcribe.py +139 -139
- package/scripts/watch_issues.py +212 -212
- package/scripts/watch_mail.py +131 -131
- package/scripts/wechat_avatar.py +185 -0
- package/scripts/wechat_ledger.py +190 -0
- package/src/services/cursorProbe.ts +222 -222
- package/src/services/diagnosticReport.ts +22 -0
- package/src/services/qchatImport.ts +327 -327
- package/src/services/setupVenv.ts +238 -238
- package/src/services/syncService.ts +363 -363
- package/src/services/syncState.ts +275 -275
- package/src/services/wechatCompat.ts +67 -0
- 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()
|