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.
- package/CHANGELOG.md +42 -0
- package/README.md +16 -4
- package/bin/weflow-cli.ts +348 -6
- package/dist/bin/weflow-cli.js +381 -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/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/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 +92 -30
- 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/POSITIONING.md +68 -68
- package/docs/PROJECT_STATE.md +6 -3
- package/docs/REPLAY.md +169 -0
- package/docs/SYNC_CONTRACT.md +197 -180
- package/package.json +89 -88
- 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/js/graph3d/viewer2d.js +550 -448
- package/resources/panel/quick-menu.cjs +197 -197
- package/scripts/_utils.py +1338 -1338
- package/scripts/article_notes.py +423 -423
- package/scripts/chat_notes.py +86 -3
- package/scripts/check_readme_commands.py +427 -426
- package/scripts/contact_schema.py +339 -339
- package/scripts/create_reading_notes.py +11 -11
- package/scripts/daily_task.ps1 +213 -213
- package/scripts/fav_notes.py +321 -321
- package/scripts/graph_2d.py +156 -151
- package/scripts/graph_3d.py +672 -646
- package/scripts/health_check.py +473 -473
- package/scripts/imported_chats.py +68 -0
- package/scripts/install_tasks.ps1 +105 -105
- package/scripts/leads.py +179 -179
- package/scripts/network.py +206 -206
- package/scripts/nt_decrypt.py +135 -1
- package/scripts/on_this_day.py +178 -178
- package/scripts/opinion_timeline.py +138 -138
- package/scripts/promises.py +185 -185
- package/scripts/replay.py +803 -0
- package/scripts/run_hidden.vbs +25 -0
- package/scripts/semantic_search.py +73 -5
- 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/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/qchatImport.ts +327 -0
- package/src/services/setupVenv.ts +238 -0
- package/src/services/syncService.ts +363 -363
- package/src/services/syncState.ts +275 -275
- package/src/services/wechatMedia.ts +166 -166
- package/src/utils/python.ts +33 -4
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `weflow-cli setup` —— 自管 Python venv。
|
|
3
|
+
*
|
|
4
|
+
* Issues 里最大的两类环境噪音:PowerShell 执行策略拦 `.ps1`(.cmd 包装绕开),
|
|
5
|
+
* 和 PATH 上只有微软商店的 python 占位程序(一跑 exit 9009,`getPythonCommand()`
|
|
6
|
+
* 兜底返回裸名 `python` 时正好交给它)。后者真正的根因是"这台机器没有一个能
|
|
7
|
+
* `import sqlcipher3` 的 Python"——本命令一次性解决:在 `~/.weflow-cli/venv`
|
|
8
|
+
* 建一个 venv、装上 requirements,之后 `getPythonCommand()` 优先用它。
|
|
9
|
+
*
|
|
10
|
+
* Windows 上 `sqlcipher3` 被替换为 `sqlcipher3-wheels`(预编译轮,import 名
|
|
11
|
+
* 相同):源码包要 OpenSSL 头文件 + MSVC,正是要消掉的那类支持负担。替换会
|
|
12
|
+
* 写进计划与输出,不藏在背后。
|
|
13
|
+
*/
|
|
14
|
+
import { execFile } from 'child_process'
|
|
15
|
+
import { promisify } from 'util'
|
|
16
|
+
import { existsSync, readFileSync, rmSync } from 'fs'
|
|
17
|
+
import { basename, join } from 'path'
|
|
18
|
+
import { homedir } from 'os'
|
|
19
|
+
import { resolvePackageRoot } from '../utils/packageRoot.js'
|
|
20
|
+
import { clearPythonCommandCache, getPythonCommand } from '../utils/python.js'
|
|
21
|
+
|
|
22
|
+
const execFileAsync = promisify(execFile)
|
|
23
|
+
|
|
24
|
+
export const VENV_DIR = join(homedir(), '.weflow-cli', 'venv')
|
|
25
|
+
|
|
26
|
+
export interface SetupPlan {
|
|
27
|
+
venvDir: string
|
|
28
|
+
venvPython: string
|
|
29
|
+
/** 用来建 venv 的解释器;null = 这台机器连可运行的 Python 都没有 */
|
|
30
|
+
creator: string | null
|
|
31
|
+
recreate: boolean
|
|
32
|
+
requirementFiles: string[]
|
|
33
|
+
/** 解析后的安装清单(注释/空行已去掉,win32 的替换已做) */
|
|
34
|
+
packages: string[]
|
|
35
|
+
/** 替换说明,如 `sqlcipher3 -> sqlcipher3-binary (Windows 预编译轮)` */
|
|
36
|
+
substitutions: string[]
|
|
37
|
+
createsUnder: '~/.weflow-cli/venv'
|
|
38
|
+
network: true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SetupStep {
|
|
42
|
+
name: string
|
|
43
|
+
ok: boolean
|
|
44
|
+
/** 一步的补充事实(版本号、跳过原因等),不放大段日志 */
|
|
45
|
+
detail?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SetupResult {
|
|
49
|
+
success: boolean
|
|
50
|
+
steps: SetupStep[]
|
|
51
|
+
venvPython: string
|
|
52
|
+
/** 安装完清缓存后 getPythonCommand() 的实际选择——应等于 venvPython */
|
|
53
|
+
effectivePython: string | null
|
|
54
|
+
error?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** requirements 文件 → 去注释/空行的依赖列表。 */
|
|
58
|
+
export function parseRequirements(text: string): string[] {
|
|
59
|
+
return text
|
|
60
|
+
.split('\n')
|
|
61
|
+
.map(line => line.trim())
|
|
62
|
+
.filter(line => line && !line.startsWith('#'))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** win32 下把源码包换成预编译轮;其余平台原样。 */
|
|
66
|
+
export function resolvePackages(names: string[], platform: string): { packages: string[]; substitutions: string[] } {
|
|
67
|
+
if (platform !== 'win32') return { packages: [...names], substitutions: [] }
|
|
68
|
+
// Windows 上的预编译轮是 `sqlcipher3-wheels`(cp310+ 的 win32/amd64/arm64 都有)。
|
|
69
|
+
// `sqlcipher3-binary` 只有 Linux/macOS 轮——CI 的 ubuntu 能装它,Windows 上
|
|
70
|
+
// pip 直接报 "No matching distribution"(2026-10-23 实测)。
|
|
71
|
+
const packages = names.map(n => (n === 'sqlcipher3' ? 'sqlcipher3-wheels' : n))
|
|
72
|
+
const substitutions = packages.some(p => p === 'sqlcipher3-wheels') && names.some(n => n === 'sqlcipher3')
|
|
73
|
+
? ['sqlcipher3 -> sqlcipher3-wheels (Windows 预编译轮,import 名相同;源码包要 OpenSSL 头文件 + MSVC)']
|
|
74
|
+
: []
|
|
75
|
+
return { packages, substitutions }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 建一个"能用来创建 venv"的解释器探测:PATH 上的 python/py 逐个 `--version`。
|
|
80
|
+
* 商店占位程序在这一步就会被排掉(`--version` 以 9009 退出)。
|
|
81
|
+
*/
|
|
82
|
+
async function findCreator(): Promise<{ creator: string | null; version: string | null }> {
|
|
83
|
+
const names = process.platform === 'win32'
|
|
84
|
+
? ['python', 'python3', 'py']
|
|
85
|
+
: ['python3', 'python']
|
|
86
|
+
for (const name of names) {
|
|
87
|
+
try {
|
|
88
|
+
const { stdout } = await execFileAsync(name, ['--version'], { windowsHide: true, timeout: 10_000 })
|
|
89
|
+
const version = stdout.trim().split('\n')[0] || null
|
|
90
|
+
return { creator: name, version }
|
|
91
|
+
} catch {
|
|
92
|
+
// 这个名字跑不起来(商店占位程序 9009 也会落到这里),试下一个
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { creator: null, version: null }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function planSetup(opts: {
|
|
99
|
+
recreate?: boolean
|
|
100
|
+
withVoice?: boolean
|
|
101
|
+
with3x?: boolean
|
|
102
|
+
} = {}): Promise<SetupPlan> {
|
|
103
|
+
const pkgRoot = resolvePackageRoot(import.meta.url)
|
|
104
|
+
const files = ['requirements.txt']
|
|
105
|
+
if (opts.with3x) files.push('requirements-3x.txt')
|
|
106
|
+
if (opts.withVoice) files.push('requirements-voice.txt')
|
|
107
|
+
|
|
108
|
+
const names: string[] = []
|
|
109
|
+
for (const f of files) {
|
|
110
|
+
names.push(...parseRequirements(readFileSync(join(pkgRoot, f), 'utf-8')))
|
|
111
|
+
}
|
|
112
|
+
const { packages, substitutions } = resolvePackages([...new Set(names)], process.platform)
|
|
113
|
+
const { creator } = await findCreator()
|
|
114
|
+
const venvPython = process.platform === 'win32'
|
|
115
|
+
? join(VENV_DIR, 'Scripts', 'python.exe')
|
|
116
|
+
: join(VENV_DIR, 'bin', 'python')
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
venvDir: VENV_DIR,
|
|
120
|
+
venvPython,
|
|
121
|
+
creator,
|
|
122
|
+
recreate: !!opts.recreate,
|
|
123
|
+
requirementFiles: files,
|
|
124
|
+
packages,
|
|
125
|
+
substitutions,
|
|
126
|
+
createsUnder: '~/.weflow-cli/venv',
|
|
127
|
+
network: true,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function runSetup(plan: SetupPlan, onLine?: (line: string) => void): Promise<SetupResult> {
|
|
132
|
+
const steps: SetupStep[] = []
|
|
133
|
+
const say = (line: string) => onLine?.(line)
|
|
134
|
+
|
|
135
|
+
if (!plan.creator) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
steps: [{ name: 'find-python', ok: false, detail: 'PATH 上没有可运行的 Python(商店占位程序不算)——先装一个真正的 Python 3.10+ 再运行 setup' }],
|
|
139
|
+
venvPython: plan.venvPython,
|
|
140
|
+
effectivePython: null,
|
|
141
|
+
error: 'NO_USABLE_PYTHON',
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 1. 重建或创建 venv
|
|
146
|
+
if (plan.recreate && existsSync(plan.venvDir)) {
|
|
147
|
+
rmSync(plan.venvDir, { recursive: true, force: true })
|
|
148
|
+
steps.push({ name: 'remove-existing-venv', ok: true, detail: basename(plan.venvDir) })
|
|
149
|
+
say(`已删除旧 venv: ${plan.venvDir}`)
|
|
150
|
+
}
|
|
151
|
+
if (!existsSync(plan.venvDir)) {
|
|
152
|
+
try {
|
|
153
|
+
await execFileAsync(plan.creator, ['-m', 'venv', plan.venvDir], { windowsHide: true, timeout: 180_000 })
|
|
154
|
+
steps.push({ name: 'create-venv', ok: true, detail: `由 ${plan.creator} 创建` })
|
|
155
|
+
say(`已创建 venv: ${plan.venvDir}`)
|
|
156
|
+
} catch (e: any) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
steps: [...steps, { name: 'create-venv', ok: false, detail: String(e?.message || e).slice(0, 200) }],
|
|
160
|
+
venvPython: plan.venvPython,
|
|
161
|
+
effectivePython: null,
|
|
162
|
+
error: 'VENV_CREATE_FAILED',
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
steps.push({ name: 'create-venv', ok: true, detail: '已存在,跳过(--recreate 可重建)' })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 2. 装依赖(走 venv 自己的 pip;PyPI 网络请求就是这一步)。
|
|
170
|
+
// 失败重试一次:PyPI 的瞬时网络错误(本机 2026-10-23 实测过一次整批失败、
|
|
171
|
+
// 逐包重装全过)不值得让用户从头再来——重试前说一声。
|
|
172
|
+
const pipArgs = ['-m', 'pip', 'install', '--disable-pip-version-check', ...plan.packages]
|
|
173
|
+
const pipDetail = (e: unknown): string => {
|
|
174
|
+
const err = e as { stderr?: string; message?: string }
|
|
175
|
+
const tail = (err?.stderr || '').split('\n').map(l => l.trim()).filter(Boolean).slice(-3).join(' | ')
|
|
176
|
+
return (tail || String(err?.message || e)).slice(0, 300)
|
|
177
|
+
}
|
|
178
|
+
let installed = false
|
|
179
|
+
let pipError: unknown = null
|
|
180
|
+
for (let attempt = 1; attempt <= 2 && !installed; attempt++) {
|
|
181
|
+
try {
|
|
182
|
+
say(attempt === 1
|
|
183
|
+
? `安装 ${plan.packages.length} 个包(来自 ${plan.requirementFiles.join('、')})...`
|
|
184
|
+
: `重试安装(第 1 次失败:${pipDetail(pipError)})...`)
|
|
185
|
+
await execFileAsync(plan.venvPython, pipArgs, {
|
|
186
|
+
windowsHide: true,
|
|
187
|
+
timeout: 600_000,
|
|
188
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
189
|
+
})
|
|
190
|
+
installed = true
|
|
191
|
+
} catch (e) {
|
|
192
|
+
pipError = e
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!installed) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
steps: [...steps, { name: 'pip-install', ok: false, detail: pipDetail(pipError) }],
|
|
199
|
+
venvPython: plan.venvPython,
|
|
200
|
+
effectivePython: null,
|
|
201
|
+
error: 'PIP_INSTALL_FAILED',
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
steps.push({ name: 'pip-install', ok: true, detail: `${plan.packages.length} 个包(${plan.requirementFiles.join('、')})` })
|
|
205
|
+
for (const s of plan.substitutions) say(` 替换: ${s}`)
|
|
206
|
+
|
|
207
|
+
// 3. 验收:venv 里真能 import sqlcipher3(NT 解密的核心依赖)
|
|
208
|
+
let verified = false
|
|
209
|
+
try {
|
|
210
|
+
await execFileAsync(plan.venvPython, ['-c', 'import sqlcipher3'], { windowsHide: true, timeout: 30_000 })
|
|
211
|
+
verified = true
|
|
212
|
+
} catch {
|
|
213
|
+
verified = false
|
|
214
|
+
}
|
|
215
|
+
steps.push({
|
|
216
|
+
name: 'verify-sqlcipher3',
|
|
217
|
+
ok: verified,
|
|
218
|
+
detail: verified ? 'import sqlcipher3 通过' : 'import sqlcipher3 失败——pip 报成功但模块不可用,把上面 pip 的完整输出贴到 issue',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// 4. 确认运行时真的会选它(清掉模块级缓存再看选择)
|
|
222
|
+
clearPythonCommandCache()
|
|
223
|
+
const effective = getPythonCommand()
|
|
224
|
+
const wiringOk = existsSync(plan.venvPython) && effective === plan.venvPython
|
|
225
|
+
steps.push({
|
|
226
|
+
name: 'runtime-prefers-venv',
|
|
227
|
+
ok: wiringOk,
|
|
228
|
+
detail: wiringOk ? 'getPythonCommand() 已指向自管 venv' : `getPythonCommand() 选了 ${effective}——venv 未通过探测(sqlcipher3 不可用时按原顺序回退 PATH)`,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
success: steps.every(s => s.ok),
|
|
233
|
+
steps,
|
|
234
|
+
venvPython: plan.venvPython,
|
|
235
|
+
effectivePython: effective,
|
|
236
|
+
...(verified && !wiringOk ? { error: 'VENV_NOT_PREFERRED' } : {}),
|
|
237
|
+
}
|
|
238
|
+
}
|