@deepseek-ai/dsh-native-command 0.1.7-rc.2 → 0.2.0-rc.2
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 +6 -6
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/lib/index.js +22 -20
- package/lib/types/file-applications-linux.js +2 -2
- package/lib/types/file-applications-windows.js +1 -1
- package/lib/types/file-applications.js +4 -4
- package/lib/types/path-opener.js +11 -10
- package/lib/types/runner.d.ts +4 -2
- package/lib/types/runner.js +5 -3
- package/package.json +1 -1
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:
|
|
13
|
-
zh:
|
|
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:
|
|
22
|
-
zh:
|
|
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:
|
|
37
|
-
zh:
|
|
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
|
|
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;
|
|
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
|
|
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` 选项在调用方中止触发时终止子进程;`
|
|
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
|
|
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:
|
|
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 === '')
|
package/lib/types/path-opener.js
CHANGED
|
@@ -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
|
-
|
|
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}`);
|
package/lib/types/runner.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/lib/types/runner.js
CHANGED
|
@@ -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
|
|
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:
|
|
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.
|
|
4
|
+
"version": "0.2.0-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|