@deepseek-ai/dsh-native-command 0.1.7-rc.2 → 0.2.0-rc.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/README.i18n.yaml CHANGED
@@ -9,8 +9,8 @@
9
9
  en: 5ebca99c0488e1c8
10
10
  zh: 24b2f8f341ff648d
11
11
  /deepseek-ai-dsh-native-command/summary:
12
- en: 18e2f1f3798079dd
13
- zh: 6f5579374e208624
12
+ en: 8369dd3eb2af0781
13
+ zh: fa9dfd927cc8aaad
14
14
  /deepseek-ai-dsh-native-command/table-of-contents:
15
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
@@ -18,8 +18,8 @@
18
18
  en: aba695cba233905f
19
19
  zh: c6359da30eb8e372
20
20
  /deepseek-ai-dsh-native-command/use-this-package/running-a-command:
21
- en: 3a499bda33e33102
22
- zh: d01abbf863810607
21
+ en: 6dbc6c9bf6db34be
22
+ zh: 9ab16a86ca5abcc2
23
23
  /deepseek-ai-dsh-native-command/use-this-package/injecting-the-command-boundary:
24
24
  en: 987bed629737b59d
25
25
  zh: 258e30f813b0edcb
@@ -33,8 +33,8 @@
33
33
  en: a19bb4cb9f6de785
34
34
  zh: cc930d8facf1ca07
35
35
  /deepseek-ai-dsh-native-command/understand-the-implementation/what-execfile-gives-the-runner:
36
- en: b16da99b2ed73bfd
37
- zh: 5e806c31a6296366
36
+ en: ce6180ce8209db94
37
+ zh: 30000a1a073110e8
38
38
  /deepseek-ai-dsh-native-command/further-exploration:
39
39
  en: a3af562edb6e0385
40
40
  zh: e26e2c311c07d1c5
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-native-command` runs host executables without a shell and opens Host filesystem paths through the desktop. The command runner captures utf8 output, propagates cancellation, and hides transient Windows consoles. The path opener supports default-application and text-editor intents, browser-renderable documents, WSL translation, and desktop availability checks. It is a library, not a plugin: no `ctx`, no state, no events.
12
+ `dsh-native-command` runs host executables without a shell and opens Host filesystem paths through the desktop. The command runner captures utf8 output, propagates cancellation, and requires explicit Windows startup visibility. The path opener supports default-application and text-editor intents, browser-renderable documents, WSL translation, and desktop availability checks. It is a library, not a plugin: no `ctx`, no state, no events.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -34,10 +34,10 @@ import { runNativeCommand } from '@deepseek-ai/dsh-native-command'
34
34
 
35
35
  declare const script: string
36
36
  declare const signal: AbortSignal
37
- const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], signal)
37
+ const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], signal, 'hidden')
38
38
  ```
39
39
 
40
- On exit 0 the call resolves with captured stdout and stderr. On any failure it rejects with the exit `code` and both captured streams attached, so a caller can tell a missing tool (`ENOENT`), a cancellation (`ABORT_ERR`), and a real command failure apart without re-running the command.
40
+ On exit 0 the call resolves with captured stdout and stderr. On any failure it rejects with the exit `code` and both captured streams attached, so a caller can tell a missing tool (`ENOENT`), a cancellation (`ABORT_ERR`), and a real command failure apart without re-running the command. The required fourth argument, `window: 'hidden' | 'visible'`, controls Windows startup visibility and has no effect on other platforms. Background commands pass `'hidden'`; GUI launchers pass `'visible'`. The path opener selects `'visible'` for Explorer, including WSL handoffs and file selection, because hiding Explorer also hides its folder window.
41
41
 
42
42
  ### Injecting the command boundary
43
43
 
@@ -72,7 +72,7 @@ The command runner is a thin wrapper over Node's `execFile`. The path opener sel
72
72
 
73
73
  ### What execFile gives the runner
74
74
 
