weflow-cli 1.7.0 → 1.8.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/ARCHITECTURE.md +3 -2
- package/CHANGELOG.md +2108 -2
- package/OPERATIONS.md +810 -1
- package/README.en.md +96 -10
- package/README.md +34 -9
- package/bin/weflow-cli.ts +937 -28
- package/dist/bin/weflow-cli.js +955 -28
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.js +39 -13
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/appMsgFormat.d.ts +37 -0
- package/dist/src/core/appMsgFormat.d.ts.map +1 -0
- package/dist/src/core/appMsgFormat.js +106 -0
- package/dist/src/core/appMsgFormat.js.map +1 -0
- package/dist/src/core/ntCore.d.ts +19 -2
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +11 -1
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/core/sqlcipherCore.d.ts +0 -12
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
- package/dist/src/core/sqlcipherCore.js +5 -64
- package/dist/src/core/sqlcipherCore.js.map +1 -1
- package/dist/src/core/wcdbCore.d.ts +1 -1
- package/dist/src/core/wcdbCore.d.ts.map +1 -1
- package/dist/src/core/wcdbCore.js +5 -1
- package/dist/src/core/wcdbCore.js.map +1 -1
- package/dist/src/panel/client.d.ts +25 -0
- package/dist/src/panel/client.d.ts.map +1 -0
- package/dist/src/panel/client.js +34 -0
- package/dist/src/panel/client.js.map +1 -0
- package/dist/src/panel/endpoint.d.ts +45 -0
- package/dist/src/panel/endpoint.d.ts.map +1 -0
- package/dist/src/panel/endpoint.js +132 -0
- package/dist/src/panel/endpoint.js.map +1 -0
- package/dist/src/panel/launch.d.ts +50 -0
- package/dist/src/panel/launch.d.ts.map +1 -0
- package/dist/src/panel/launch.js +74 -0
- package/dist/src/panel/launch.js.map +1 -0
- package/dist/src/panel/server.d.ts +23 -0
- package/dist/src/panel/server.d.ts.map +1 -0
- package/dist/src/panel/server.js +413 -0
- package/dist/src/panel/server.js.map +1 -0
- package/dist/src/panel/userId.d.ts +37 -0
- package/dist/src/panel/userId.d.ts.map +1 -0
- package/dist/src/panel/userId.js +56 -0
- package/dist/src/panel/userId.js.map +1 -0
- package/dist/src/services/assistantDaemon.d.ts +20 -3
- package/dist/src/services/assistantDaemon.d.ts.map +1 -1
- package/dist/src/services/assistantDaemon.js +46 -9
- package/dist/src/services/assistantDaemon.js.map +1 -1
- package/dist/src/services/assistantEval.d.ts +141 -0
- package/dist/src/services/assistantEval.d.ts.map +1 -0
- package/dist/src/services/assistantEval.js +497 -0
- package/dist/src/services/assistantEval.js.map +1 -0
- package/dist/src/services/assistantMemory.d.ts +106 -4
- package/dist/src/services/assistantMemory.d.ts.map +1 -1
- package/dist/src/services/assistantMemory.js +396 -61
- package/dist/src/services/assistantMemory.js.map +1 -1
- package/dist/src/services/assistantPrivacy.d.ts +15 -2
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -1
- package/dist/src/services/assistantPrivacy.js +25 -6
- package/dist/src/services/assistantPrivacy.js.map +1 -1
- package/dist/src/services/assistantRouter.d.ts +41 -0
- package/dist/src/services/assistantRouter.d.ts.map +1 -0
- package/dist/src/services/assistantRouter.js +151 -0
- package/dist/src/services/assistantRouter.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +209 -3
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +599 -75
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +217 -1
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +1116 -59
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/assistantTrace.d.ts +47 -0
- package/dist/src/services/assistantTrace.d.ts.map +1 -0
- package/dist/src/services/assistantTrace.js +150 -0
- package/dist/src/services/assistantTrace.js.map +1 -0
- package/dist/src/services/chatService.d.ts +1 -1
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +5 -2
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/configService.d.ts +9 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +15 -8
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/pythonBridge.d.ts +41 -0
- package/dist/src/services/pythonBridge.d.ts.map +1 -0
- package/dist/src/services/pythonBridge.js +119 -0
- package/dist/src/services/pythonBridge.js.map +1 -0
- package/dist/src/services/syncService.d.ts +8 -2
- package/dist/src/services/syncService.d.ts.map +1 -1
- package/dist/src/services/syncService.js +26 -7
- package/dist/src/services/syncService.js.map +1 -1
- package/dist/src/services/wechatMessageService.d.ts +25 -0
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +69 -7
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/services/wereadService.d.ts +33 -6
- package/dist/src/services/wereadService.d.ts.map +1 -1
- package/dist/src/services/wereadService.js +27 -2
- package/dist/src/services/wereadService.js.map +1 -1
- package/dist/src/utils/atomicWrite.d.ts +4 -0
- package/dist/src/utils/atomicWrite.d.ts.map +1 -0
- package/dist/src/utils/atomicWrite.js +60 -0
- package/dist/src/utils/atomicWrite.js.map +1 -0
- package/dist/src/utils/dateRange.d.ts +15 -0
- package/dist/src/utils/dateRange.d.ts.map +1 -1
- package/dist/src/utils/dateRange.js +47 -0
- package/dist/src/utils/dateRange.js.map +1 -1
- package/dist/src/utils/text.d.ts +18 -0
- package/dist/src/utils/text.d.ts.map +1 -0
- package/dist/src/utils/text.js +20 -0
- package/dist/src/utils/text.js.map +1 -0
- package/docs/AI_INTERFACE.md +2 -0
- package/docs/BRANCHES.md +13 -4
- package/docs/DECISIONS.md +1424 -0
- package/docs/EXTENDING.md +187 -0
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -0
- package/docs/MCP.md +23 -4
- package/docs/PROJECT_STATE.md +210 -9
- package/docs/SETUP.md +22 -0
- package/docs/SYNC_CONTRACT.md +31 -3
- package/mcp-server/index.ts +681 -657
- package/package.json +84 -83
- package/resources/panel/ball-position.cjs +153 -0
- package/resources/panel/index.html +46 -0
- package/resources/panel/main.cjs +483 -0
- package/resources/panel/mascot-happy.png +0 -0
- package/resources/panel/mascot-sorry.png +0 -0
- package/resources/panel/mascot-thinking.png +0 -0
- package/resources/panel/mascot-tired.png +0 -0
- package/resources/panel/mascot.png +0 -0
- package/resources/panel/package.json +7 -0
- package/resources/panel/panel.css +415 -0
- package/resources/panel/preload.cjs +79 -0
- package/resources/panel/quick-menu.cjs +197 -0
- package/resources/panel/renderer.js +470 -0
- package/resources/panel/tray-menu.cjs +40 -0
- package/resources/panel/tray.png +0 -0
- package/scripts/_utils.py +425 -54
- package/scripts/annual_report.py +4 -2
- package/scripts/article_notes.py +423 -0
- package/scripts/backfill_articles.py +438 -0
- package/scripts/biz_daily.py +590 -105
- package/scripts/chat_notes.py +470 -0
- package/scripts/classify_daily.py +10 -18
- package/scripts/compile_wiki.py +1060 -46
- package/scripts/create_reading_notes.py +199 -14
- package/scripts/decide.py +253 -0
- package/scripts/draft_reply.py +671 -0
- package/scripts/export_chat_html.py +35 -33
- package/scripts/extract_todos.py +31 -5
- package/scripts/fav_notes.py +318 -0
- package/scripts/fix_topics.py +2 -1
- package/scripts/generate_ai_report.py +214 -9
- package/scripts/generate_html.py +18 -3
- package/scripts/generate_review.py +2 -2
- package/scripts/jev_client.py +294 -0
- package/scripts/jev_probe.py +440 -0
- package/scripts/nt_common.py +82 -0
- package/scripts/nt_decrypt.py +1755 -1460
- package/scripts/pipeline.py +27 -2
- package/scripts/promote_ideas.py +19 -4
- package/scripts/quality_eval.py +707 -0
- package/scripts/rag_chat.py +4 -4
- package/scripts/read_image.py +187 -0
- package/scripts/reply_debt.py +488 -0
- package/scripts/route_cards.py +453 -0
- package/scripts/semantic_search.py +208 -49
- package/scripts/user_notes.py +315 -0
- package/scripts/vault_rag.py +13 -7
- package/scripts/vault_search.py +21 -4
- package/scripts/wiki_lint.py +429 -0
- package/src/core/appMsgFormat.ts +117 -0
- package/src/core/ntCore.ts +27 -2
- package/src/core/sqlcipherCore.ts +5 -61
- package/src/core/wcdbCore.ts +6 -2
- package/src/panel/client.ts +49 -0
- package/src/panel/endpoint.ts +144 -0
- package/src/panel/launch.ts +101 -0
- package/src/panel/server.ts +424 -0
- package/src/panel/userId.ts +72 -0
- package/src/services/assistantDaemon.ts +57 -9
- package/src/services/assistantEval.ts +611 -0
- package/src/services/assistantMemory.ts +413 -57
- package/src/services/assistantPrivacy.ts +25 -6
- package/src/services/assistantRouter.ts +191 -0
- package/src/services/assistantService.ts +681 -74
- package/src/services/assistantTools.ts +1149 -58
- package/src/services/assistantTrace.ts +175 -0
- package/src/services/chatService.ts +6 -2
- package/src/services/configService.ts +403 -385
- package/src/services/pythonBridge.ts +139 -0
- package/src/services/syncService.ts +37 -9
- package/src/services/wechatMessageService.ts +73 -7
- package/src/services/wereadService.ts +43 -7
- package/src/utils/atomicWrite.ts +56 -0
- package/src/utils/dateRange.ts +46 -0
- package/src/utils/text.ts +20 -0
|
@@ -1,385 +1,403 @@
|
|
|
1
|
-
import { join } from 'path'
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs'
|
|
3
|
-
import { homedir, hostname, userInfo } from 'os'
|
|
4
|
-
import crypto from 'crypto'
|
|
5
|
-
import { expandHomePath } from '../utils/pathUtils.js'
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
this.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs'
|
|
3
|
+
import { homedir, hostname, userInfo } from 'os'
|
|
4
|
+
import crypto from 'crypto'
|
|
5
|
+
import { expandHomePath } from '../utils/pathUtils.js'
|
|
6
|
+
import { writeFileAtomic } from '../utils/atomicWrite.js'
|
|
7
|
+
import type { ConfigData } from '../types.js'
|
|
8
|
+
|
|
9
|
+
const LOCK_PREFIX = 'lock:'
|
|
10
|
+
|
|
11
|
+
interface CliConfig {
|
|
12
|
+
dbPath: string
|
|
13
|
+
wxid: string
|
|
14
|
+
decryptKey: string
|
|
15
|
+
decryptKey3x: string
|
|
16
|
+
dataVersion: string
|
|
17
|
+
dbPath3x: string
|
|
18
|
+
ntDbPath: string
|
|
19
|
+
ntKey: string
|
|
20
|
+
ntSalt: string
|
|
21
|
+
contactDbPath: string
|
|
22
|
+
contactKey: string
|
|
23
|
+
contactSalt: string
|
|
24
|
+
// 微信消息通道
|
|
25
|
+
wechatOcToken: string
|
|
26
|
+
wechatOcAccountId: string
|
|
27
|
+
wechatOcBaseUrl: string
|
|
28
|
+
wechatOcSyncBuf: string
|
|
29
|
+
/** 持久化的 context_token: wxid -> token (加密 JSON 字符串) */
|
|
30
|
+
wechatOcContextTokens: string
|
|
31
|
+
// 白名单
|
|
32
|
+
whitelist: string[]
|
|
33
|
+
// 黑名单 (绝对禁止收发)
|
|
34
|
+
blacklist: string[]
|
|
35
|
+
/** 白名单带昵称: {wxid, displayName, addedAt}[] */
|
|
36
|
+
whitelistEntries: ListEntry[]
|
|
37
|
+
/** 黑名单带昵称: {wxid, displayName, addedAt, reason?}[] */
|
|
38
|
+
blacklistEntries: ListEntry[]
|
|
39
|
+
// Vault & Pipeline
|
|
40
|
+
vaultRepo: string
|
|
41
|
+
aiEngine: string
|
|
42
|
+
// 微信读书
|
|
43
|
+
wereadApiKey: string
|
|
44
|
+
// AI 引擎 API Key
|
|
45
|
+
deepseekApiKey: string
|
|
46
|
+
// TypeSafe (Jev) 决策模型 API Key —— 日报的主题/相关度判断用它
|
|
47
|
+
typesafeApiKey: string
|
|
48
|
+
// 阿里云百炼(DashScope)API Key —— 语义检索 / RAG 的 embedding 用它
|
|
49
|
+
dashscopeApiKey: string
|
|
50
|
+
// 朋友圈 (SNS) 数据库
|
|
51
|
+
snsDbPath: string
|
|
52
|
+
snsKey: string
|
|
53
|
+
snsSalt: string
|
|
54
|
+
// 收藏 (favorite.db)
|
|
55
|
+
favDbPath: string
|
|
56
|
+
favKey: string
|
|
57
|
+
emoticonSeed: string
|
|
58
|
+
favPassphrase: string
|
|
59
|
+
// 第二大脑助手
|
|
60
|
+
/** 隐私模式: open | balanced | strict,默认 strict */
|
|
61
|
+
assistantPrivacy: string
|
|
62
|
+
/** 本地推理引擎模型名 (ollama/lmstudio) */
|
|
63
|
+
localModel: string
|
|
64
|
+
/** 自定义 AI 端点 (OpenAI 兼容中转站); 设置后云端引擎走此地址 */
|
|
65
|
+
aiBaseUrl: string
|
|
66
|
+
/** 自定义端点使用的模型名 */
|
|
67
|
+
aiModel: string
|
|
68
|
+
/** 助手白名单 (@im.wechat ID, 逗号/空格分隔); 空 = 拒绝所有人 */
|
|
69
|
+
assistantWhitelist: string
|
|
70
|
+
/** 官方通道明确提供群 ID 时才使用;空 = 拒绝所有群 */
|
|
71
|
+
assistantGroupWhitelist: string
|
|
72
|
+
/** 群内是否必须明确 @ 机器人;默认开启 */
|
|
73
|
+
assistantGroupRequireMention: string
|
|
74
|
+
/** 助手单轮快路径(D-035):off(默认,与今天逐字段一致) | log(只记「本来会走哪条」) | on */
|
|
75
|
+
assistantFastRoute: string
|
|
76
|
+
/** 本机面板用哪个记忆桶(空 = 自动:白名单恰好一条就用它)。见 src/panel/userId.ts */
|
|
77
|
+
assistantPanelUser: string
|
|
78
|
+
quickReplyContacts: string
|
|
79
|
+
dailySources: string
|
|
80
|
+
/** 公众号类别映射,JSON 对象:公众号名称或 gh_ ID -> 类别 */
|
|
81
|
+
dailySourceCategories: string
|
|
82
|
+
/** 不在日报里出现的主题,逗号分隔(如 新闻,投资);展示层开关,改它不用重抓 */
|
|
83
|
+
dailyExcludeTopics: string
|
|
84
|
+
dailyAiEnabled: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ListEntry {
|
|
88
|
+
wxid: string
|
|
89
|
+
displayName?: string
|
|
90
|
+
addedAt?: number
|
|
91
|
+
reason?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 需要加密存储的字段 */
|
|
95
|
+
const ENCRYPTED_KEYS: ReadonlySet<keyof CliConfig> = new Set([
|
|
96
|
+
'decryptKey', 'decryptKey3x', 'ntKey', 'contactKey',
|
|
97
|
+
'wechatOcToken', 'wereadApiKey', 'deepseekApiKey', 'typesafeApiKey', 'dashscopeApiKey', 'snsKey',
|
|
98
|
+
'favKey', 'favPassphrase',
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
const CONFIG_DIR = join(homedir(), '.weflow-cli')
|
|
102
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
|
|
103
|
+
|
|
104
|
+
export class ConfigService {
|
|
105
|
+
private config: CliConfig = { dbPath: '', wxid: '', decryptKey: '', decryptKey3x: '', dataVersion: '', dbPath3x: '', ntDbPath: '', ntKey: '', ntSalt: '', contactDbPath: '', contactKey: '', contactSalt: '', wechatOcToken: '', wechatOcAccountId: '', wechatOcBaseUrl: '', wechatOcSyncBuf: '', wechatOcContextTokens: '', whitelist: [], blacklist: [], whitelistEntries: [], blacklistEntries: [], vaultRepo: '', aiEngine: 'deepseek', wereadApiKey: '', deepseekApiKey: '', typesafeApiKey: '', dashscopeApiKey: '', snsDbPath: '', snsKey: '', snsSalt: '', favDbPath: '', favKey: '', favPassphrase: '', emoticonSeed: '', assistantPrivacy: 'strict', localModel: '', aiBaseUrl: '', aiModel: '', assistantWhitelist: '', assistantGroupWhitelist: '', assistantGroupRequireMention: 'true', assistantFastRoute: 'off', assistantPanelUser: '', quickReplyContacts: '', dailySources: '', dailySourceCategories: '', dailyExcludeTopics: '', dailyAiEnabled: 'true' }
|
|
106
|
+
|
|
107
|
+
/** 本进程修改过、待回写的字段 (多进程并发写保护) */
|
|
108
|
+
private dirty = new Set<keyof CliConfig>()
|
|
109
|
+
|
|
110
|
+
constructor() {
|
|
111
|
+
this.load()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private load(): void {
|
|
115
|
+
try {
|
|
116
|
+
if (existsSync(CONFIG_FILE)) {
|
|
117
|
+
const data = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'))
|
|
118
|
+
const whitelist = Array.isArray(data.whitelist) ? data.whitelist : []
|
|
119
|
+
const blacklist = Array.isArray(data.blacklist) ? data.blacklist : []
|
|
120
|
+
// entries 优先; 缺失时从旧 string[] 迁移
|
|
121
|
+
const whitelistEntries: ListEntry[] = Array.isArray(data.whitelistEntries)
|
|
122
|
+
? data.whitelistEntries
|
|
123
|
+
: whitelist.map((wxid: string) => ({ wxid }))
|
|
124
|
+
const blacklistEntries: ListEntry[] = Array.isArray(data.blacklistEntries)
|
|
125
|
+
? data.blacklistEntries
|
|
126
|
+
: blacklist.map((wxid: string) => ({ wxid }))
|
|
127
|
+
this.config = {
|
|
128
|
+
dbPath: data.dbPath || '',
|
|
129
|
+
wxid: data.wxid || '',
|
|
130
|
+
decryptKey: data.decryptKey || '',
|
|
131
|
+
decryptKey3x: data.decryptKey3x || '',
|
|
132
|
+
dataVersion: data.dataVersion || '',
|
|
133
|
+
dbPath3x: data.dbPath3x || '',
|
|
134
|
+
ntDbPath: data.ntDbPath || '',
|
|
135
|
+
ntKey: data.ntKey || '',
|
|
136
|
+
ntSalt: data.ntSalt || '',
|
|
137
|
+
contactDbPath: data.contactDbPath || '',
|
|
138
|
+
contactKey: data.contactKey || '',
|
|
139
|
+
contactSalt: data.contactSalt || '',
|
|
140
|
+
wechatOcToken: data.wechatOcToken || '',
|
|
141
|
+
wechatOcAccountId: data.wechatOcAccountId || '',
|
|
142
|
+
wechatOcBaseUrl: data.wechatOcBaseUrl || '',
|
|
143
|
+
wechatOcSyncBuf: data.wechatOcSyncBuf || '',
|
|
144
|
+
wechatOcContextTokens: data.wechatOcContextTokens || '',
|
|
145
|
+
whitelist,
|
|
146
|
+
blacklist,
|
|
147
|
+
whitelistEntries,
|
|
148
|
+
blacklistEntries,
|
|
149
|
+
vaultRepo: data.vaultRepo || '',
|
|
150
|
+
aiEngine: data.aiEngine || 'deepseek',
|
|
151
|
+
wereadApiKey: data.wereadApiKey || '',
|
|
152
|
+
deepseekApiKey: data.deepseekApiKey || '',
|
|
153
|
+
typesafeApiKey: data.typesafeApiKey || '',
|
|
154
|
+
dashscopeApiKey: data.dashscopeApiKey || '',
|
|
155
|
+
snsDbPath: data.snsDbPath || '',
|
|
156
|
+
snsKey: data.snsKey || '',
|
|
157
|
+
snsSalt: data.snsSalt || '',
|
|
158
|
+
favDbPath: data.favDbPath || '',
|
|
159
|
+
favKey: data.favKey || '',
|
|
160
|
+
favPassphrase: data.favPassphrase || '',
|
|
161
|
+
emoticonSeed: data.emoticonSeed || '',
|
|
162
|
+
assistantPrivacy: data.assistantPrivacy || 'strict',
|
|
163
|
+
localModel: data.localModel || '',
|
|
164
|
+
aiBaseUrl: data.aiBaseUrl || '',
|
|
165
|
+
aiModel: data.aiModel || '',
|
|
166
|
+
assistantWhitelist: data.assistantWhitelist || '',
|
|
167
|
+
assistantGroupWhitelist: data.assistantGroupWhitelist || '',
|
|
168
|
+
assistantGroupRequireMention: data.assistantGroupRequireMention === 'false' ? 'false' : 'true',
|
|
169
|
+
assistantFastRoute: data.assistantFastRoute || 'off',
|
|
170
|
+
assistantPanelUser: data.assistantPanelUser || '',
|
|
171
|
+
quickReplyContacts: data.quickReplyContacts || '',
|
|
172
|
+
dailySources: data.dailySources || '',
|
|
173
|
+
dailySourceCategories: data.dailySourceCategories || '',
|
|
174
|
+
dailyExcludeTopics: data.dailyExcludeTopics || '',
|
|
175
|
+
dailyAiEnabled: data.dailyAiEnabled === 'false' ? 'false' : 'true',
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
// 配置文件损坏,输出警告并使用默认值
|
|
180
|
+
console.warn(`[weflow-cli] 配置文件解析失败 (${CONFIG_FILE}): ${e instanceof Error ? e.message : e},将使用默认配置`)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private save(): void {
|
|
185
|
+
try {
|
|
186
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
187
|
+
mkdirSync(CONFIG_DIR, { recursive: true })
|
|
188
|
+
}
|
|
189
|
+
// 多进程安全 (守护进程与 CLI 并发): 只回写本进程修改过的字段,
|
|
190
|
+
// 其余字段以磁盘最新值保留, 避免旧内存快照覆盖他人写入 (幽灵回写)。
|
|
191
|
+
let merged: Record<string, unknown> = { ...this.config }
|
|
192
|
+
try {
|
|
193
|
+
if (existsSync(CONFIG_FILE) && this.dirty.size > 0) {
|
|
194
|
+
const onDisk: Record<string, unknown> = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'))
|
|
195
|
+
merged = { ...onDisk, ...this.config }
|
|
196
|
+
for (const key of Object.keys(onDisk)) {
|
|
197
|
+
if (this.dirty.has(key as keyof CliConfig)) continue
|
|
198
|
+
merged[key] = onDisk[key]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch { /* 磁盘副本不可读时退回整份写入 */ }
|
|
202
|
+
// 原子写入: 先写临时文件再改名, 防止进程中途被杀导致配置损坏。
|
|
203
|
+
// 改名在 Windows 上会因"文件正被别的句柄打开"而 EPERM(见 atomicWrite 的注释),
|
|
204
|
+
// 那里重试并兜底,所以配置不会因为一个索引器扫过就静默没存上
|
|
205
|
+
writeFileAtomic(CONFIG_FILE, JSON.stringify(merged, null, 2))
|
|
206
|
+
this.dirty.clear()
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error('保存配置失败:', e)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get<K extends keyof CliConfig>(key: K): CliConfig[K] {
|
|
213
|
+
const raw = this.config[key]
|
|
214
|
+
if (ENCRYPTED_KEYS.has(key) && typeof raw === 'string' && raw.startsWith(LOCK_PREFIX)) {
|
|
215
|
+
return this.lockDecrypt(raw) as CliConfig[K]
|
|
216
|
+
}
|
|
217
|
+
if ((key === 'dbPath' || key === 'dbPath3x' || key === 'ntDbPath') && typeof raw === 'string') {
|
|
218
|
+
return expandHomePath(raw) as CliConfig[K]
|
|
219
|
+
}
|
|
220
|
+
return raw
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
set<K extends keyof CliConfig>(key: K, value: CliConfig[K]): void {
|
|
224
|
+
if (ENCRYPTED_KEYS.has(key) && typeof value === 'string' && value) {
|
|
225
|
+
this.config[key] = this.lockEncrypt(value) as CliConfig[K]
|
|
226
|
+
} else if ((key === 'dbPath' || key === 'dbPath3x' || key === 'ntDbPath') && typeof value === 'string') {
|
|
227
|
+
this.config[key] = expandHomePath(value) as CliConfig[K]
|
|
228
|
+
} else {
|
|
229
|
+
this.config[key] = value
|
|
230
|
+
}
|
|
231
|
+
this.dirty.add(key)
|
|
232
|
+
this.save()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
getAll(): ConfigData {
|
|
236
|
+
return {
|
|
237
|
+
dbPath: this.get('dbPath'),
|
|
238
|
+
wxid: this.get('wxid'),
|
|
239
|
+
decryptKey: this.get('decryptKey'),
|
|
240
|
+
decryptKey3x: this.get('decryptKey3x'),
|
|
241
|
+
dataVersion: (this.config.dataVersion || '4.x') as ConfigData['dataVersion'],
|
|
242
|
+
dbPath3x: this.get('dbPath3x'),
|
|
243
|
+
ntDbPath: this.config.ntDbPath,
|
|
244
|
+
ntKey: this.get('ntKey'),
|
|
245
|
+
ntSalt: this.config.ntSalt,
|
|
246
|
+
contactDbPath: this.config.contactDbPath,
|
|
247
|
+
contactKey: this.get('contactKey'),
|
|
248
|
+
contactSalt: this.config.contactSalt,
|
|
249
|
+
snsDbPath: this.config.snsDbPath,
|
|
250
|
+
snsKey: this.get('snsKey'),
|
|
251
|
+
snsSalt: this.config.snsSalt,
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
getWhitelist(): string[] {
|
|
256
|
+
return this.config.whitelist || []
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
setWhitelist(list: string[]): void {
|
|
260
|
+
this.config.whitelist = list
|
|
261
|
+
this.dirty.add('whitelist')
|
|
262
|
+
this.save()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
getWhitelistEntries(): ListEntry[] {
|
|
266
|
+
return this.config.whitelistEntries || []
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
setWhitelistEntries(entries: ListEntry[]): void {
|
|
270
|
+
this.config.whitelistEntries = entries
|
|
271
|
+
// 同步 string[] 视图, 供旧代码使用
|
|
272
|
+
this.config.whitelist = entries.map(e => e.wxid)
|
|
273
|
+
this.dirty.add('whitelistEntries')
|
|
274
|
+
this.dirty.add('whitelist')
|
|
275
|
+
this.save()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getBlacklist(): string[] {
|
|
279
|
+
return this.config.blacklist || []
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setBlacklist(list: string[]): void {
|
|
283
|
+
this.config.blacklist = list
|
|
284
|
+
this.dirty.add('blacklist')
|
|
285
|
+
this.save()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
getBlacklistEntries(): ListEntry[] {
|
|
289
|
+
return this.config.blacklistEntries || []
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
setBlacklistEntries(entries: ListEntry[]): void {
|
|
293
|
+
this.config.blacklistEntries = entries
|
|
294
|
+
this.config.blacklist = entries.map(e => e.wxid)
|
|
295
|
+
this.dirty.add('blacklistEntries')
|
|
296
|
+
this.dirty.add('blacklist')
|
|
297
|
+
this.save()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** 读取持久化的 context_token: wxid -> token */
|
|
301
|
+
getContextTokens(): Record<string, string> {
|
|
302
|
+
const raw = this.config.wechatOcContextTokens || ''
|
|
303
|
+
if (!raw) return {}
|
|
304
|
+
const json = raw.startsWith(LOCK_PREFIX) ? this.lockDecrypt(raw) : raw
|
|
305
|
+
if (!json) return {}
|
|
306
|
+
try {
|
|
307
|
+
const obj = JSON.parse(json)
|
|
308
|
+
return (obj && typeof obj === 'object') ? obj as Record<string, string> : {}
|
|
309
|
+
} catch {
|
|
310
|
+
return {}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** 持久化 context_token 集合 (加密存储) */
|
|
315
|
+
setContextTokens(tokens: Record<string, string>): void {
|
|
316
|
+
const json = JSON.stringify(tokens || {})
|
|
317
|
+
this.config.wechatOcContextTokens = json ? this.lockEncrypt(json) : ''
|
|
318
|
+
this.dirty.add('wechatOcContextTokens')
|
|
319
|
+
this.save()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** 增量更新单个 wxid 的 context_token */
|
|
323
|
+
upsertContextToken(wxid: string, token: string): void {
|
|
324
|
+
if (!wxid || !token) return
|
|
325
|
+
const tokens = this.getContextTokens()
|
|
326
|
+
if (tokens[wxid] === token) return
|
|
327
|
+
tokens[wxid] = token
|
|
328
|
+
this.setContextTokens(tokens)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
isConfigured(): boolean {
|
|
332
|
+
const has4x = !!(this.config.dbPath && this.config.decryptKey)
|
|
333
|
+
const has3x = !!(this.config.dbPath3x && this.config.decryptKey3x)
|
|
334
|
+
const hasNt = !!(this.config.ntDbPath && this.config.ntKey && this.config.ntSalt)
|
|
335
|
+
return has4x || has3x || hasNt
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
clearDatabaseKeys(): void {
|
|
339
|
+
this.clearDatabaseKeysInMemory(true)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
maskDatabaseKeysForTesting(): void {
|
|
343
|
+
this.clearDatabaseKeysInMemory(false)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private clearDatabaseKeysInMemory(persist: boolean): void {
|
|
347
|
+
const keyFields: Array<keyof CliConfig> = [
|
|
348
|
+
'decryptKey', 'decryptKey3x', 'ntKey', 'ntSalt',
|
|
349
|
+
'contactKey', 'contactSalt', 'snsKey', 'snsSalt',
|
|
350
|
+
'favKey', 'favPassphrase',
|
|
351
|
+
]
|
|
352
|
+
for (const key of keyFields) {
|
|
353
|
+
this.config[key] = '' as never
|
|
354
|
+
if (persist) this.dirty.add(key)
|
|
355
|
+
}
|
|
356
|
+
if (persist) this.save()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
clear(): void {
|
|
360
|
+
this.config = { dbPath: '', wxid: '', decryptKey: '', decryptKey3x: '', dataVersion: '', dbPath3x: '', ntDbPath: '', ntKey: '', ntSalt: '', contactDbPath: '', contactKey: '', contactSalt: '', wechatOcToken: '', wechatOcAccountId: '', wechatOcBaseUrl: '', wechatOcSyncBuf: '', wechatOcContextTokens: '', whitelist: [], blacklist: [], whitelistEntries: [], blacklistEntries: [], vaultRepo: '', aiEngine: 'deepseek', wereadApiKey: '', deepseekApiKey: '', typesafeApiKey: '', dashscopeApiKey: '', snsDbPath: '', snsKey: '', snsSalt: '', favDbPath: '', favKey: '', favPassphrase: '', emoticonSeed: '', assistantPrivacy: 'strict', localModel: '', aiBaseUrl: '', aiModel: '', assistantWhitelist: '', assistantGroupWhitelist: '', assistantGroupRequireMention: 'true', assistantFastRoute: 'off', assistantPanelUser: '', quickReplyContacts: '', dailySources: '', dailySourceCategories: '', dailyExcludeTopics: '', dailyAiEnabled: 'true' }
|
|
361
|
+
// clear 意图是全量重置: 所有字段标记为脏, 覆盖磁盘上的全部旧值
|
|
362
|
+
this.dirty = new Set(Object.keys(this.config) as (keyof CliConfig)[])
|
|
363
|
+
this.save()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private lockEncrypt(plaintext: string): string {
|
|
367
|
+
const machineId = this.getMachineId()
|
|
368
|
+
const salt = crypto.randomBytes(16)
|
|
369
|
+
const iv = crypto.randomBytes(12)
|
|
370
|
+
const derivedKey = crypto.pbkdf2Sync(machineId, salt, 100000, 32, 'sha256')
|
|
371
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', derivedKey, iv)
|
|
372
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
|
|
373
|
+
const authTag = cipher.getAuthTag()
|
|
374
|
+
const combined = Buffer.concat([salt, iv, authTag, encrypted])
|
|
375
|
+
return LOCK_PREFIX + combined.toString('base64')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private lockDecrypt(stored: string): string {
|
|
379
|
+
if (!stored || !stored.startsWith(LOCK_PREFIX)) return stored
|
|
380
|
+
try {
|
|
381
|
+
const machineId = this.getMachineId()
|
|
382
|
+
const combined = Buffer.from(stored.slice(LOCK_PREFIX.length), 'base64')
|
|
383
|
+
const salt = combined.subarray(0, 16)
|
|
384
|
+
const iv = combined.subarray(16, 28)
|
|
385
|
+
const authTag = combined.subarray(28, 44)
|
|
386
|
+
const ciphertext = combined.subarray(44)
|
|
387
|
+
const derivedKey = crypto.pbkdf2Sync(machineId, salt, 100000, 32, 'sha256')
|
|
388
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', derivedKey, iv)
|
|
389
|
+
decipher.setAuthTag(authTag)
|
|
390
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()])
|
|
391
|
+
return decrypted.toString('utf8')
|
|
392
|
+
} catch {
|
|
393
|
+
return ''
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private getMachineId(): string {
|
|
398
|
+
// 使用机器名 + 用户名作为机器标识,足够绑定到单台机器
|
|
399
|
+
return `${hostname()}-${userInfo().username}-weflow-cli`
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export const configService = new ConfigService()
|