char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somadd powershell tool for windowsae32111

main
3.5 KiB102 linesraw
1import { Buffer } from "node:buffer";
2import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3import type { Environment } from "@berry/api";
4
5function variable(env: Environment, name: string): string | undefined {
6  // windows spells its variables in any case
7  return Object.entries(env.variables).find(([key]) =>
8    env.platform === "win32" ? key.toUpperCase() === name : key === name
9  )?.[1];
10}
11
12async function findCommand(env: Environment, command: string): Promise<string | undefined> {
13  const windows = env.platform === "win32";
14  const names = windows
15    ? (variable(env, "PATHEXT") ?? ".EXE").split(";").filter(Boolean).map((ext) => command + ext)
16    : [command];
17  for (const dir of (variable(env, "PATH") ?? "").split(windows ? ";" : ":").filter(Boolean)) {
18    for (const name of names) {
19      const file = await env.joinPath([dir.replace(/^"(.*)"$/, "$1"), name], BACKGROUND_CONTEXT);
20      if (!file.ok) continue;
21      const info = await env.fileInfo(file.value, BACKGROUND_CONTEXT);
22      if (info.ok && info.value.kind !== "directory") return file.value;
23    }
24  }
25}
26
27const commands = new Map<string, Promise<boolean>>();
28
29/** Whether `command` is on the environment's PATH: asked once of each machine and PATH. */
30export function hasCommand(env: Environment, command: string): Promise<boolean> {
31  const key = JSON.stringify([
32    env.id,
33    env.cwd,
34    env.platform,
35    variable(env, "PATH"),
36    variable(env, "PATHEXT"),
37    command,
38  ]);
39  let found = commands.get(key);
40  if (found === undefined) {
41    found = findCommand(env, command).then((path) => path !== undefined);
42    commands.set(key, found);
43  }
44  return found;
45}
46
47/** PowerShell on this machine, favoring PS7 even when only PS5 is on PATH. */
48export async function powershellCommand(env: Environment, command: string): Promise<string[]> {
49  let shell = await findCommand(env, "pwsh");
50  for (
51    const [root, parts] of [
52      ["PROGRAMFILES", ["PowerShell", "7", "pwsh.exe"]],
53      ["PROGRAMW6432", ["PowerShell", "7", "pwsh.exe"]],
54    ] as const
55  ) {
56    if (shell !== undefined) break;
57    const dir = variable(env, root);
58    if (!dir) continue;
59    const file = await env.joinPath([dir, ...parts], BACKGROUND_CONTEXT);
60    if (!file.ok) continue;
61    const info = await env.fileInfo(file.value, BACKGROUND_CONTEXT);
62    if (info.ok && info.value.kind !== "directory") shell = file.value;
63  }
64  shell ??= await findCommand(env, "powershell");
65  if (shell === undefined) {
66    const root = variable(env, "SYSTEMROOT");
67    if (root) {
68      const file = await env.joinPath([
69        root,
70        "System32",
71        "WindowsPowerShell",
72        "v1.0",
73        "powershell.exe",
74      ], BACKGROUND_CONTEXT);
75      if (file.ok) {
76        const info = await env.fileInfo(file.value, BACKGROUND_CONTEXT);
77        if (info.ok && info.value.kind !== "directory") shell = file.value;
78      }
79    }
80  }
81  if (shell === undefined) {
82    throw new Error("PowerShell not found. Install PowerShell 7 or add it to PATH.");
83  }
84  // encoded commands survive windows argument quoting and PS5's legacy input encoding
85  const script = [
86    "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
87    "$OutputEncoding = [Console]::OutputEncoding",
88    "$ProgressPreference = 'SilentlyContinue'",
89    command,
90    "if ($?) { exit 0 }; if ($LASTEXITCODE) { exit $LASTEXITCODE }; exit 1",
91  ].join("\n");
92  return [
93    shell,
94    "-NoLogo",
95    "-NoProfile",
96    "-NonInteractive",
97    "-OutputFormat",
98    "Text",
99    "-EncodedCommand",
100    Buffer.from(script, "utf16le").toString("base64"),
101  ];
102}