75
- `execFile` spawns the executable directly with an argv array — no shell string, no shell interpretation of the arguments. The `signal` option terminates the child when the caller's abort fires; `windowsHide` suppresses the transient console window on Windows. On a non-zero exit or spawn error, the callback attaches `code`, `stdout`, and `stderr` to the rejected error and keeps the original error as `cause`.
75
+ `execFile` spawns the executable directly with an argv array — no shell string, no shell interpretation of the arguments. The `signal` option terminates the child when the caller's abort fires; the runner maps `window` to Node's `windowsHide` option. On a non-zero exit or spawn error, the callback attaches `code`, `stdout`, and `stderr` to the rejected error and keeps the original error as `cause`.
76
76
 
77
77
  </details>
78
78
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-library"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-native-command` 无需 shell 即可运行 Host 可执行文件,并通过桌面打开 Host 文件系统路径。命令运行器捕获 utf8 输出、传播取消,并隐藏 Windows 瞬时控制台。路径打开器支持默认应用与文本编辑器意图、浏览器可渲染文档、WSL 转换与桌面可用性检查。它是库而非插件:没有 `ctx`、无状态、不发事件。
12
+ `dsh-native-command` 无需 shell 即可运行 Host 可执行文件,并通过桌面打开 Host 文件系统路径。命令运行器捕获 utf8 输出、传播取消,并要求显式指定 Windows 启动时的窗口可见性。路径打开器支持默认应用与文本编辑器意图、浏览器可渲染文档、WSL 转换与桌面可用性检查。它是库而非插件:没有 `ctx`、无状态、不发事件。
13
13
 
14
14
  ## 目录
15
15
 
@@ -34,10 +34,10 @@ import { runNativeCommand } from '@deepseek-ai/dsh-native-command'
34
34
 
35
35
  declare const script: string
36
36
  declare const signal: AbortSignal
