dsh-bash-terminal-ts 0.2.6 → 0.5.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/README.en.md +82 -197
- package/README.md +197 -194
- package/cordis.patch.yml +5 -5
- package/dist/client.core.js +46 -39
- package/dist/client.js +47 -40
- package/dsh.plugin.json +22 -22
- package/lib/client.js +148 -0
- package/lib/dsh-types.d.ts +1 -1
- package/lib/index.d.ts +17 -16
- package/lib/index.js +47 -47
- package/lib/terminal.d.ts +24 -1
- package/lib/terminal.js +214 -49
- package/package.json +38 -57
- package/screenshots.json +3 -0
- package/scripts/build-client.mjs +14 -13
- package/src/client.tsx +70 -46
- package/src/dsh-types.ts +1 -1
- package/src/index.ts +47 -47
- package/src/terminal.ts +254 -54
- package/README.ja.md +0 -196
- package/README.ko.md +0 -194
package/dist/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({
|
|
2
|
-
id: "dsh-bash-terminal
|
|
2
|
+
id: "dsh-bash-terminal",
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
@@ -28,13 +28,24 @@ __export(client_exports, {
|
|
|
28
28
|
inject: () => inject
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(client_exports);
|
|
31
|
+
var import_react = require("react");
|
|
31
32
|
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
33
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
32
34
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
35
|
var SETTINGS_NS = "settings.bash-terminal";
|
|
34
36
|
var SETTINGS_NAMESPACE = "bash-terminal";
|
|
37
|
+
var SHELLS = ["powershell", "gitbash", "msys2", "wsl"];
|
|
38
|
+
var ROW_CSS = ".btRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.btRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.btTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.btDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.btSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.btSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.btChevron{flex:none}";
|
|
39
|
+
if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="bash-terminal-row"]') === null) {
|
|
40
|
+
const tag = document.createElement("style");
|
|
41
|
+
tag.dataset.plugin = "dsh-bash-terminal";
|
|
42
|
+
tag.dataset.pluginCss = "bash-terminal-row";
|
|
43
|
+
tag.textContent = ROW_CSS;
|
|
44
|
+
document.head.appendChild(tag);
|
|
45
|
+
}
|
|
35
46
|
var zh = {
|
|
36
47
|
"shell.title": "\u9ED8\u8BA4\u7EC8\u7AEF",
|
|
37
|
-
"shell.description": "shell \u5DE5\u5177\u6267\u884C\u547D\u4EE4\u65F6\u4F7F\u7528\u7684\u7EC8\u7AEF
|
|
48
|
+
"shell.description": "shell \u5DE5\u5177\u6267\u884C\u547D\u4EE4\u65F6\u4F7F\u7528\u7684\u7EC8\u7AEF",
|
|
38
49
|
"shell.powershell": "PowerShell",
|
|
39
50
|
"shell.gitbash": "Git Bash",
|
|
40
51
|
"shell.msys2": "MSYS2",
|
|
@@ -42,7 +53,7 @@ var zh = {
|
|
|
42
53
|
};
|
|
43
54
|
var en = {
|
|
44
55
|
"shell.title": "Default terminal",
|
|
45
|
-
"shell.description": "Terminal used by the shell tool
|
|
56
|
+
"shell.description": "Terminal used by the shell tool",
|
|
46
57
|
"shell.powershell": "PowerShell",
|
|
47
58
|
"shell.gitbash": "Git Bash",
|
|
48
59
|
"shell.msys2": "MSYS2",
|
|
@@ -52,49 +63,45 @@ var inject = ["slots", "locale", "settingsScope"];
|
|
|
52
63
|
function ShellPreferenceRow({ t, useStore, setShell }) {
|
|
53
64
|
const shell = useStore((s) => s.shell);
|
|
54
65
|
const writable = useStore((s) => s.writable);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
67
|
+
const items = SHELLS.map((id) => ({ id, label: t("shell." + id) }));
|
|
68
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRow", children: [
|
|
69
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRowText", children: [
|
|
70
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: t("shell.title") }),
|
|
71
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: t("shell.description") })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
74
|
+
import_dsh_client_ui_primitives.Menu,
|
|
75
|
+
{
|
|
76
|
+
open,
|
|
77
|
+
onClose: () => setOpen(false),
|
|
78
|
+
items,
|
|
79
|
+
selectedId: shell,
|
|
80
|
+
onSelect: (id) => {
|
|
81
|
+
setOpen(false);
|
|
82
|
+
setShell(id);
|
|
83
|
+
},
|
|
84
|
+
align: "end",
|
|
85
|
+
portal: true,
|
|
86
|
+
anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
87
|
+
"button",
|
|
72
88
|
{
|
|
73
|
-
|
|
89
|
+
type: "button",
|
|
90
|
+
className: "btSelector",
|
|
91
|
+
"aria-haspopup": "menu",
|
|
92
|
+
"aria-expanded": open,
|
|
74
93
|
disabled: !writable,
|
|
75
|
-
|
|
76
|
-
style: {
|
|
77
|
-
fontSize: 14,
|
|
78
|
-
padding: "6px 10px",
|
|
79
|
-
borderRadius: 8,
|
|
80
|
-
border: "1px solid var(--dsw-alias-line-strong, #ccc)",
|
|
81
|
-
background: "var(--dsw-alias-bg-layer-2, #fff)",
|
|
82
|
-
color: "var(--dsw-alias-label-primary, inherit)",
|
|
83
|
-
outline: "none",
|
|
84
|
-
cursor: writable ? "pointer" : "not-allowed",
|
|
85
|
-
maxWidth: 180
|
|
86
|
-
},
|
|
94
|
+
onClick: () => setOpen(!open),
|
|
95
|
+
style: !writable ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
|
|
87
96
|
children: [
|
|
88
|
-
|
|
89
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
90
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "msys2", children: t("shell.msys2") }),
|
|
91
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "wsl", children: t("shell.wsl") })
|
|
97
|
+
t("shell." + shell),
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "btChevron" })
|
|
92
99
|
]
|
|
93
100
|
}
|
|
94
101
|
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] });
|
|
98
105
|
}
|
|
99
106
|
function apply(ctx) {
|
|
100
107
|
ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en }), "bash-terminal: settings dictionaries");
|
package/dsh.plugin.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "dsh-bash-terminal
|
|
3
|
-
"name": "dsh-bash-terminal
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": {
|
|
6
|
-
"en": "One shell tool that runs commands through PowerShell, Git Bash, MSYS2 or WSL on Windows, with a user-chosen default terminal in the Web UI settings.",
|
|
7
|
-
"zh": "一个 shell 工具,可在 Windows 上通过 PowerShell / Git Bash / MSYS2 / WSL 执行命令,默认终端在 Web UI 设置中选择。"
|
|
8
|
-
},
|
|
9
|
-
"homepage": "https://github.com/drscrewdriver/dsh-bash-terminal-ts",
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "https://github.com/drscrewdriver/dsh-bash-terminal-ts.git"
|
|
13
|
-
},
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"engines": {
|
|
16
|
-
"dsh": ">=0.1.
|
|
17
|
-
},
|
|
18
|
-
"components": {
|
|
19
|
-
"host": "lib/index.js",
|
|
20
|
-
"client": "dist/client.js"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "dsh-bash-terminal",
|
|
3
|
+
"name": "dsh-bash-terminal",
|
|
4
|
+
"version": "0.3.18",
|
|
5
|
+
"description": {
|
|
6
|
+
"en": "One shell tool that runs commands through PowerShell, Git Bash, MSYS2 or WSL on Windows, with a user-chosen default terminal in the Web UI settings.",
|
|
7
|
+
"zh": "一个 shell 工具,可在 Windows 上通过 PowerShell / Git Bash / MSYS2 / WSL 执行命令,默认终端在 Web UI 设置中选择。"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/drscrewdriver/dsh-bash-terminal-ts",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/drscrewdriver/dsh-bash-terminal-ts.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"engines": {
|
|
16
|
+
"dsh": ">=0.1.5-alpha.1 <0.2.0-0"
|
|
17
|
+
},
|
|
18
|
+
"components": {
|
|
19
|
+
"host": "lib/index.js",
|
|
20
|
+
"client": "dist/client.js"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-bash-terminal",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
|
|
24
|
+
// src/client.tsx
|
|
25
|
+
var client_exports = {};
|
|
26
|
+
__export(client_exports, {
|
|
27
|
+
apply: () => apply,
|
|
28
|
+
inject: () => inject
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(client_exports);
|
|
31
|
+
var import_react = require("react");
|
|
32
|
+
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
33
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
34
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
+
var SETTINGS_NS = "settings.bash-terminal";
|
|
36
|
+
var SETTINGS_NAMESPACE = "bash-terminal";
|
|
37
|
+
var SHELLS = ["powershell", "gitbash", "msys2", "wsl"];
|
|
38
|
+
var ROW_CSS = ".btRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.btRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.btTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.btDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.btSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.btSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.btChevron{flex:none}";
|
|
39
|
+
if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="bash-terminal-row"]') === null) {
|
|
40
|
+
const tag = document.createElement("style");
|
|
41
|
+
tag.dataset.plugin = "dsh-bash-terminal";
|
|
42
|
+
tag.dataset.pluginCss = "bash-terminal-row";
|
|
43
|
+
tag.textContent = ROW_CSS;
|
|
44
|
+
document.head.appendChild(tag);
|
|
45
|
+
}
|
|
46
|
+
var zh = {
|
|
47
|
+
"shell.title": "\u9ED8\u8BA4\u7EC8\u7AEF",
|
|
48
|
+
"shell.description": "shell \u5DE5\u5177\u6267\u884C\u547D\u4EE4\u65F6\u4F7F\u7528\u7684\u7EC8\u7AEF",
|
|
49
|
+
"shell.powershell": "PowerShell",
|
|
50
|
+
"shell.gitbash": "Git Bash",
|
|
51
|
+
"shell.msys2": "MSYS2",
|
|
52
|
+
"shell.wsl": "WSL"
|
|
53
|
+
};
|
|
54
|
+
var en = {
|
|
55
|
+
"shell.title": "Default terminal",
|
|
56
|
+
"shell.description": "Terminal used by the shell tool",
|
|
57
|
+
"shell.powershell": "PowerShell",
|
|
58
|
+
"shell.gitbash": "Git Bash",
|
|
59
|
+
"shell.msys2": "MSYS2",
|
|
60
|
+
"shell.wsl": "WSL"
|
|
61
|
+
};
|
|
62
|
+
var inject = ["slots", "locale", "settingsScope"];
|
|
63
|
+
function ShellPreferenceRow({ t, useStore, setShell }) {
|
|
64
|
+
const shell = useStore((s) => s.shell);
|
|
65
|
+
const writable = useStore((s) => s.writable);
|
|
66
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
67
|
+
const items = SHELLS.map((id) => ({ id, label: t("shell." + id) }));
|
|
68
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRow", children: [
|
|
69
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRowText", children: [
|
|
70
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: t("shell.title") }),
|
|
71
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: t("shell.description") })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
74
|
+
import_dsh_client_ui_primitives.Menu,
|
|
75
|
+
{
|
|
76
|
+
open,
|
|
77
|
+
onClose: () => setOpen(false),
|
|
78
|
+
items,
|
|
79
|
+
selectedId: shell,
|
|
80
|
+
onSelect: (id) => {
|
|
81
|
+
setOpen(false);
|
|
82
|
+
setShell(id);
|
|
83
|
+
},
|
|
84
|
+
align: "end",
|
|
85
|
+
portal: true,
|
|
86
|
+
anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
87
|
+
"button",
|
|
88
|
+
{
|
|
89
|
+
type: "button",
|
|
90
|
+
className: "btSelector",
|
|
91
|
+
"aria-haspopup": "menu",
|
|
92
|
+
"aria-expanded": open,
|
|
93
|
+
disabled: !writable,
|
|
94
|
+
onClick: () => setOpen(!open),
|
|
95
|
+
style: !writable ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
|
|
96
|
+
children: [
|
|
97
|
+
t("shell." + shell),
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "btChevron" })
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
function apply(ctx) {
|
|
107
|
+
ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en }), "bash-terminal: settings dictionaries");
|
|
108
|
+
const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
|
|
109
|
+
const store = (0, import_dsh_client_store.defineStore)({
|
|
110
|
+
init: () => ({ shell: "powershell", revision: -1, writable: false }),
|
|
111
|
+
actions: {
|
|
112
|
+
sync: (d, shell, revision, writable) => {
|
|
113
|
+
if (revision !== void 0 && revision <= d.revision) return;
|
|
114
|
+
if (shell !== void 0) d.shell = shell;
|
|
115
|
+
if (revision !== void 0) d.revision = revision;
|
|
116
|
+
if (writable !== void 0) d.writable = writable;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
let bound;
|
|
121
|
+
const push = (snap) => {
|
|
122
|
+
bound?.sync(snap.value?.defaultShell, snap.revision, snap.writable);
|
|
123
|
+
};
|
|
124
|
+
ctx.slots.inject(
|
|
125
|
+
"settings.general.item",
|
|
126
|
+
() => ctx.slots.register(
|
|
127
|
+
{
|
|
128
|
+
name: "settings.general.item",
|
|
129
|
+
id: "bash-terminal-shell",
|
|
130
|
+
order: 20,
|
|
131
|
+
store,
|
|
132
|
+
locale: SETTINGS_NS,
|
|
133
|
+
inject: (actions) => {
|
|
134
|
+
bound = actions;
|
|
135
|
+
push(scope.getSnapshot());
|
|
136
|
+
return { setShell: (value) => void scope.set("defaultShell", value) };
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
ShellPreferenceRow
|
|
140
|
+
),
|
|
141
|
+
"bash-terminal: settings row"
|
|
142
|
+
);
|
|
143
|
+
ctx.effect(() => scope.subscribe(() => push(scope.getSnapshot())), "bash-terminal: settings watch");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return module.exports;
|
|
147
|
+
}
|
|
148
|
+
});
|
package/lib/dsh-types.d.ts
CHANGED
|
@@ -241,7 +241,7 @@ export interface ApprovalSeam {
|
|
|
241
241
|
request(input: unknown): Promise<unknown>;
|
|
242
242
|
}
|
|
243
243
|
/**
|
|
244
|
-
* The structural context face dsh-bash-terminal
|
|
244
|
+
* The structural context face dsh-bash-terminal programs against: the union
|
|
245
245
|
* of every `ctx.*` member touched by src/index.ts and src/terminal.ts.
|
|
246
246
|
*/
|
|
247
247
|
export interface BashTerminalContext {
|
package/lib/index.d.ts
CHANGED
|
@@ -31,17 +31,18 @@ export declare function candidatePwshPaths(env?: NodeJS.ProcessEnv): string[];
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function candidateGitBashPaths(env?: NodeJS.ProcessEnv): string[];
|
|
33
33
|
/**
|
|
34
|
-
* MSYS2 locations, in preference order: the real `bash.exe` under usr\bin
|
|
35
|
-
* then bin\bash.exe, and `msys2.exe` dead last.
|
|
34
|
+
* MSYS2 locations, in preference order: the real `bash.exe` under usr\bin
|
|
35
|
+
* first, then bin\bash.exe, and `msys2.exe` dead last.
|
|
36
36
|
*
|
|
37
37
|
* msys2.exe is NOT a usable backend for piped execution: it is the console-
|
|
38
38
|
* allocating Cygwin launcher, so a spawn with piped stdio returns exit 0 with
|
|
39
|
-
* zero bytes on both stdout and stderr (measured on
|
|
40
|
-
* Keeping it in the list only as a last-resort
|
|
41
|
-
* config docs reference, but a working
|
|
39
|
+
* zero bytes on both stdout and stderr (measured on this machine against
|
|
40
|
+
* MSYS2 with bash 5.3.15). Keeping it in the list only as a last-resort
|
|
41
|
+
* fallback preserves the path the config docs reference, but a working
|
|
42
|
+
* bash.exe always wins.
|
|
42
43
|
*
|
|
43
|
-
* MSYS2 uses the same Cygwin/MSYS2 runtime as Git Bash, so it cannot run
|
|
44
|
-
* the DSH Windows ACL restricted-token sandbox.
|
|
44
|
+
* MSYS2 uses the same Cygwin/MSYS2 runtime as Git Bash, so it cannot run
|
|
45
|
+
* under the DSH Windows ACL restricted-token sandbox.
|
|
45
46
|
*/
|
|
46
47
|
export declare function candidateMsys2Paths(env?: NodeJS.ProcessEnv): string[];
|
|
47
48
|
export declare function defaultWslPath(env?: NodeJS.ProcessEnv): string;
|
|
@@ -52,16 +53,16 @@ export declare function resolveAllPaths(config?: PathConfig, env?: NodeJS.Proces
|
|
|
52
53
|
export declare function buildArgv(shell: string, command: string, paths: ResolvedPaths, distro?: string): Array<string | undefined>;
|
|
53
54
|
/**
|
|
54
55
|
* Merge the DSH_* environment over the process environment. For WSL, only
|
|
55
|
-
* variables explicitly listed in WSLENV cross the boundary, so every DSH_*
|
|
56
|
-
* appended
|
|
56
|
+
* variables explicitly listed in WSLENV cross the boundary, so every DSH_*
|
|
57
|
+
* key is appended there — and the list is *layered onto* whatever WSLENV the
|
|
58
|
+
* host already had (Windows Terminal exports e.g. `WT_SESSION:WT_PROFILE_ID:`),
|
|
59
|
+
* never rebuilt from scratch: WSLENV is an allow-list, so dropping the
|
|
60
|
+
* inherited entries would silently stop them crossing into WSL.
|
|
57
61
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* wholesale (the PTY path) must pass the inherited value explicitly.
|
|
63
|
-
*
|
|
64
|
-
* @param inheritedWslenv - the WSLENV the child would otherwise inherit.
|
|
62
|
+
* @param inheritedWslenv - the ambient WSLENV to layer onto. Callers that
|
|
63
|
+
* replace the child environment wholesale (the PTY path) must pass
|
|
64
|
+
* `process.env.WSLENV` explicitly, because the ambient value is not visible
|
|
65
|
+
* through `dshEnv`.
|
|
65
66
|
*/
|
|
66
67
|
export declare function buildEnv(shell: string, dshEnv?: Record<string, string>, inheritedWslenv?: string | undefined): Record<string, string | undefined>;
|
|
67
68
|
export interface SpawnResolution {
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-bash-terminal
|
|
1
|
+
// dsh-bash-terminal - one shell tool, four Windows terminals.
|
|
2
2
|
//
|
|
3
3
|
// Registers a model-facing shell tool. The terminal backend (powershell /
|
|
4
4
|
// gitbash / msys2 / wsl) is chosen by the USER in the Web UI settings (default
|
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
// choice:
|
|
7
7
|
// - powershell: pwsh -NoLogo -NoProfile -NonInteractive -Command <cmd>
|
|
8
8
|
// - gitbash: Git for Windows bash -lc <cmd> (POSIX; /d/... paths)
|
|
9
|
-
// - msys2:
|
|
10
|
-
// toolchain). NOT msys2.exe: that Cygwin launcher allocates a
|
|
11
|
-
// console and returns exit 0 with zero bytes under piped stdio.
|
|
12
|
-
// MSYSTEM=MINGW64 is injected via buildEnv so /etc/profile picks
|
|
13
|
-
// the MINGW64 environment and /mingw64/bin lands on PATH.
|
|
9
|
+
// - msys2: MSYS2 bash -lc <cmd> (POSIX; /c/... paths, full GCC/mingw64 toolchain)
|
|
14
10
|
// - wsl: wsl [-d <distro>] -e bash -lc <cmd> (Linux; /mnt/d/... paths)
|
|
15
11
|
//
|
|
16
12
|
// The tool spawns through the shared ctx.subprocess seam (process-tree
|
|
@@ -18,8 +14,13 @@
|
|
|
18
14
|
// handles with the generic ctx.jobs registry, mirroring the shipped
|
|
19
15
|
// dsh-tool-bash / dsh-tool-pwsh story call-for-call. It deliberately
|
|
20
16
|
// does NOT consume the ctx.shell capability seam: the platform's own
|
|
21
|
-
// sandboxed PowerShell executor keeps serving the pwsh tool
|
|
22
|
-
//
|
|
17
|
+
// sandboxed PowerShell executor keeps serving the pwsh tool. This tool is
|
|
18
|
+
// an additional, user-selected terminal that still honors the DSH sandbox:
|
|
19
|
+
// confined modes wrap PowerShell's spawn argv through ctx.sandbox
|
|
20
|
+
// (fail-closed), while danger-full-access, Git Bash, MSYS2, and WSL run
|
|
21
|
+
// unconfined. Git Bash and MSYS2 are unconfined because the DSH Windows ACL
|
|
22
|
+
// restricted-token runner cannot host Cygwin/MSYS2: bash aborts with
|
|
23
|
+
// "CreateFileMapping ... Win32 error 5" during startup.
|
|
23
24
|
import { lstatSync } from "node:fs";
|
|
24
25
|
import { isAbsolute, join, resolve } from "node:path";
|
|
25
26
|
import { ESCALATION_TARGETS, TOOL_ABORTED, approveEscalation, defineTool, escalationHintMarker, clampTimeout, deadline, parseExitStatus, timeoutOf, sandboxDenialMarker, validateEscalationArgs, z, HarnessError } from "./dsh.js";
|
|
@@ -120,34 +121,30 @@ export function candidateGitBashPaths(env = process.env) {
|
|
|
120
121
|
return candidates;
|
|
121
122
|
}
|
|
122
123
|
/**
|
|
123
|
-
* MSYS2 locations, in preference order: the real `bash.exe` under usr\bin
|
|
124
|
-
* then bin\bash.exe, and `msys2.exe` dead last.
|
|
124
|
+
* MSYS2 locations, in preference order: the real `bash.exe` under usr\bin
|
|
125
|
+
* first, then bin\bash.exe, and `msys2.exe` dead last.
|
|
125
126
|
*
|
|
126
127
|
* msys2.exe is NOT a usable backend for piped execution: it is the console-
|
|
127
128
|
* allocating Cygwin launcher, so a spawn with piped stdio returns exit 0 with
|
|
128
|
-
* zero bytes on both stdout and stderr (measured on
|
|
129
|
-
* Keeping it in the list only as a last-resort
|
|
130
|
-
* config docs reference, but a working
|
|
129
|
+
* zero bytes on both stdout and stderr (measured on this machine against
|
|
130
|
+
* MSYS2 with bash 5.3.15). Keeping it in the list only as a last-resort
|
|
131
|
+
* fallback preserves the path the config docs reference, but a working
|
|
132
|
+
* bash.exe always wins.
|
|
131
133
|
*
|
|
132
|
-
* MSYS2 uses the same Cygwin/MSYS2 runtime as Git Bash, so it cannot run
|
|
133
|
-
* the DSH Windows ACL restricted-token sandbox.
|
|
134
|
+
* MSYS2 uses the same Cygwin/MSYS2 runtime as Git Bash, so it cannot run
|
|
135
|
+
* under the DSH Windows ACL restricted-token sandbox.
|
|
134
136
|
*/
|
|
135
137
|
export function candidateMsys2Paths(env = process.env) {
|
|
136
|
-
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
137
138
|
const candidates = [
|
|
138
139
|
"C:\\msys64\\usr\\bin\\bash.exe",
|
|
139
140
|
"C:\\msys64\\bin\\bash.exe"
|
|
140
141
|
];
|
|
141
|
-
// Also check 32-bit variant
|
|
142
|
-
const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
143
|
-
if (programFilesX86 !== programFiles) {
|
|
144
|
-
candidates.push("C:\\msys64\\usr\\bin\\bash.exe"); // same path regardless of arch
|
|
145
|
-
}
|
|
146
142
|
for (const entry of (env.PATH ?? "").split(";")) {
|
|
147
143
|
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
148
144
|
if (trimmed.length === 0)
|
|
149
145
|
continue;
|
|
150
|
-
|
|
146
|
+
const lower = trimmed.toLowerCase();
|
|
147
|
+
if (lower.includes("msys64") || lower.includes("mingw64")) {
|
|
151
148
|
candidates.push(join(trimmed, "bash.exe"));
|
|
152
149
|
}
|
|
153
150
|
}
|
|
@@ -195,40 +192,42 @@ export function buildArgv(shell, command, paths, distro) {
|
|
|
195
192
|
}
|
|
196
193
|
/**
|
|
197
194
|
* Merge the DSH_* environment over the process environment. For WSL, only
|
|
198
|
-
* variables explicitly listed in WSLENV cross the boundary, so every DSH_*
|
|
199
|
-
* appended
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* rebuilt, or those entries would be silently dropped from every WSL call.
|
|
204
|
-
* Callers that spawn through a seam which replaces the parent environment
|
|
205
|
-
* wholesale (the PTY path) must pass the inherited value explicitly.
|
|
195
|
+
* variables explicitly listed in WSLENV cross the boundary, so every DSH_*
|
|
196
|
+
* key is appended there — and the list is *layered onto* whatever WSLENV the
|
|
197
|
+
* host already had (Windows Terminal exports e.g. `WT_SESSION:WT_PROFILE_ID:`),
|
|
198
|
+
* never rebuilt from scratch: WSLENV is an allow-list, so dropping the
|
|
199
|
+
* inherited entries would silently stop them crossing into WSL.
|
|
206
200
|
*
|
|
207
|
-
* @param inheritedWslenv - the WSLENV
|
|
201
|
+
* @param inheritedWslenv - the ambient WSLENV to layer onto. Callers that
|
|
202
|
+
* replace the child environment wholesale (the PTY path) must pass
|
|
203
|
+
* `process.env.WSLENV` explicitly, because the ambient value is not visible
|
|
204
|
+
* through `dshEnv`.
|
|
208
205
|
*/
|
|
209
206
|
export function buildEnv(shell, dshEnv, inheritedWslenv = process.env.WSLENV) {
|
|
210
207
|
const env = { ...ENV_OVERRIDES, ...dshEnv };
|
|
211
208
|
if (shell === "msys2") {
|
|
212
|
-
// Picks the MINGW64 environment, so /mingw64/bin (gcc, make, ...) joins
|
|
213
|
-
// via /etc/profile. Without it MSYS2 defaults to the bare MSYS
|
|
209
|
+
// Picks the MINGW64 environment, so /mingw64/bin (gcc, make, ...) joins
|
|
210
|
+
// PATH via /etc/profile. Without it MSYS2 defaults to the bare MSYS
|
|
211
|
+
// environment. An explicit caller-supplied value wins.
|
|
214
212
|
if (env.MSYSTEM === undefined)
|
|
215
213
|
env.MSYSTEM = "MINGW64";
|
|
216
214
|
}
|
|
217
215
|
if (shell === "wsl") {
|
|
218
216
|
const keys = Object.keys(dshEnv ?? {});
|
|
219
217
|
if (keys.length > 0) {
|
|
220
|
-
// An explicit WSLENV
|
|
218
|
+
// An explicit WSLENV in dshEnv wins over the inherited one; otherwise the
|
|
219
|
+
// inherited value is the base. Either way the base is SPLIT into entries
|
|
220
|
+
// (the host value ends with a trailing ":", so string concatenation would
|
|
221
|
+
// produce a malformed empty entry) and the WSLENV key itself is excluded
|
|
222
|
+
// (it is a key of dshEnv too, and appending it would add a bogus entry).
|
|
221
223
|
const declared = Object.prototype.hasOwnProperty.call(dshEnv ?? {}, "WSLENV");
|
|
222
224
|
const base = declared
|
|
223
225
|
? env.WSLENV
|
|
224
226
|
: (typeof inheritedWslenv === "string" && inheritedWslenv.length > 0 ? inheritedWslenv : env.WSLENV);
|
|
225
|
-
// Windows Terminal's own value ends with a trailing ':' and the caller's
|
|
226
|
-
// may too, so split/normalise instead of string-concatenating -- a naive
|
|
227
|
-
// join yields an empty entry ("A:B::DSH_X"), which WSL flags as malformed.
|
|
228
227
|
const parts = typeof base === "string" ? base.split(":") : [];
|
|
229
228
|
env.WSLENV = [...parts, ...keys.filter((k) => k !== "WSLENV").flatMap((k) => k.split(":"))]
|
|
230
|
-
.map((
|
|
231
|
-
.filter((
|
|
229
|
+
.map((p) => p.trim())
|
|
230
|
+
.filter((p) => p.length > 0)
|
|
232
231
|
.join(":");
|
|
233
232
|
}
|
|
234
233
|
}
|
|
@@ -252,7 +251,7 @@ function spawnSpec(resolved, argv, env, signal) {
|
|
|
252
251
|
function collectedOutput(handle) {
|
|
253
252
|
const { stdout, stderr } = handle.collected;
|
|
254
253
|
if (!isCollectedStream(stdout) || !isCollectedStream(stderr)) {
|
|
255
|
-
throw new Error("dsh-bash-terminal
|
|
254
|
+
throw new Error("dsh-bash-terminal: subprocess implementation dropped a requested collect stream");
|
|
256
255
|
}
|
|
257
256
|
return { stdout, stderr };
|
|
258
257
|
}
|
|
@@ -293,7 +292,7 @@ async function runForeground(ctx, argv, resolved, env, signal, timeoutMs) {
|
|
|
293
292
|
function requireSubprocess(ctx) {
|
|
294
293
|
const subprocess = ctx.subprocess;
|
|
295
294
|
if (subprocess === null) {
|
|
296
|
-
throw new Error("dsh-bash-terminal
|
|
295
|
+
throw new Error("dsh-bash-terminal: ctx.subprocess seam unavailable (missing inject service)");
|
|
297
296
|
}
|
|
298
297
|
return subprocess;
|
|
299
298
|
}
|
|
@@ -442,8 +441,9 @@ export function validateArgs(args) {
|
|
|
442
441
|
* Git Bash and MSYS2 are also not confined: DSH's Windows ACL restricted-token
|
|
443
442
|
* runner cannot start Cygwin/MSYS2 (CreateFileMapping Win32 error 5), so
|
|
444
443
|
* attempting to wrap them would abort every Git Bash / MSYS2 command. A
|
|
445
|
-
* requested confined mode
|
|
446
|
-
*
|
|
444
|
+
* requested confined mode
|
|
445
|
+
* with no usable backend throws the fail-closed SandboxUnavailableError,
|
|
446
|
+
* exactly like the shipped executors.
|
|
447
447
|
*/
|
|
448
448
|
function confineSpawn(ctx, argv, policy, shell) {
|
|
449
449
|
if (policy.mode === "danger-full-access" || shell === "wsl" || shell === "gitbash" || shell === "msys2") {
|
|
@@ -495,14 +495,14 @@ const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
|
495
495
|
// ---- plugin -------------------------------------------------------------------
|
|
496
496
|
export function apply(ctx, config = {}) {
|
|
497
497
|
if (process.platform !== "win32") {
|
|
498
|
-
ctx.logger?.info?.("dsh-bash-terminal
|
|
498
|
+
ctx.logger?.info?.("dsh-bash-terminal: only meaningful on win32; skipping tool registration");
|
|
499
499
|
return;
|
|
500
500
|
}
|
|
501
501
|
const backgroundEnabled = true;
|
|
502
502
|
const paths = resolveAllPaths(config);
|
|
503
503
|
const defaultShell = config.defaultShell ?? DEFAULT_SHELL;
|
|
504
504
|
if (!SHELLS.includes(defaultShell)) {
|
|
505
|
-
throw new Error(`dsh-bash-terminal
|
|
505
|
+
throw new Error(`dsh-bash-terminal: invalid defaultShell ${JSON.stringify(defaultShell)}`);
|
|
506
506
|
}
|
|
507
507
|
const settingsScope = ctx.settings.register(SETTINGS_NAMESPACE, z.object({ defaultShell: z.union(SHELLS.map((s) => z.const(s))).default(defaultShell) }), { base: { defaultShell } });
|
|
508
508
|
/** Official sandbox-escalation surface (mirrors tool-bash): advertise the
|
|
@@ -525,7 +525,7 @@ export function apply(ctx, config = {}) {
|
|
|
525
525
|
ctx.systemPrompt.section({
|
|
526
526
|
name: "tool:bash-terminal",
|
|
527
527
|
order: 105,
|
|
528
|
-
text: "Use the shell tool for terminal commands: it runs in the terminal the user chose in Settings -> General -> Default terminal (PowerShell, Git Bash, or WSL) and honors the DSH sandbox. Prefer it over the pwsh tool for everyday commands; keep the pwsh tool for cases that specifically need the sandboxed PowerShell surface."
|
|
528
|
+
text: "Use the shell tool for terminal commands: it runs in the terminal the user chose in Settings -> General -> Default terminal (PowerShell, Git Bash, MSYS2, or WSL) and honors the DSH sandbox. Prefer it over the pwsh tool for everyday commands; keep the pwsh tool for cases that specifically need the sandboxed PowerShell surface."
|
|
529
529
|
});
|
|
530
530
|
const toolName = "shell";
|
|
531
531
|
const terminalRegistry = createTerminalRegistry(ctx);
|
|
@@ -647,7 +647,7 @@ export function apply(ctx, config = {}) {
|
|
|
647
647
|
const shell = settingsScope.get().defaultShell;
|
|
648
648
|
const argv0 = buildArgv(shell, v.command, paths, v.distro);
|
|
649
649
|
if (argv0[0] === undefined) {
|
|
650
|
-
throw new Error(`dsh-bash-terminal
|
|
650
|
+
throw new Error(`dsh-bash-terminal: ${shell} backend unavailable - executable not found. Install it or set the corresponding *Path config.`);
|
|
651
651
|
}
|
|
652
652
|
// Only element 0 (the resolved executable) can be undefined; guarded above.
|
|
653
653
|
const argv = argv0;
|