@xlight-oss/visionary-dsh 0.6.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/lib/index.mjs +78 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -31,13 +31,15 @@ dsh plugin --profile web add /path/to/packages/dsh-plugin
31
31
 
32
32
  ## 前置要求
33
33
 
34
- `visionary-server` 二进制需可被找到(三者任一):
34
+ `visionary-server` 二进制需可被找到(三者任一,按优先序):
35
35
 
36
36
  1. `Config.binaryPath`(插件配置,绝对路径)
37
37
  2. `DEEPSEEK_VISIONARY_BIN` 环境变量
38
- 3. 在 PATH
38
+ 3. 在 PATH 中(Windows 额外支持 npm 全局包的 `.cmd` / `.ps1` shim——插件自动解析 shim 定位包内 `node_modules/.bin_real/visionary-server.exe` 真身)
39
39
 
40
- 安装二进制见 [DeepSeek Visionary 安装章节](https://github.com/xlight/deepseek-visionary#安装)(install.sh / brew / npm)。未找到时工具返回含安装指引的错误。
40
+ 二进制路径在**每次工具调用时**重新解析(懒解析):修改 PATH 或设置 `DEEPSEEK_VISIONARY_BIN` 后无需重启 DSH 即生效。
41
+
42
+ 安装二进制见 [DeepSeek Visionary 安装章节](https://github.com/xlight/deepseek-visionary#安装)(install.sh / brew / npm)。未找到时工具返回含安装指引的错误(Windows 提示 npm / binaryPath 指引)。
41
43
 
42
44
  ## 配置
43
45
 
package/lib/index.mjs CHANGED
@@ -15,7 +15,7 @@
15
15
  import { defineTool } from "@deepseek-ai/dsh-tools";
16
16
  import z from "@deepseek-ai/schemastery";
17
17
  import { spawn } from "node:child_process";
18
- import { statSync } from "node:fs";
18
+ import { statSync, readFileSync } from "node:fs";
19
19
  import { promises as fs } from "node:fs";
20
20
  import os from "node:os";
21
21
  import path from "node:path";
@@ -52,6 +52,43 @@ const Config = z.object({
52
52
 
53
53
  // --- binary resolution -------------------------------------------------------
54
54
 
55
+ // npm 全局安装(@xlight-oss/visionary-server)在 Windows 上只在 PATH 生成
56
+ // .cmd/.ps1 shim(node 包装脚本),真实 exe 在包内 node_modules/.bin_real/。
57
+ // shim 层用 spawnSync + stdio:"inherit" 转发——直接 spawn shim 会丢 stdout 管道、
58
+ // kill 链路断裂(孤儿进程)。故:解析 shim 文本定位 exe 真身,spawn 真身。
59
+ const NPM_PKG_SHIM_RE =
60
+ /node_modules[\\/]@xlight-oss[\\/]visionary-server[\\/]run-visionary-server\.js/;
61
+
62
+ // 从 npm shim(.cmd/.ps1)文本中解析出 exe 真身路径。
63
+ // shim 内容形如:... "%dp0%\node_modules\@xlight-oss\visionary-server\run-visionary-server.js" ...
64
+ // 包目录 = <shim目录>\node_modules\@xlight-oss\visionary-server
65
+ // 真身 = <包目录>\node_modules\.bin_real\visionary-server.exe
66
+ function resolveFromNpmShim(shimPath) {
67
+ let text;
68
+ try {
69
+ text = readFileSync(shimPath, "utf8");
70
+ } catch {
71
+ return null;
72
+ }
73
+ const m = NPM_PKG_SHIM_RE.exec(text);
74
+ if (!m) return null;
75
+ // shim 文本使用反斜杠分隔符(Windows 产物);手动切分保证在任意平台
76
+ // (包括测试跑的 macOS/Linux)都能解析,不依赖 path.dirname 的分隔符语义。
77
+ const pkgParts = m[0].split(/[\\/]+/).filter(Boolean);
78
+ if (pkgParts.length < 3) return null;
79
+ // node_modules\@xlight-oss\visionary-server\run-visionary-server.js → 去掉末段(文件名)
80
+ // npm shim 是 Windows 产物(.cmd/.ps1),包内 exe 恒为 visionary-server.exe,
81
+ // 与 process.platform 无关——固定扩展名保证任意平台测试/解析一致。
82
+ const pkgDir = path.join(path.dirname(shimPath), ...pkgParts.slice(0, -1));
83
+ const candidate = path.join(pkgDir, "node_modules", ".bin_real", "visionary-server.exe");
84
+ try {
85
+ if (statSync(candidate).isFile()) return candidate;
86
+ } catch {
87
+ // keep looking
88
+ }
89
+ return null;
90
+ }
91
+
55
92
  function resolveBinaryPath(config) {
56
93
  if (config.binaryPath) return config.binaryPath;
57
94
  const fromEnv = process.env.DEEPSEEK_VISIONARY_BIN;
@@ -65,17 +102,40 @@ function resolveBinaryPath(config) {
65
102
  // keep looking
66
103
  }
67
104
  }
105
+ // win32 追加:npm 全局包 shim(.cmd / .ps1)→ 解析 exe 真身
106
+ if (process.platform === "win32") {
107
+ for (const shimName of ["visionary-server.cmd", "visionary-server.ps1"]) {
108
+ for (const dir of (process.env.PATH || "").split(path.delimiter).filter(Boolean)) {
109
+ const shim = path.join(dir, shimName);
110
+ try {
111
+ if (statSync(shim).isFile()) {
112
+ const resolved = resolveFromNpmShim(shim);
113
+ if (resolved) return resolved;
114
+ }
115
+ } catch {
116
+ // keep looking
117
+ }
118
+ }
119
+ }
120
+ }
68
121
  return null;
69
122
  }
70
123
 
71
124
  const binaryMissingHelp = () =>
72
- [
73
- "visionary-server binary not found. Install it and retry:",
74
- " - One-liner: curl -LsSf https://github.com/xlight/deepseek-visionary/releases/latest/download/visionary-server-installer.sh | sh",
75
- " - Homebrew: brew install <tap>/visionary-server",
76
- " - npm: npm install -g @xlight-oss/visionary-server",
77
- "Or point the plugin at the binary via Config.binaryPath / DEEPSEEK_VISIONARY_BIN.",
78
- ].join("\n");
125
+ process.platform === "win32"
126
+ ? [
127
+ "visionary-server binary not found. Install it and retry:",
128
+ " - npm: npm install -g @xlight-oss/visionary-server (then restart DSH)",
129
+ " - or download from GitHub Releases: https://github.com/xlight/deepseek-visionary/releases/latest",
130
+ "Or point the plugin at the binary via Config.binaryPath / DEEPSEEK_VISIONARY_BIN.",
131
+ ].join("\n")
132
+ : [
133
+ "visionary-server binary not found. Install it and retry:",
134
+ " - One-liner: curl -LsSf https://github.com/xlight/deepseek-visionary/releases/latest/download/visionary-server-installer.sh | sh",
135
+ " - Homebrew: brew install <tap>/visionary-server",
136
+ " - npm: npm install -g @xlight-oss/visionary-server",
137
+ "Or point the plugin at the binary via Config.binaryPath / DEEPSEEK_VISIONARY_BIN.",
138
+ ].join("\n");
79
139
 
80
140
  // --- subprocess --------------------------------------------------------------
81
141
 
@@ -172,16 +232,20 @@ function parseMinor(v) {
172
232
  // --- tools -------------------------------------------------------------------
173
233
 
174
234
  function apply(ctx, config) {
175
- const binary = resolveBinaryPath(config);
176
235
  const loginSeconds = (() => {
177
236
  const raw = Number(process.env.DEEPSEEK_LOGIN_TIMEOUT);
178
237
  if (Number.isFinite(raw) && raw > 0) return raw;
179
238
  return config.loginTimeoutSeconds > 0 ? config.loginTimeoutSeconds : 600;
180
239
  })();
181
240
 
241
+ // 版本探测:apply 时 fire-and-forget(仅用于结果附带版本警告)。
242
+ // 注意:二进制路径【不在此缓存】——每次工具调用经 requireBinary()
243
+ // 重新 resolveBinaryPath(config),用户修改 PATH / DEEPSEEK_VISIONARY_BIN
244
+ // 后无需重启 DSH 即生效(懒解析,成本为数次 statSync)。
182
245
  let versionInfo = { known: false, compatible: true, version: "" };
183
- if (binary) {
184
- runCli(binary, ["--version"], { timeoutMs: 5000 })
246
+ const probeBinary = resolveBinaryPath(config);
247
+ if (probeBinary) {
248
+ runCli(probeBinary, ["--version"], { timeoutMs: 5000 })
185
249
  .then((r) => {
186
250
  const version = (r.stdout || r.stderr).trim();
187
251
  versionInfo = {
@@ -219,7 +283,9 @@ function apply(ctx, config) {
219
283
  });
220
284
  }
221
285
 
286
+ // 懒解析:每次工具调用重新定位二进制(PATH / 环境变量改动即时生效)。
222
287
  const requireBinary = () => {
288
+ const binary = resolveBinaryPath(config);
223
289
  if (!binary) throw new Error(binaryMissingHelp());
224
290
  return binary;
225
291
  };
@@ -412,4 +478,4 @@ function apply(ctx, config) {
412
478
  );
413
479
  }
414
480
 
415
- export { name, inject, Config, apply };
481
+ export { name, inject, Config, apply, resolveBinaryPath, resolveFromNpmShim };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xlight-oss/visionary-dsh",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "DeepSeek Visionary native plugin for DeepSeek Harness: deepseek_vision / status / login / logout native tools plus the text-model image bridge, all backed by the visionary-server CLI (DeepSeek web vision model, no API key).",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",