37
- const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], signal)
37
+ const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], signal, 'hidden')
38
38
  ```
39
39
 
40
- 退出码为 0 时,调用解析为捕获到的 stdout 与 stderr。任何失败都会以错误拒绝,错误附带退出 `code` 与两路已捕获输出,因此调用方无需重跑命令即可区分工具缺失(`ENOENT`)、取消(`ABORT_ERR`)与真实的命令失败。
40
+ 退出码为 0 时,调用解析为捕获到的 stdout 与 stderr。任何失败都会以错误拒绝,错误附带退出 `code` 与两路已捕获输出,因此调用方无需重跑命令即可区分工具缺失(`ENOENT`)、取消(`ABORT_ERR`)与真实的命令失败。必填的第四个参数 `window: 'hidden' | 'visible'` 控制 Windows 启动时的窗口可见性,在其他平台不生效。后台命令传入 `'hidden'`,GUI 启动程序传入 `'visible'`。路径打开器为 Explorer 选择 `'visible'`,包括 WSL 转交和文件选中操作,因为隐藏 Explorer 也会隐藏它的文件夹窗口。
41
41
 
42
42
  ### 注入命令边界
43
43
 
@@ -72,7 +72,7 @@ const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], s
72
72
 
73
73
  ### execFile 给了运行器什么
74
74
 
75
- `execFile` 以 argv 数组直接 spawn 可执行文件——没有 shell 字符串,参数不经 shell 解释。`signal` 选项在调用方中止触发时终止子进程;`windowsHide` 在 Windows 上抑制瞬时控制台窗口。遇到非零退出或 spawn 错误时,回调把 `code`、`stdout`、`stderr` 挂到被拒绝的错误上,并保留原始错误作为 `cause`。
75
+ `execFile` 以 argv 数组直接 spawn 可执行文件——没有 shell 字符串,参数不经 shell 解释。`signal` 选项在调用方中止触发时终止子进程;`window` 由运行器映射为 Node 的 `windowsHide` 选项。遇到非零退出或 spawn 错误时,回调把 `code`、`stdout`、`stderr` 挂到被拒绝的错误上,并保留原始错误作为 `cause`。
76
76
 
77
77
  </details>
78
78
 
package/lib/index.js CHANGED
@@ -9,17 +9,19 @@ import { mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promise
9
9
  * @module @deepseek-ai/dsh-native-command/runner
10
10
  */
11
11
  /**
12
- * Run a host command with utf8 stdio, abort propagation, and Windows hide.
12
+ * Run a host command with utf8 stdio, abort propagation, and explicit GUI visibility.
13
13
  * @param command - executable path or PATH name.
14
14
  * @param args - argv (never a shell string).
15
15
  * @param signal - caller/connection lifetime; abort terminates the child.
16
+ * @param window - Windows startup visibility: `hidden` for background commands,
17
+ * `visible` for GUI launchers. Ignored on other platforms.
16
18
  * @returns captured stdout/stderr on exit 0.
17
19
  */
18
- const runNativeCommand = (command, args, signal) => new Promise((resolve, reject) => {
20
+ const runNativeCommand = (command, args, signal, window) => new Promise((resolve, reject) => {
19
21
  execFile(command, [...args], {
20
22
  encoding: "utf8",
21
23
  signal,
22
- windowsHide: true
24
+ windowsHide: window === "hidden"
23
25
  }, (error, stdout, stderr) => {
24
26
  if (error !== null) {
25
27
  reject(Object.assign(new Error(error.message, { cause: error }), {
@@ -77,7 +79,7 @@ async function openInBrowser(path, signal, platform, run, env) {
77
79
  if (platform === "darwin") {
78
80
  let bundle;
79
81
  try {
80
- const { stdout } = await run("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure"], signal);
82
+ const { stdout } = await run("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure"], signal, "hidden");
81
83
  bundle = macBundleForHttps(stdout);
82
84
  } catch {
83
85
  return false;
@@ -87,13 +89,13 @@ async function openInBrowser(path, signal, platform, run, env) {
87
89
  "-b",
88
90
  bundle,
89
91
  path
90
- ], signal);
92
+ ], signal, "hidden");
91
93
  return true;
92
94
  }
93
95
  if (platform === "linux") {
94
96
  const browser = env.BROWSER;
95
97
  if (browser === void 0 || browser === "") return false;
96
- await run(browser, [path], signal);
98
+ await run(browser, [path], signal, "hidden");
97
99
  return true;
98
100
  }
99
101
  return false;
@@ -140,7 +142,7 @@ function explorerTarget(windowsPath) {
140
142
  */
141
143
  async function runExplorer(args, signal, run) {
142
144
  try {
143
- await run("explorer.exe", args, signal);
145
+ await run("explorer.exe", args, signal, "visible");
144
146
  } catch (error) {
145
147
  signal.throwIfAborted();
146
148
  if (!(error instanceof Error) || !("code" in error) || error.code !== 1) throw error;
@@ -158,7 +160,7 @@ async function openWindowsPath(path, signal, run) {
158
160
  }
159
161
  /** Translate a WSL path before handing it to the Windows desktop. */
160
162
  async function openWslPath(path, signal, run) {
161
- const translated = await run("wslpath", ["-w", path], signal);
163
+ const translated = await run("wslpath", ["-w", path], signal, "hidden");
162
164
  signal.throwIfAborted();
163
165
  const windowsPath = translated.stdout.replace(/[\r\n]+$/, "");
164
166
  if (windowsPath === "") throw new Error("wslpath returned no Windows path");
@@ -172,7 +174,7 @@ async function openNativePathWithIntent(path, signal, intent, internals = {}) {
172
174
  const wsl = platform === "linux" && isWsl(internals);
173
175
  if (!wsl && intent === "default" && BROWSER_DOCUMENTS.has(extname(path).toLowerCase()) && await openInBrowser(path, signal, platform, run, env)) return;
174
176
  if (platform === "darwin") {
175
- await run("open", intent === "text-editor" ? ["-t", path] : [path], signal);
177
+ await run("open", intent === "text-editor" ? ["-t", path] : [path], signal, "hidden");
176
178
  return;
177
179
  }
178
180
  if (platform === "win32") {
@@ -184,7 +186,7 @@ async function openNativePathWithIntent(path, signal, intent, internals = {}) {
184
186
  await openWslPath(path, signal, run);
185
187
  return;
186
188
  }
187
- await run("xdg-open", [path], signal);
189
+ await run("xdg-open", [path], signal, "hidden");
188
190
  return;
189
191
  }
190
192
  throw new Error(`native path opener is unsupported on ${platform}`);
@@ -264,13 +266,13 @@ async function revealNativePath(path, signal, internals = {}) {
264
266
  platform
265
267
  });
266
268
  if (manager === "finder") {
267
- await run("open", ["-R", path], signal);
269
+ await run("open", ["-R", path], signal, "hidden");
268
270
  return;
269
271
  }
270
272
  if (manager === "explorer") {
271
273
  let windowsPath = path;
272
274
  if (platform === "linux") {
273
- const translated = await run("wslpath", ["-w", path], signal);
275
+ const translated = await run("wslpath", ["-w", path], signal, "hidden");
274
276
  signal.throwIfAborted();
275
277
  windowsPath = translated.stdout.replace(/[\r\n]+$/, "");
276
278
  if (windowsPath === "") throw new Error("wslpath returned no Windows path");
@@ -279,7 +281,7 @@ async function revealNativePath(path, signal, internals = {}) {
279
281
  return;
280
282
  }
281
283
  if (manager === "directory") {
282
- await run("xdg-open", [dirname(path)], signal);
284
+ await run("xdg-open", [dirname(path)], signal, "hidden");
283
285
  return;
284
286
  }
285
287
  throw new Error(`native file manager is unsupported on ${platform}`);
@@ -394,7 +396,7 @@ async function linuxFileApplications(path, signal, run, env) {
394
396
  "-a",
395
397
  "standard::content-type",
396
398
  path
397
- ], signal);
399
+ ], signal, "hidden");
398
400
  const mime = /standard::content-type:\s*(\S+)/.exec(info.stdout)?.[1];
399
401
  if (mime === void 0) throw new Error("GIO did not identify the file content type");
400
402
  const result = await run("env", [
@@ -402,7 +404,7 @@ async function linuxFileApplications(path, signal, run, env) {
402
404
  "gio",
403
405
  "mime",
404
406
  mime
405
- ], signal);
407
+ ], signal, "hidden");
406
408
  const preferred = /^Default application.*:\s*(.+\.desktop)\s*$/m.exec(result.stdout)?.[1];
407
409
  const ids = [...new Set([...preferred === void 0 ? [] : [preferred], ...result.stdout.split(/\r?\n/).filter((line) => /^\s+.*\.desktop\s*$/.test(line)).map((line) => line.trim())])];
408
410
  const directories = desktopDataDirectories(env.HOME ?? homedir(), env);
@@ -693,7 +695,7 @@ ${application === null ? "ConvertTo-Json -InputObject @([DshFileAssociations]::L
693
695
  "Bypass",
694
696
  "-File",
695
697
  scriptPath
696
- ], signal)).stdout;
698
+ ], signal, "hidden")).stdout;
697
699
  } finally {
698
700
  await rm(directory, {
699
701
  recursive: true,
@@ -794,7 +796,7 @@ async function queryFileApplications(path, signal, internals, display) {
794
796
  MAC_APPLICATIONS,
795
797
  target.path,
796
798
  display ? "icons" : "handlers"
797
- ], signal);
799
+ ], signal, "hidden");
798
800
  const applications = parseMacApplications(JSON.parse(stdout));
799
801
  return display ? dedupeMacApplications(applications) : applications;
800
802
  }
@@ -898,19 +900,19 @@ async function openNativeFileApplication(path, application, signal, internals =
898
900
  "launch",
899
901
  application,
900
902
  path
901
- ], signal);
903
+ ], signal, "hidden");
902
904
  else await run("open", [
903
905
  "-a",
904
906
  application,
905
907
  path
906
- ], signal);
908
+ ], signal, "hidden");
907
909
  }
908
910
  /** Resolve the desktop that owns the file, including Windows applications reached from WSL. */
909
911
  async function desktopTarget(path, signal, internals) {
910
912
  signal.throwIfAborted();
911
913
  const platform = internals.platform ?? process.platform;
912
914
  if (platform === "linux" && nativeFileManager(internals) === "explorer") {
913
- const translated = await (internals.run ?? runNativeCommand)("wslpath", ["-w", path], signal);
915
+ const translated = await (internals.run ?? runNativeCommand)("wslpath", ["-w", path], signal, "hidden");
914
916
  signal.throwIfAborted();
915
917
  const windowsPath = translated.stdout.replace(/[\r\n]+$/, "");
916
918
  if (windowsPath === "") throw new Error("wslpath returned no Windows path");
@@ -36,11 +36,11 @@ async function desktopFile(root, id, directory = root) {
36
36
  * @returns registered applications with the desktop's default marked.
37
37
  */
38
38
  export async function linuxFileApplications(path, signal, run, env) {
39
- const info = await run('gio', ['info', '-a', 'standard::content-type', path], signal);
39
+ const info = await run('gio', ['info', '-a', 'standard::content-type', path], signal, 'hidden');
40
40
  const mime = /standard::content-type:\s*(\S+)/.exec(info.stdout)?.[1];
41
41
  if (mime === undefined)
42
42
  throw new Error('GIO did not identify the file content type');
43
- const result = await run('env', ['LC_ALL=C', 'gio', 'mime', mime], signal);
43
+ const result = await run('env', ['LC_ALL=C', 'gio', 'mime', mime], signal, 'hidden');
44
44
  const preferred = /^Default application.*:\s*(.+\.desktop)\s*$/m.exec(result.stdout)?.[1];
45
45
  const ids = [...new Set([...(preferred === undefined ? [] : [preferred]), ...result.stdout.split(/\r?\n/)
46
46
  .filter(line => /^\s+.*\.desktop\s*$/.test(line)).map(line => line.trim())])];
@@ -263,7 +263,7 @@ ${application === null ? 'ConvertTo-Json -InputObject @([DshFileAssociations]::L
263
263
  await writeFile(scriptPath, `\uFEFF${script}`, 'utf8');
264
264
  const result = await run('powershell.exe', [
265
265
  '-NoProfile', '-NonInteractive', '-STA', '-ExecutionPolicy', 'Bypass', '-File', scriptPath,
266
- ], signal);
266
+ ], signal, 'hidden');
267
267
  return result.stdout;
268
268
  }
269
269
  finally {
@@ -64,7 +64,7 @@ async function queryFileApplications(path, signal, internals, display) {
64
64
  if (target.platform === 'linux')
65
65
  return linuxFileApplications(path, signal, run, internals.env ?? process.env);
66
66
  if (target.platform === 'darwin') {
67
- const { stdout } = await run('osascript', ['-l', 'JavaScript', '-e', MAC_APPLICATIONS, target.path, display ? 'icons' : 'handlers'], signal);
67
+ const { stdout } = await run('osascript', ['-l', 'JavaScript', '-e', MAC_APPLICATIONS, target.path, display ? 'icons' : 'handlers'], signal, 'hidden');
68
68
  const applications = parseMacApplications(JSON.parse(stdout));
69
69
  return display ? dedupeMacApplications(applications) : applications;
70
70
  }
@@ -170,16 +170,16 @@ export async function openNativeFileApplication(path, application, signal, inter
170
170
  if (!apps.some(app => app.id === application))
171
171
  throw new Error('Application is not registered for this file');
172
172
  if (target.platform === 'linux')
173
- await run('gio', ['launch', application, path], signal);
173
+ await run('gio', ['launch', application, path], signal, 'hidden');
174
174
  else
175
- await run('open', ['-a', application, path], signal);
175
+ await run('open', ['-a', application, path], signal, 'hidden');
176
176
  }
177
177
  /** Resolve the desktop that owns the file, including Windows applications reached from WSL. */
178
178
  async function desktopTarget(path, signal, internals) {
179
179
  signal.throwIfAborted();
180
180
  const platform = internals.platform ?? process.platform;
181
181
  if (platform === 'linux' && nativeFileManager(internals) === 'explorer') {
182
- const translated = await (internals.run ?? runNativeCommand)('wslpath', ['-w', path], signal);
182
+ const translated = await (internals.run ?? runNativeCommand)('wslpath', ['-w', path], signal, 'hidden');
183
183
  signal.throwIfAborted();
184
184
  const windowsPath = translated.stdout.replace(/[\r\n]+$/, '');
185
185
  if (windowsPath === '')
@@ -38,7 +38,7 @@ async function openInBrowser(path, signal, platform, run, env) {
38
38
  if (platform === 'darwin') {
39
39
  let bundle;
40
40
  try {
41
- const { stdout } = await run('defaults', ['read', 'com.apple.LaunchServices/com.apple.launchservices.secure'], signal);
41
+ const { stdout } = await run('defaults', ['read', 'com.apple.LaunchServices/com.apple.launchservices.secure'], signal, 'hidden');
42
42
  bundle = macBundleForHttps(stdout);
43
43
  }
44
44
  catch {
@@ -48,7 +48,7 @@ async function openInBrowser(path, signal, platform, run, env) {
48
48
  }
49
49
  if (bundle === undefined)
50
50
  return false;
51
- await run('open', ['-b', bundle, path], signal);
51
+ await run('open', ['-b', bundle, path], signal, 'hidden');
52
52
  return true;
53
53
  }
54
54
  if (platform === 'linux') {
@@ -57,7 +57,7 @@ async function openInBrowser(path, signal, platform, run, env) {
57
57
  const browser = env.BROWSER;
58
58
  if (browser === undefined || browser === '')
59
59
  return false;
60
- await run(browser, [path], signal);
60
+ await run(browser, [path], signal, 'hidden');
61
61
  return true;
62
62
  }
63
63
  // Windows names no browser without reading the UserChoice registry, and its
@@ -112,7 +112,8 @@ function explorerTarget(windowsPath) {
112
112
  */
113
113
  async function runExplorer(args, signal, run) {
114
114
  try {
115
- await run('explorer.exe', args, signal);
115
+ // SW_HIDE also hides Explorer's folder window, not just console windows.
116
+ await run('explorer.exe', args, signal, 'visible');
116
117
  }
117
118
  catch (error) {
118
119
  signal.throwIfAborted();
@@ -133,7 +134,7 @@ async function openWindowsPath(path, signal, run) {
133
134
  }
134
135
  /** Translate a WSL path before handing it to the Windows desktop. */
135
136
  async function openWslPath(path, signal, run) {
136
- const translated = await run('wslpath', ['-w', path], signal);
137
+ const translated = await run('wslpath', ['-w', path], signal, 'hidden');
137
138
  signal.throwIfAborted();
138
139
  const windowsPath = translated.stdout.replace(/[\r\n]+$/, '');
139
140
  if (windowsPath === '')
@@ -150,7 +151,7 @@ async function openNativePathWithIntent(path, signal, intent, internals = {}) {
150
151
  && await openInBrowser(path, signal, platform, run, env))
151
152
  return;
152
153
  if (platform === 'darwin') {
153
- await run('open', intent === 'text-editor' ? ['-t', path] : [path], signal);
154
+ await run('open', intent === 'text-editor' ? ['-t', path] : [path], signal, 'hidden');
154
155
  return;
155
156
  }
156
157
  if (platform === 'win32') {
@@ -162,7 +163,7 @@ async function openNativePathWithIntent(path, signal, intent, internals = {}) {
162
163
  await openWslPath(path, signal, run);
163
164
  return;
164
165
  }
165
- await run('xdg-open', [path], signal);
166
+ await run('xdg-open', [path], signal, 'hidden');
166
167
  return;
167
168
  }
168
169
  throw new Error(`native path opener is unsupported on ${platform}`);
@@ -243,13 +244,13 @@ export async function revealNativePath(path, signal, internals = {}) {
243
244
  const run = internals.run ?? runNativeCommand;
244
245
  const manager = nativeFileManager({ ...internals, platform });
245
246
  if (manager === 'finder') {
246
- await run('open', ['-R', path], signal);
247
+ await run('open', ['-R', path], signal, 'hidden');
247
248
  return;
248
249
  }
249
250
  if (manager === 'explorer') {
250
251
  let windowsPath = path;
251
252
  if (platform === 'linux') {
252
- const translated = await run('wslpath', ['-w', path], signal);
253
+ const translated = await run('wslpath', ['-w', path], signal, 'hidden');
253
254
  signal.throwIfAborted();
254
255
  windowsPath = translated.stdout.replace(/[\r\n]+$/, '');
255
256
  if (windowsPath === '')
@@ -259,7 +260,7 @@ export async function revealNativePath(path, signal, internals = {}) {
259
260
  return;
260
261
  }
261
262
  if (manager === 'directory') {
262
- await run('xdg-open', [dirname(path)], signal);
263
+ await run('xdg-open', [dirname(path)], signal, 'hidden');
263
264
  return;
264
265
  }
265
266
  throw new Error(`native file manager is unsupported on ${platform}`);
@@ -3,15 +3,17 @@
3
3
  * @module @deepseek-ai/dsh-native-command/runner
4
4
  */
5
5
  /** Testable command boundary; native implementations never invoke a shell. */
6
- export type NativeCommandRunner = (command: string, args: readonly string[], signal: AbortSignal) => Promise<{
6
+ export type NativeCommandRunner = (command: string, args: readonly string[], signal: AbortSignal, window: 'hidden' | 'visible') => Promise<{
7
7
  stdout: string;
8
8
  stderr: string;
9
9
  }>;
10
10
  /**
11
- * Run a host command with utf8 stdio, abort propagation, and Windows hide.
11
+ * Run a host command with utf8 stdio, abort propagation, and explicit GUI visibility.
12
12
  * @param command - executable path or PATH name.
13
13
  * @param args - argv (never a shell string).
14
14
  * @param signal - caller/connection lifetime; abort terminates the child.
15
+ * @param window - Windows startup visibility: `hidden` for background commands,
16
+ * `visible` for GUI launchers. Ignored on other platforms.
15
17
  * @returns captured stdout/stderr on exit 0.
16
18
  */
17
19
  export declare const runNativeCommand: NativeCommandRunner;
@@ -4,14 +4,16 @@
4
4
  */
5
5
  import { execFile } from 'node:child_process';
6
6
  /**
7
- * Run a host command with utf8 stdio, abort propagation, and Windows hide.
7
+ * Run a host command with utf8 stdio, abort propagation, and explicit GUI visibility.
8
8
  * @param command - executable path or PATH name.
9
9
  * @param args - argv (never a shell string).
10
10
  * @param signal - caller/connection lifetime; abort terminates the child.
11
+ * @param window - Windows startup visibility: `hidden` for background commands,
12
+ * `visible` for GUI launchers. Ignored on other platforms.
11
13
  * @returns captured stdout/stderr on exit 0.
12
14
  */
13
- export const runNativeCommand = (command, args, signal) => new Promise((resolve, reject) => {
14
- execFile(command, [...args], { encoding: 'utf8', signal, windowsHide: true }, (error, stdout, stderr) => {
15
+ export const runNativeCommand = (command, args, signal, window) => new Promise((resolve, reject) => {
16
+ execFile(command, [...args], { encoding: 'utf8', signal, windowsHide: window === 'hidden' }, (error, stdout, stderr) => {
15
17
  if (error !== null) {
16
18
  const failure = Object.assign(new Error(error.message, { cause: error }), {
17
19
  code: error.code,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-native-command",
3
3
  "description": "Host-native command and path-opening utilities with shell-free execution, cancellation, desktop detection, and WSL handoff",
4
- "version": "0.1.7-rc.2",
4
+ "version": "0.2.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },