dsh-bash-terminal-ts 0.4.0 → 0.6.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 +53 -162
- package/README.md +103 -94
- package/cordis.patch.yml +2 -2
- package/dist/client.core.js +49 -42
- package/dist/client.js +50 -43
- package/dsh.plugin.json +4 -4
- package/lib/client.js +148 -0
- package/lib/dsh-types.d.ts +1 -2
- package/lib/dsh.d.ts +2 -0
- package/lib/index.d.ts +17 -18
- package/lib/index.js +63 -57
- package/lib/terminal.d.ts +24 -1
- package/lib/terminal.js +214 -49
- package/package.json +115 -137
- package/screenshots.json +5 -0
- package/scripts/build-client.mjs +14 -13
- package/src/client.tsx +80 -55
- package/src/dsh-types.ts +1 -2
- package/src/dsh.ts +2 -0
- package/src/index.ts +67 -61
- package/src/terminal.ts +254 -54
- package/template/README.md +82 -0
- package/template/terminal-adapter.ts +343 -0
- package/README.ja.md +0 -196
- package/README.ko.md +0 -194
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// Terminal adapter templates — four Windows terminal backends, ready to copy.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from dsh-bash-terminal-ts `src/index.ts` and sanitised for reuse:
|
|
4
|
+
// no author-local paths, no host-specific values. Copy this file — or just the
|
|
5
|
+
// one adapter you need — into your own DSH plugin, then wire the seams listed
|
|
6
|
+
// under "Wiring into DSH" in ./README.md.
|
|
7
|
+
//
|
|
8
|
+
// powershell pwsh -NoLogo -NoProfile -NonInteractive -Command <cmd>
|
|
9
|
+
// gitbash Git for Windows bash -lc <cmd> POSIX; /d/... paths
|
|
10
|
+
// msys2 MSYS2 bash -lc <cmd> POSIX; /c/... paths, GCC/mingw64
|
|
11
|
+
// wsl wsl [-d <distro>] -e bash -lc <cmd> Linux; /mnt/d/... paths
|
|
12
|
+
//
|
|
13
|
+
// The flag choices here are not guesses: each non-obvious one was measured on
|
|
14
|
+
// Windows and carries its reason inline. Read the comment before changing a
|
|
15
|
+
// flag — several of them look arbitrary and are not.
|
|
16
|
+
//
|
|
17
|
+
// Node >= 22. No runtime dependencies beyond node:fs / node:path.
|
|
18
|
+
|
|
19
|
+
import { lstatSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
/** The four backends, in the order they appear in the settings dropdown. */
|
|
23
|
+
export const SHELLS = ["powershell", "gitbash", "msys2", "wsl"] as const;
|
|
24
|
+
export type ShellId = (typeof SHELLS)[number];
|
|
25
|
+
|
|
26
|
+
/** Chosen when the user has never touched the setting. */
|
|
27
|
+
export const DEFAULT_SHELL: ShellId = "powershell";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Forced into every child process. A shell tool feeds its output to a model, so
|
|
31
|
+
* pagers and colour codes are pure noise: `PAGER=cat` and `NO_COLOR=1` keep the
|
|
32
|
+
* captured stdout byte-for-byte the command's actual output.
|
|
33
|
+
*/
|
|
34
|
+
export const ENV_OVERRIDES: Record<string, string> = {
|
|
35
|
+
NO_COLOR: "1",
|
|
36
|
+
TERM: "dumb",
|
|
37
|
+
PAGER: "cat",
|
|
38
|
+
GIT_PAGER: "cat"
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ---- executable resolution -------------------------------------------------
|
|
42
|
+
|
|
43
|
+
function candidateExists(candidate: string): boolean {
|
|
44
|
+
try {
|
|
45
|
+
const stat = lstatSync(candidate);
|
|
46
|
+
return stat.isFile() || stat.isSymbolicLink();
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** First candidate that exists on disk, or undefined when none does. */
|
|
53
|
+
export function resolveFromCandidates(candidates: readonly string[]): string | undefined {
|
|
54
|
+
for (const candidate of candidates) {
|
|
55
|
+
if (candidateExists(candidate)) return candidate;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Well-known PowerShell install locations, then PATH entries. Newest first. */
|
|
61
|
+
export function candidatePwshPaths(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
62
|
+
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
63
|
+
const systemRoot = env.SystemRoot ?? "C:\\Windows";
|
|
64
|
+
// PowerShell 7 first: pwsh.exe is the cross-platform one and is what the
|
|
65
|
+
// -NoLogo/-NoProfile/-NonInteractive flag set is written against.
|
|
66
|
+
const candidates = [join(programFiles, "PowerShell", "7", "pwsh.exe")];
|
|
67
|
+
for (const entry of (env.PATH ?? "").split(";")) {
|
|
68
|
+
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
69
|
+
if (trimmed.length === 0) continue;
|
|
70
|
+
candidates.push(join(trimmed, "pwsh.exe"));
|
|
71
|
+
}
|
|
72
|
+
// Windows PowerShell 5.1 as the floor — present on every Windows install.
|
|
73
|
+
candidates.push(join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"));
|
|
74
|
+
return candidates;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Git for Windows locations, then PATH `bash.exe` entries EXCLUDING the
|
|
79
|
+
* System32 launcher.
|
|
80
|
+
*
|
|
81
|
+
* That exclusion is the whole point of this function: `C:\Windows\System32\
|
|
82
|
+
* bash.exe` also exists on machines with WSL and is the **WSL forwarder**, not
|
|
83
|
+
* a Git Bash shell. Picking it up silently sends Git Bash commands into a Linux
|
|
84
|
+
* VM, where `/d/...` paths and the Git for Windows toolchain do not exist.
|
|
85
|
+
*/
|
|
86
|
+
export function candidateGitBashPaths(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
87
|
+
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
88
|
+
const systemRoot = (env.SystemRoot ?? "C:\\Windows").toLowerCase();
|
|
89
|
+
const localAppData = env.LOCALAPPDATA ?? "";
|
|
90
|
+
const candidates = [
|
|
91
|
+
join(programFiles, "Git", "bin", "bash.exe"),
|
|
92
|
+
// usr\bin\bash.exe is the MSYS-rooted twin; some installs only expose this one.
|
|
93
|
+
join(programFiles, "Git", "usr", "bin", "bash.exe")
|
|
94
|
+
];
|
|
95
|
+
// Per-user installs land here.
|
|
96
|
+
if (localAppData.length > 0) candidates.push(join(localAppData, "Programs", "Git", "bin", "bash.exe"));
|
|
97
|
+
for (const entry of (env.PATH ?? "").split(";")) {
|
|
98
|
+
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
99
|
+
if (trimmed.length === 0) continue;
|
|
100
|
+
if (trimmed.toLowerCase().includes(systemRoot)) continue; // ← the WSL forwarder
|
|
101
|
+
candidates.push(join(trimmed, "bash.exe"));
|
|
102
|
+
}
|
|
103
|
+
return candidates;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* MSYS2 locations, in preference order: the real `bash.exe` under usr\bin
|
|
108
|
+
* first, then bin\bash.exe, and `msys2.exe` dead last.
|
|
109
|
+
*
|
|
110
|
+
* `msys2.exe` is NOT a usable backend for piped execution. It is the
|
|
111
|
+
* console-allocating Cygwin launcher: spawned with piped stdio it returns exit
|
|
112
|
+
* 0 with zero bytes on both stdout and stderr, so every command looks like it
|
|
113
|
+
* succeeded and printed nothing. It stays in the list only as a last resort;
|
|
114
|
+
* a working `bash.exe` always wins.
|
|
115
|
+
*/
|
|
116
|
+
export function candidateMsys2Paths(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
117
|
+
const candidates = [
|
|
118
|
+
"C:\\msys64\\usr\\bin\\bash.exe",
|
|
119
|
+
"C:\\msys64\\bin\\bash.exe"
|
|
120
|
+
];
|
|
121
|
+
for (const entry of (env.PATH ?? "").split(";")) {
|
|
122
|
+
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
123
|
+
if (trimmed.length === 0) continue;
|
|
124
|
+
const lower = trimmed.toLowerCase();
|
|
125
|
+
if (lower.includes("msys64") || lower.includes("mingw64")) {
|
|
126
|
+
candidates.push(join(trimmed, "bash.exe"));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
candidates.push("C:\\msys64\\msys2.exe"); // last resort — see the note above
|
|
130
|
+
return candidates;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `wsl.exe` always ships at this fixed location; it is never "not installed". */
|
|
134
|
+
export function defaultWslPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
135
|
+
const systemRoot = env.SystemRoot ?? "C:\\Windows";
|
|
136
|
+
return join(systemRoot, "System32", "wsl.exe");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ResolvedPaths {
|
|
140
|
+
pwsh?: string;
|
|
141
|
+
gitbash?: string;
|
|
142
|
+
msys2?: string;
|
|
143
|
+
wsl?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Per-backend executable overrides; an empty string means "auto-detect". */
|
|
147
|
+
export interface PathConfig {
|
|
148
|
+
pwshPath?: string;
|
|
149
|
+
gitBashPath?: string;
|
|
150
|
+
msys2Path?: string;
|
|
151
|
+
wslPath?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolve all four executables. A backend resolves to `undefined` when it is
|
|
156
|
+
* not installed — surface that as a loud per-call error rather than a silent
|
|
157
|
+
* fallback to another shell, or the user's explicit choice gets ignored.
|
|
158
|
+
*/
|
|
159
|
+
export function resolveAllPaths(config: PathConfig = {}, env: NodeJS.ProcessEnv = process.env): ResolvedPaths {
|
|
160
|
+
const pick = (override: string | undefined, candidates: readonly string[]): string | undefined =>
|
|
161
|
+
override !== undefined && override.trim().length > 0 ? override : resolveFromCandidates(candidates);
|
|
162
|
+
return {
|
|
163
|
+
pwsh: pick(config.pwshPath, candidatePwshPaths(env)),
|
|
164
|
+
gitbash: pick(config.gitBashPath, candidateGitBashPaths(env)),
|
|
165
|
+
msys2: pick(config.msys2Path, candidateMsys2Paths(env)),
|
|
166
|
+
// wsl.exe is not probed: it is a Windows component, so treat it as present.
|
|
167
|
+
wsl: config.wslPath !== undefined && config.wslPath.trim().length > 0 ? config.wslPath : defaultWslPath(env)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---- argv construction -----------------------------------------------------
|
|
172
|
+
|
|
173
|
+
/** Prepend the backend-specific command flags. `command` always goes last. */
|
|
174
|
+
export function buildArgv(
|
|
175
|
+
shell: ShellId,
|
|
176
|
+
command: string,
|
|
177
|
+
paths: ResolvedPaths,
|
|
178
|
+
distro?: string
|
|
179
|
+
): Array<string | undefined> {
|
|
180
|
+
switch (shell) {
|
|
181
|
+
case "powershell":
|
|
182
|
+
// -NonInteractive so a stray prompt can never hang the capture; the
|
|
183
|
+
// model's command is one complete script, not a REPL session.
|
|
184
|
+
return [paths.pwsh, "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command];
|
|
185
|
+
case "gitbash":
|
|
186
|
+
// -l (login shell) matters: it sources /etc/profile, which is what puts
|
|
187
|
+
// /usr/bin and /mingw64/bin on PATH. With a bare -c, `git`, `ssh` and
|
|
188
|
+
// friends are "command not found".
|
|
189
|
+
return [paths.gitbash, "-lc", command];
|
|
190
|
+
case "msys2":
|
|
191
|
+
// Same -l reasoning as gitbash, and it also picks up /etc/profile's
|
|
192
|
+
// MSYSTEM handling that puts gcc/make on PATH.
|
|
193
|
+
return [paths.msys2, "-lc", command];
|
|
194
|
+
case "wsl": {
|
|
195
|
+
const distroArg = distro !== undefined && distro.trim().length > 0 ? ["-d", distro.trim()] : [];
|
|
196
|
+
// `-e bash -lc` — not `wsl bash -lc`: `-e` runs the command directly and
|
|
197
|
+
// without a shell wrapper, so quoting survives intact.
|
|
198
|
+
return [paths.wsl, ...distroArg, "-e", "bash", "-lc", command];
|
|
199
|
+
}
|
|
200
|
+
default: {
|
|
201
|
+
const exhaustive: never = shell;
|
|
202
|
+
throw new Error(`invalid shell: ${JSON.stringify(exhaustive)} (expected one of ${SHELLS.join(", ")})`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---- environment -----------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Merge the host environment with per-backend fixups.
|
|
211
|
+
*
|
|
212
|
+
* @param inheritedWslenv - the ambient WSLENV to layer onto. Callers that
|
|
213
|
+
* replace the child environment wholesale (a PTY path, say) must pass
|
|
214
|
+
* `process.env.WSLENV` explicitly, because the ambient value is not visible
|
|
215
|
+
* through `dshEnv`.
|
|
216
|
+
*/
|
|
217
|
+
export function buildEnv(
|
|
218
|
+
shell: ShellId,
|
|
219
|
+
hostEnv?: Record<string, string>,
|
|
220
|
+
inheritedWslenv: string | undefined = process.env.WSLENV
|
|
221
|
+
): Record<string, string | undefined> {
|
|
222
|
+
const env: Record<string, string | undefined> = { ...ENV_OVERRIDES, ...hostEnv };
|
|
223
|
+
if (shell === "msys2") {
|
|
224
|
+
// Selects the MINGW64 environment, so /mingw64/bin (gcc, make, ...) joins
|
|
225
|
+
// PATH via /etc/profile. Without it MSYS2 defaults to the bare MSYS
|
|
226
|
+
// environment and the toolchain is invisible. An explicit caller value wins.
|
|
227
|
+
if (env.MSYSTEM === undefined) env.MSYSTEM = "MINGW64";
|
|
228
|
+
}
|
|
229
|
+
if (shell === "wsl") {
|
|
230
|
+
const keys = Object.keys(hostEnv ?? {});
|
|
231
|
+
if (keys.length > 0) {
|
|
232
|
+
// WSLENV is an allow-list: only the variables named in it cross into WSL.
|
|
233
|
+
// LAYER onto the inherited value, never rebuild it — dropping inherited
|
|
234
|
+
// entries (Windows Terminal exports e.g. `WT_SESSION:WT_PROFILE_ID:`)
|
|
235
|
+
// would silently stop them crossing.
|
|
236
|
+
//
|
|
237
|
+
// The base is SPLIT on ":" rather than concatenated because the host
|
|
238
|
+
// value ends with a trailing ":", so string concatenation would produce a
|
|
239
|
+
// malformed empty entry.
|
|
240
|
+
const declared = Object.prototype.hasOwnProperty.call(hostEnv ?? {}, "WSLENV");
|
|
241
|
+
const base = declared
|
|
242
|
+
? env.WSLENV
|
|
243
|
+
: (typeof inheritedWslenv === "string" && inheritedWslenv.length > 0 ? inheritedWslenv : env.WSLENV);
|
|
244
|
+
const parts = typeof base === "string" ? base.split(":") : [];
|
|
245
|
+
env.WSLENV = [...parts, ...keys.filter((k) => k !== "WSLENV").flatMap((k) => k.split(":"))]
|
|
246
|
+
.map((p) => p.trim())
|
|
247
|
+
.filter((p) => p.length > 0)
|
|
248
|
+
.join(":");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return env;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---- sandbox confinement ---------------------------------------------------
|
|
255
|
+
|
|
256
|
+
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Whether a backend can be wrapped by your sandbox seam, and what to report
|
|
260
|
+
* when it cannot.
|
|
261
|
+
*
|
|
262
|
+
* Only PowerShell is confinable on Windows. Git Bash and MSYS2 must NOT be
|
|
263
|
+
* wrapped: DSH's Windows ACL restricted-token runner cannot start a
|
|
264
|
+
* Cygwin/MSYS2 process — bash aborts during startup with
|
|
265
|
+
* `CreateFileMapping ... Win32 error 5` — so wrapping them would abort every
|
|
266
|
+
* command instead of confining it. WSL is its own Linux VM; that isolation IS
|
|
267
|
+
* the sandbox, so wrapping it is redundant.
|
|
268
|
+
*
|
|
269
|
+
* Publish this honestly: report `*-unconfined` rather than claiming a
|
|
270
|
+
* confinement that did not happen.
|
|
271
|
+
*/
|
|
272
|
+
export function confinementFor(mode: SandboxMode, shell: ShellId): string {
|
|
273
|
+
if (shell === "wsl") return "wsl-isolation";
|
|
274
|
+
if (shell === "gitbash") return "gitbash-unconfined";
|
|
275
|
+
if (shell === "msys2") return "msys2-unconfined";
|
|
276
|
+
return mode === "danger-full-access" ? "none" : "powershell-confined";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** True when the spawn argv must be passed through the sandbox facade. */
|
|
280
|
+
export function shouldConfine(mode: SandboxMode, shell: ShellId): boolean {
|
|
281
|
+
return mode !== "danger-full-access" && shell === "powershell";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---- the adapter table -----------------------------------------------------
|
|
285
|
+
|
|
286
|
+
export interface TerminalAdapter {
|
|
287
|
+
readonly id: ShellId;
|
|
288
|
+
/** Shown in the settings dropdown. */
|
|
289
|
+
readonly label: string;
|
|
290
|
+
/** One line describing the backend, suitable for a settings-row caption. */
|
|
291
|
+
readonly summary: string;
|
|
292
|
+
/** Model-facing tool description when this backend is active. */
|
|
293
|
+
readonly toolDescription: string;
|
|
294
|
+
/** Candidate executables, best first. */
|
|
295
|
+
candidates(env?: NodeJS.ProcessEnv): string[];
|
|
296
|
+
/** Path key in your config schema, or undefined when there is nothing to override. */
|
|
297
|
+
readonly configKey?: keyof PathConfig;
|
|
298
|
+
/** Whether this backend's spawn argv may be confined. */
|
|
299
|
+
readonly confinable: boolean;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const ADAPTERS: Record<ShellId, TerminalAdapter> = {
|
|
303
|
+
powershell: {
|
|
304
|
+
id: "powershell",
|
|
305
|
+
label: "PowerShell",
|
|
306
|
+
summary: "PowerShell 7 (pwsh), the Windows default",
|
|
307
|
+
toolDescription:
|
|
308
|
+
"Execute a PowerShell command (pwsh -NoLogo -NoProfile -NonInteractive -Command <command>) and return its stdout/stderr. PowerShell syntax; native Windows paths (C:\\...); environment variables via $env:NAME.",
|
|
309
|
+
candidates: candidatePwshPaths,
|
|
310
|
+
configKey: "pwshPath",
|
|
311
|
+
confinable: true
|
|
312
|
+
},
|
|
313
|
+
gitbash: {
|
|
314
|
+
id: "gitbash",
|
|
315
|
+
label: "Git Bash",
|
|
316
|
+
summary: "Git for Windows bash — POSIX syntax, /d/... paths",
|
|
317
|
+
toolDescription:
|
|
318
|
+
"Execute a bash command (Git for Windows bash -lc <command>) and return its stdout/stderr. POSIX syntax; paths like /d/workspace; PATH includes /usr/bin and /mingw64/bin so git, npm, ssh etc. work; environment variables via $NAME.",
|
|
319
|
+
candidates: candidateGitBashPaths,
|
|
320
|
+
configKey: "gitBashPath",
|
|
321
|
+
confinable: false
|
|
322
|
+
},
|
|
323
|
+
msys2: {
|
|
324
|
+
id: "msys2",
|
|
325
|
+
label: "MSYS2",
|
|
326
|
+
summary: "MSYS2 bash — POSIX syntax, /c/... paths, full GCC/mingw64 toolchain",
|
|
327
|
+
toolDescription:
|
|
328
|
+
"Execute a bash command (MSYS2 bash -lc <command>) and return its stdout/stderr. POSIX syntax; paths like /c/...; PATH includes /usr/bin and /mingw64/bin so git, npm, gcc, make etc. work; environment variables via $NAME. MSYS2 provides a full GCC/mingw64 toolchain.",
|
|
329
|
+
candidates: candidateMsys2Paths,
|
|
330
|
+
configKey: "msys2Path",
|
|
331
|
+
confinable: false
|
|
332
|
+
},
|
|
333
|
+
wsl: {
|
|
334
|
+
id: "wsl",
|
|
335
|
+
label: "WSL",
|
|
336
|
+
summary: "WSL Linux — /mnt/d/... paths, optional distro",
|
|
337
|
+
toolDescription:
|
|
338
|
+
"Execute a bash command in WSL (wsl [-d <distro>] -e bash -lc <command>) and return its stdout/stderr. Linux syntax; paths like /mnt/d/workspace; environment variables via $NAME (crossing the boundary through WSLENV).",
|
|
339
|
+
candidates: (env = process.env) => [defaultWslPath(env)],
|
|
340
|
+
configKey: "wslPath",
|
|
341
|
+
confinable: false
|
|
342
|
+
}
|
|
343
|
+
};
|
package/README.ja.md
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# dsh-bash-terminal-ts
|
|
2
|
-
|
|
3
|
-
[](https://github.com/drscrewdriver/dsh-bash-terminal-ts/actions/workflows/test.yml)
|
|
4
|
-
|
|
5
|
-
[English](README.en.md) | **日本語** | [한국어](README.ko.md) | [中文](README.md)
|
|
6
|
-
|
|
7
|
-
DSH(DeepSeek Harness)プラグイン:Windows 上で **PowerShell / Git Bash / MSYS2 / WSL** の 4 種類のターミナルコマンドを 1 つの `shell` ツールに統合して実行します。
|
|
8
|
-
|
|
9
|
-
ターミナルは**あなたが Web UI で選びます**。モデルは変更できません。一度選べば、以降のすべてのコマンドはそのターミナルで実行されます。
|
|
10
|
-
|
|
11
|
-
## なぜ使うのか
|
|
12
|
-
|
|
13
|
-
| 売り | ひとことで |
|
|
14
|
-
|------|--------|
|
|
15
|
-
| **MSYS2 が本当に動く** | 「ドロップダウンの選択肢を 1 つ足した」だけではありません。`bash.exe` の解決、ログインシェル、`MSYSTEM` 環境という 3 点をすべて正しく処理しています —— MSYS2 を選べばそのまま `gcc` や `make` が使えます(後述の「MSYS2 サポート」参照) |
|
|
16
|
-
| **TypeScript ソース** | `strict` + `noUncheckedIndexedAccess`。argv / env の構築は純関数が担当するため単体テスト可能です |
|
|
17
|
-
| **DSH 0.1.2 を主対象** | `engines.dsh: >=0.1.2-rc.1 <0.2.0-0`。0.1.2 の `ctx.subprocess` / `ctx.sandbox` / PTY の接合部に合わせて構築しています |
|
|
18
|
-
| **4 種類のターミナルを 1 つのツールで** | 同じ `shell` ツールが PowerShell / Git Bash / MSYS2 / WSL をカバーするので、モデルは 4 通りもの引数を覚える必要がありません |
|
|
19
|
-
| **サンドボックスは公式準拠** | 公式の `ctx.sandboxPolicy` + `ctx.sandbox` を使用し、fail-closed。拒否時には同一ターン内での昇格案内を返します |
|
|
20
|
-
| **対話型ターミナル** | 本物の PTY セッションツールも用意しています:`open / send / read / signal / close`。Ctrl+C が使え、ターンをまたいで状態を保持します |
|
|
21
|
-
|
|
22
|
-
## 対応バックエンド
|
|
23
|
-
|
|
24
|
-
| バックエンド | 実際の実行 | 構文 / パス | 環境変数 |
|
|
25
|
-
|------|----------|-------------|----------|
|
|
26
|
-
| `powershell`(デフォルト) | `pwsh -NoLogo -NoProfile -NonInteractive -Command <cmd>` | PowerShell;`C:\...` | `$env:NAME` |
|
|
27
|
-
| `gitbash` | Git for Windows `bash -lc <cmd>` | POSIX;`/d/WorkSpace`;PATH に `/usr/bin`、`/mingw64/bin` を含む | `$NAME` |
|
|
28
|
-
| `msys2` | `C:\msys64\usr\bin\bash.exe -lc <cmd>`(ログインシェル;`MSYSTEM=MINGW64`) | POSIX;完全な GCC / mingw64 ツールチェーンを同梱 | `$NAME` |
|
|
29
|
-
| `wsl` | `wsl [-d <distro>] -e bash -lc <cmd>` | Linux;`/mnt/d/...` | `$NAME`(WSLENV 経由) |
|
|
30
|
-
|
|
31
|
-
呼び出しごとにまったく新しいシェルが起動します:**状態は保持されません**(cwd / 変数 / エイリアス)。`cd` ではなく `workdir` を渡してください。ターンをまたいで状態を保持したい場合は対話型ターミナルツールを使ってください。
|
|
32
|
-
|
|
33
|
-
## MSYS2 サポート
|
|
34
|
-
|
|
35
|
-
MSYS2 は「バックエンドをもう 1 つ足すだけ」に見えますが、実際には 3 つの落とし穴があり、本プラグインはそれを 1 つずつ処理しています。
|
|
36
|
-
|
|
37
|
-
**1. `msys2.exe` は起動できません。**
|
|
38
|
-
`C:\msys64\msys2.exe` はコンソールウィンドウを割り当てる Cygwin ランチャーです。本プラグインはパイプ stdio で spawn しますが(これが DSH の標準方式です)、その場合これは **exit 0 でゼロバイトの出力を返します** —— コマンドは静かに失敗し、「成功」したように見えて実際には何も行われません。したがって候補の順序は `usr\bin\bash.exe` → `bin\bash.exe` → `msys2.exe` のフォールバックとなり、**利用可能な `bash.exe` が常に優先されます**。
|
|
39
|
-
|
|
40
|
-
**2. `-lc` は省略できません。**
|
|
41
|
-
`/etc/profile` を読むのはログインシェルだけで、`/usr/bin` と `/mingw64/bin` を PATH に追加するのも `/etc/profile` だけです。裸の `-c` を使うと `tr`、`sed`、`gcc` がすべて `command not found` になります。
|
|
42
|
-
|
|
43
|
-
**3. `MSYSTEM=MINGW64` を注入する必要があります。**
|
|
44
|
-
そうしないと `/etc/profile` がデフォルトの MSYS 環境として初期化され、`/mingw64/bin` にある gcc や make が使えません。本プラグインは `buildEnv` 経由で注入します(**あなたが明示的に設定した値が優先されます**)。また `shell` ツールと対話型ターミナルは同じ `buildEnv` を通るため、「ツールでは動くのにターミナルでは動かない」といったずれは発生しません。
|
|
45
|
-
|
|
46
|
-
実測(本物の PTY):プロンプトが `MSYS` から `MINGW64` に変わり、`command -v gcc` → `/mingw64/bin/gcc` となります。
|
|
47
|
-
|
|
48
|
-
これら 3 点はすべて回帰テストで守られています。`test/unit.ts` は `bash.exe` が `msys2.exe` より前に並ぶことを断言し、`test/apply.ts` は PTY 環境に `MSYSTEM=MINGW64` が含まれ、かつ gitbash に漏れないことを断言します。
|
|
49
|
-
|
|
50
|
-
## 設計上の要点
|
|
51
|
-
|
|
52
|
-
- **ターミナルはユーザーが決め、AI は変更できない**:Web UI の設定画面(設定 → 一般)に「デフォルトターミナル」ドロップダウン(PowerShell / Git Bash / MSYS2 / WSL)が表示されます。`shell` ツールは常にこの設定のみを使用し、モデルにターミナル引数を公開しません。設定は DSH settings システム(settings.yaml)で永続化されます。
|
|
53
|
-
- **`ctx.shell` 能力の接合部を占有しない**:DSH 標準のサンドボックス化された `pwsh` ツールはそのまま利用可能です。本プラグインの `shell` ツールは**追加の**マルチターミナル入口です。
|
|
54
|
-
- 共有された `ctx.subprocess` seam を通じてプロセスを派生させます:プロセスツリーの終了(Windows `taskkill /T`)、SIGTERM→grace→SIGKILL、出力の spill ファイル。公式の `dsh-tool-bash` / `dsh-tool-pwsh` と同じ挙動です。
|
|
55
|
-
- バックグラウンドタスクは共通の `jobs` registry に登録され、`run_in_background` / `job_output` / `job_kill` をサポートします。
|
|
56
|
-
- ツール引数 `shell` は列挙型です(UI ではドロップダウンとして自動レンダリングされます)。モデルは呼び出しごとにターミナルを自身で選択します。
|
|
57
|
-
- 4 つのバックエンドについて、フロントエンドのドロップダウン内の `<option>` と 2 つの言語パックの `shell.<id>` 文言には drift ガードテストがあります —— バックエンドを追加して文言の更新を忘れると、テストがそのまま失敗します。
|
|
58
|
-
|
|
59
|
-
## インストール
|
|
60
|
-
|
|
61
|
-
### 標準インストール(npm)
|
|
62
|
-
|
|
63
|
-
```powershell
|
|
64
|
-
# 1. プラグインパッケージをインストール
|
|
65
|
-
npm install -g dsh-bash-terminal-ts
|
|
66
|
-
dsh plugin --profile web add dsh-bash-terminal-ts
|
|
67
|
-
|
|
68
|
-
# 2. DSH 設定のホワイトリストを patch(DSH 側の制限。下記の説明を参照。install.ps1 でこの手順だけを単独実行することもできます)
|
|
69
|
-
powershell -ExecutionPolicy Bypass -File install.ps1 install
|
|
70
|
-
|
|
71
|
-
# 3. dsh web を再起動
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### ローカル開発インストール(junction 直結、ソース変更が即時反映)
|
|
75
|
-
|
|
76
|
-
```powershell
|
|
77
|
-
# 1. プラグインパッケージを profile の node_modules にリンク(junction なのでソース変更が即時反映されます)
|
|
78
|
-
$profile = "$env:USERPROFILE\.dsh\profiles\web"
|
|
79
|
-
New-Item -ItemType Junction -Path "$profile\node_modules\dsh-bash-terminal-ts" -Target "D:\WorkSpace\projects\dsh-bash-terminal-ts" | Out-Null
|
|
80
|
-
|
|
81
|
-
# 2. プラグインが @deepseek-ai/* 依存を解決できるようにする(profile の依存ツリーへ junction)
|
|
82
|
-
New-Item -ItemType Junction -Path "D:\WorkSpace\projects\dsh-bash-terminal-ts\node_modules\@deepseek-ai" -Target "$profile\..\node_modules\@deepseek-ai" | Out-Null
|
|
83
|
-
|
|
84
|
-
# 3. cordis.patch.yml にマウント行を追記(下記の patch 断片を参照)
|
|
85
|
-
# 4. (フロントエンドのソースを変更した場合のみ)client bundle を再ビルド:
|
|
86
|
-
# cd D:\WorkSpace\projects\dsh-bash-terminal-ts && node scripts/build-client.mjs
|
|
87
|
-
# 5. 設定 UI が本プラグインの設定書き込みを受け付けるようにする(DSH 側の制限。下記の説明を参照)
|
|
88
|
-
# 6. dsh web を再起動
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
> **DSH 設定 UI のホワイトリスト制限**:DSH の api-gateway(dsh-host-apiproxy)は、
|
|
92
|
-
> Web 設定クライアントに公開する settings namespace について**ハードコードされた
|
|
93
|
-
> ホワイトリスト**を持っています(サードパーティ製プラグインの設定はデフォルトで
|
|
94
|
-
> `settings-not-exposed` として拒否され、UI で変更しても反映されません)。
|
|
95
|
-
> install.ps1 はこのホワイトリストを自動で patch します(`bash-terminal` を追加し、
|
|
96
|
-
> 元のファイルは先にバックアップされます)。
|
|
97
|
-
> **DSH をアップグレードした後は install.ps1 を再実行して** patch を復元してください。アンインストール時には install.ps1 が元に戻します。
|
|
98
|
-
|
|
99
|
-
`cordis.patch.yml` への追記:
|
|
100
|
-
|
|
101
|
-
```yaml
|
|
102
|
-
- insert:
|
|
103
|
-
- id: tool-bash-terminal
|
|
104
|
-
name: 'dsh-bash-terminal-ts'
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
組み立てツリーの検証(再起動不要):
|
|
108
|
-
|
|
109
|
-
```powershell
|
|
110
|
-
node "$env:APPDATA\nvm\v24.16.0\node_modules\@deepseek-ai\dsh\lib\bin.js" --profile web --dump-config | Select-String dsh-bash-terminal-ts
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## 使い方
|
|
114
|
-
|
|
115
|
-
**ユーザーは Web UI でデフォルトターミナルを設定します**:設定(歯車)→ 一般 →「デフォルトターミナル」ドロップダウンから、PowerShell / Git Bash / MSYS2 / WSL のいずれかを選びます。変更は即時反映され、永続化されます。
|
|
116
|
-
|
|
117
|
-
モデルは `shell` ツールを認識すると、コマンド実行時にあなたが選んだターミナルを自動的に使用します(ツールはターミナル引数を公開しないため、モデルはあなたの選択を変更できません)。
|
|
118
|
-
|
|
119
|
-
- デフォルトターミナル = Git Bash の場合:`shell(command: "git status")` は Git Bash を通ります
|
|
120
|
-
- デフォルトターミナル = MSYS2 の場合:`shell(command: "gcc --version")` は MSYS2 を通ります(ログインシェルで、PATH に `/usr/bin` と `/mingw64/bin` を含み、完全な GCC / mingw64 ツールチェーンを同梱)
|
|
121
|
-
- デフォルトターミナル = WSL の場合:`shell(command: "ls -la /mnt/d/WorkSpace")` は WSL を通ります。`distro: "Ubuntu"` を渡すとディストリビューションを指定できます
|
|
122
|
-
- デフォルトターミナル = PowerShell の場合:`shell(command: "Get-Process node")` は PowerShell を通ります
|
|
123
|
-
|
|
124
|
-
## 設定
|
|
125
|
-
|
|
126
|
-
**Web UI 設定**(推奨):設定 → 一般 →「デフォルトターミナル」。
|
|
127
|
-
|
|
128
|
-
プラグイン row の `config`(デフォルトを上書きし、設定の composition の基準となります):
|
|
129
|
-
|
|
130
|
-
| キー | デフォルト | 説明 |
|
|
131
|
-
|----|------|------|
|
|
132
|
-
| `defaultShell` | `powershell` | 設定で上書きされていない場合のバックエンド |
|
|
133
|
-
| `timeoutMs` | 120000 | デフォルトのタイムアウト |
|
|
134
|
-
| `maxTimeoutMs` | 600000 | 呼び出し側 timeoutMs の上限 |
|
|
135
|
-
| `pwshPath` | 自動検出 | pwsh.exe のパスを固定 |
|
|
136
|
-
| `gitBashPath` | 自動検出 | git bash.exe のパスを固定 |
|
|
137
|
-
| `msys2Path` | 自動検出 | MSYS2 入口パスを固定(`bash.exe` を指す必要があり、`msys2.exe` を指してはいけません。「MSYS2 サポート」参照) |
|
|
138
|
-
| `wslPath` | 自動検出 | wsl.exe のパスを固定 |
|
|
139
|
-
|
|
140
|
-
## アンインストール
|
|
141
|
-
|
|
142
|
-
```powershell
|
|
143
|
-
Remove-Item "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-bash-terminal-ts" -Force
|
|
144
|
-
# あわせて cordis.patch.yml から insert ブロックを削除し、dsh web を再起動します
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## サンドボックス(公式機構との接続)
|
|
148
|
-
|
|
149
|
-
`shell` ツールは DSH 公式のサンドボックス接合部(`ctx.sandboxPolicy` + `ctx.sandbox`)を通ります:
|
|
150
|
-
|
|
151
|
-
- 呼び出しごとに現在のサンドボックス方針を解決します。`danger-full-access` のセッションではそのまま実行されます(ラップしません)。
|
|
152
|
-
- PowerShell / Git Bash / MSYS2 の各バックエンドは `ctx.sandbox.confine` を通じて argv をラップします —— 公式 executor と同じ **fail-closed** の意味論です:制限モードを要求したのに利用可能なバックエンドがない場合は `SandboxUnavailableError` を投げ、ラップなしの実行を拒否します。
|
|
153
|
-
- WSL バックエンドはラップしません:WSL の独立した Linux 仮想マシン自体が隔離であるためです(結果は `enforcement: wsl-isolation` を報告します)。
|
|
154
|
-
- 制限モードでサンドボックスに拒否された場合、結果には公式のマーカー `[sandbox: file access denied under <mode> mode]` と同一ターン内での昇格案内が付与されます。モデルは `sandbox_permissions` + `justification` によって 1 回の昇格を申請できます(`ctx.approval` を通じたユーザー承認を経由)。公式の bash/pwsh ツールと完全に同じです。
|
|
155
|
-
- 注意:DSH の Windows ACL サンドボックス launcher(`node-addon-landlock-run-win32-x64`)は現在まだ npm で公開されていないため、本機のサンドボックスバックエンドは当面利用できません。アーキテクチャはすでに整っており、DSH の公開後には自動的に有効になります。
|
|
156
|
-
|
|
157
|
-
## ⚠️ セキュリティに関する注意
|
|
158
|
-
|
|
159
|
-
`shell` ツールのコマンドは**DSH サンドボックスの外側**で実行され、dsh プロセスと同じ権限を持ちます(完全アクセスのコマンド実行に相当します)。
|
|
160
|
-
`pwsh` ツールの ConstrainedLanguage 制限は適用されません。DSH のファイル操作ツール(read/write/edit)は引き続きファイルサンドボックスの制約を受けます。
|
|
161
|
-
信頼できるセッションでのみ使用してください。サンドボックスで保護された PowerShell が必要な場合は、引き続き公式の `pwsh` ツールを使用してください。
|
|
162
|
-
|
|
163
|
-
## 既知の制限
|
|
164
|
-
|
|
165
|
-
- 本プラグインは `win32` プラットフォームでのみツールを登録します。
|
|
166
|
-
- WSL のバックグラウンドプロセスは、タイムアウトや中断の後、ディストリビューション内に一時的に残ることがあります(WSL インスタンスは最後のプロセスが終了した時点で自動的に終了します)。
|
|
167
|
-
- Git Bash と MSYS2 はどちらも msys2 環境であり、WSL の Linux とは挙動に差があります(パスマッピング、パッケージの可用性)。
|
|
168
|
-
- `C:\msys64` がデフォルト以外の場所にインストールされていて PATH 上にない場合は、`msys2Path` を明示的に設定する必要があります。
|
|
169
|
-
|
|
170
|
-
## テスト
|
|
171
|
-
|
|
172
|
-
```powershell
|
|
173
|
-
git clone https://github.com/drscrewdriver/dsh-bash-terminal-ts.git
|
|
174
|
-
cd dsh-bash-terminal-ts
|
|
175
|
-
npm install # 依存をインストール(typescript を含む)
|
|
176
|
-
npm run build # tsc でコンパイル src/*.ts → lib/*.js;client.tsx → dist/client.js;test/*.ts → test-dist/
|
|
177
|
-
npm test # node test-dist/unit.js → apply.js → client.js
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
CI は `windows-latest` 上で同じ一式を実行します(`.github/workflows/test.yml`)。
|
|
181
|
-
|
|
182
|
-
## 技術実装
|
|
183
|
-
|
|
184
|
-
動作要件:**Node.js 22+(24 を推奨)**、DSH 0.1.2+。
|
|
185
|
-
|
|
186
|
-
ソースは TypeScript(`strict` + `noUncheckedIndexedAccess`)で、コンパイル成果物 `lib/`、`dist/` はリポジトリにコミットされています。DSH は `lib/index.js` をそのまま読み込むため、**インストール不要で使用できます**。
|
|
187
|
-
|
|
188
|
-
require 側の依存(`@deepseek-ai/*` など 13 パッケージ)はすべて `peerDependencies` + `peerDependenciesMeta.optional` として宣言し、ホストが同梱するコピーとの重複インストールを避けています。
|
|
189
|
-
|
|
190
|
-
## 謝辞
|
|
191
|
-
|
|
192
|
-
本プロジェクトは [MAXeaglet/dsh-bash-terminal](https://github.com/MAXeaglet/dsh-bash-terminal) を基に発展させたものです —— オリジナルの `shell` ツール、PowerShell / Git Bash / WSL の 3 バックエンド構成、およびサンドボックス接合部との接続はいずれも原作者によるものです。本バージョンではこれに MSYS2 バックエンド、TypeScript への書き直し、DSH 0.1.2 への適合を加えています。
|
|
193
|
-
|
|
194
|
-
## ライセンス
|
|
195
|
-
|
|
196
|
-
MIT
|