pi-mcp-adapter 5.2.0 → 5.3.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/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { spawnSync } from "node:child_process";
2
+ import { spawnSync, type SpawnSyncOptionsWithStringEncoding } from "node:child_process";
3
3
  import { existsSync, realpathSync } from "node:fs";
4
4
  import { homedir, platform } from "node:os";
5
5
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -180,6 +180,41 @@ export function interpolateEnvRecord(values: Record<string, string> | undefined,
180
180
  const COMMAND_SECRET_TIMEOUT_MS = 10_000;
181
181
  const COMMAND_SECRET_MAX_OUTPUT_BYTES = 1024 * 1024;
182
182
 
183
+ let windowsCommandBash: string | null | undefined;
184
+
185
+ function isLegacyWslBash(path: string): boolean {
186
+ return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(path.replace(/\//g, "\\").toLowerCase());
187
+ }
188
+
189
+ function findWindowsBash(): string | null {
190
+ const installs = [process.env.ProgramFiles, process.env["ProgramFiles(x86)"]]
191
+ .filter((dir): dir is string => Boolean(dir))
192
+ .map(dir => `${dir}\\Git\\bin\\bash.exe`);
193
+ const found = installs.find(path => existsSync(path));
194
+ if (found) return found;
195
+ try {
196
+ const result = spawnSync("where", ["bash.exe"], { encoding: "utf8", timeout: 5000, windowsHide: true });
197
+ if (result.status !== 0 || !result.stdout) return null;
198
+ return result.stdout.split(/\r?\n/)
199
+ .map(line => line.trim())
200
+ .find(path => path && !isLegacyWslBash(path) && existsSync(path)) ?? null;
201
+ } catch {
202
+ return null;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * On Windows, the bash used to run `!command` values: Git Bash in its standard
208
+ * install locations, then bash.exe on PATH (skipping the legacy WSL launcher),
209
+ * as Pi does. Undefined elsewhere or when none is found, so callers keep the
210
+ * platform default shell.
211
+ */
212
+ export function getWindowsCommandBash(): string | undefined {
213
+ if (process.platform !== "win32") return undefined;
214
+ if (windowsCommandBash === undefined) windowsCommandBash = findWindowsBash();
215
+ return windowsCommandBash ?? undefined;
216
+ }
217
+
183
218
  /** Resolve a secret value, executing only a single leading `!` command marker. */
184
219
  export function resolveCommandSecret(value: string, context: string): string;
185
220
  export function resolveCommandSecret(value: undefined, context: string): undefined;
@@ -189,14 +224,17 @@ export function resolveCommandSecret(value: string | undefined, context: string)
189
224
  if (value.startsWith("!!")) return interpolateEnvVars(value.slice(1));
190
225
  if (!value.startsWith("!")) return interpolateEnvVars(value);
191
226
 
192
- const result = spawnSync(value.slice(1), {
193
- shell: true,
227
+ const options: SpawnSyncOptionsWithStringEncoding = {
194
228
  encoding: "utf8",
195
229
  timeout: COMMAND_SECRET_TIMEOUT_MS,
196
230
  maxBuffer: COMMAND_SECRET_MAX_OUTPUT_BYTES,
197
231
  stdio: ["ignore", "pipe", "ignore"],
198
232
  windowsHide: true,
199
- });
233
+ };
234
+ const bash = getWindowsCommandBash();
235
+ const result = bash === undefined
236
+ ? spawnSync(value.slice(1), { ...options, shell: true })
237
+ : spawnSync(bash, ["-c", value.slice(1)], { ...options, shell: false });
200
238
  if (result.error) {
201
239
  const code = (result.error as NodeJS.ErrnoException).code;
202
240
  const reason = code === "ETIMEDOUT"