dsh-plugin-pscad-expert-vyma 1.0.8 → 1.0.9
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/README.md +1 -1
- package/index.js +75 -3
- package/package.json +1 -1
- package/plugin.yaml +1 -1
package/README.md
CHANGED
package/index.js
CHANGED
|
@@ -15,27 +15,99 @@
|
|
|
15
15
|
|
|
16
16
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
17
17
|
import Schema from '@deepseek-ai/schemastery'
|
|
18
|
-
import { spawn } from 'node:child_process'
|
|
18
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
19
19
|
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import fs from 'node:fs'
|
|
21
|
+
import path from 'node:path'
|
|
20
22
|
|
|
21
23
|
export const name = 'pscad-expert-bridge'
|
|
22
24
|
export const inject = ['tools']
|
|
23
25
|
|
|
24
26
|
// ---------------- 插件配置(Schemastery,无硬编码) ----------------
|
|
25
27
|
export const Config = Schema.object({
|
|
26
|
-
pythonExecutable: Schema.string().default('python')
|
|
28
|
+
pythonExecutable: Schema.string().default('python')
|
|
29
|
+
.description('Python 解释器路径;默认自动探测(PATH→py 启动器→常见安装位置),无需手动配置'),
|
|
27
30
|
toolsCli: Schema.string().default('./tools/cli.py'),
|
|
28
31
|
defaultFolder: Schema.string().default(''),
|
|
29
32
|
timeoutMs: Schema.number().default(120000),
|
|
30
33
|
})
|
|
31
34
|
|
|
35
|
+
// ---------------- Python 解释器自动探测(免 profile YAML 配置) ----------------
|
|
36
|
+
let _resolvedPython = null
|
|
37
|
+
|
|
38
|
+
function pyVersionOk(exe) {
|
|
39
|
+
try {
|
|
40
|
+
const r = spawnSync(exe, ['-c', 'import sys; print(sys.version_info[0], sys.version_info[1])'], {
|
|
41
|
+
timeout: 10000, windowsHide: true, encoding: 'utf8',
|
|
42
|
+
})
|
|
43
|
+
if (r.status !== 0 || r.error) return false
|
|
44
|
+
const m = /^(\d+)\s+(\d+)/.exec((r.stdout || '').trim())
|
|
45
|
+
return !!m && Number(m[1]) >= 3 && Number(m[2]) >= 7
|
|
46
|
+
} catch { return false }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function findPythonInDir(parent, prefix) {
|
|
50
|
+
// 升序取最旧版本(越接近 3.7 越稳妥:mhi.pscad 2.2.1 面向 Python 3.7)
|
|
51
|
+
try {
|
|
52
|
+
const dirs = fs.readdirSync(parent, { withFileTypes: true })
|
|
53
|
+
.filter(e => e.isDirectory() && e.name.startsWith(prefix))
|
|
54
|
+
.map(e => e.name).sort()
|
|
55
|
+
for (const d of dirs) {
|
|
56
|
+
const exe = path.join(parent, d, 'python.exe')
|
|
57
|
+
if (fs.existsSync(exe)) return exe
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolvePython(config) {
|
|
64
|
+
if (_resolvedPython) return _resolvedPython
|
|
65
|
+
// 用户显式配置(非默认 'python')优先,尊重覆盖
|
|
66
|
+
if (config.pythonExecutable && config.pythonExecutable !== 'python') {
|
|
67
|
+
_resolvedPython = config.pythonExecutable
|
|
68
|
+
return _resolvedPython
|
|
69
|
+
}
|
|
70
|
+
// 1) PATH 上的 python:验证是真实 3.7+(排除微软商店占位 stub,其退出码 9009/2)
|
|
71
|
+
if (pyVersionOk('python')) { _resolvedPython = 'python'; return _resolvedPython }
|
|
72
|
+
// 2) py 启动器枚举已安装版本:取最接近 3.7 的可用版本
|
|
73
|
+
try {
|
|
74
|
+
const r = spawnSync('py', ['-0p'], { timeout: 10000, windowsHide: true, encoding: 'utf8' })
|
|
75
|
+
if (r.status === 0) {
|
|
76
|
+
const picks = []
|
|
77
|
+
for (const line of (r.stdout || '').split(/\r?\n/)) {
|
|
78
|
+
// 兼容含空格路径(如 C:\Program Files\Python37\python.exe)
|
|
79
|
+
const m = /-V:(\d+)\.(\d+)\s+\*?\s*(.+\\python\.exe)\s*$/.exec(line)
|
|
80
|
+
if (m && fs.existsSync(m[3])) {
|
|
81
|
+
const v = Number(m[1]) * 100 + Number(m[2])
|
|
82
|
+
if (Number(m[1]) >= 3 && v >= 307) picks.push({ v, exe: m[3] })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (picks.length) {
|
|
86
|
+
picks.sort((a, b) => a.v - b.v)
|
|
87
|
+
_resolvedPython = picks[0].exe
|
|
88
|
+
return _resolvedPython
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
// 3) 常见安装位置(Program Files / 用户级 / 盘符根)
|
|
93
|
+
const home = process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || '', 'AppData', 'Local')
|
|
94
|
+
const found = findPythonInDir('C:/Program Files', 'Python3') ||
|
|
95
|
+
findPythonInDir(path.join(home, 'Programs', 'Python'), 'Python3') ||
|
|
96
|
+
findPythonInDir('C:/', 'Python3')
|
|
97
|
+
if (found) { _resolvedPython = found; return _resolvedPython }
|
|
98
|
+
// 4) 兜底:保持默认(若 PATH python 为 stub,错误会自然暴露并有明确提示)
|
|
99
|
+
_resolvedPython = 'python'
|
|
100
|
+
return _resolvedPython
|
|
101
|
+
}
|
|
102
|
+
|
|
32
103
|
// ---------------- Python 桥接(子进程 JSON-RPC,支持取消) ----------------
|
|
33
104
|
function runPy(tool, args, config, signal) {
|
|
34
105
|
const cliPath = fileURLToPath(new URL(config.toolsCli, import.meta.url))
|
|
106
|
+
const python = resolvePython(config)
|
|
35
107
|
return new Promise((resolve, reject) => {
|
|
36
108
|
let settled = false
|
|
37
109
|
const done = (fn, v) => { if (!settled) { settled = true; fn(v) } }
|
|
38
|
-
const proc = spawn(
|
|
110
|
+
const proc = spawn(python, [cliPath, tool, JSON.stringify(args)], {
|
|
39
111
|
windowsHide: true,
|
|
40
112
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
41
113
|
})
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED