privateer-agent 0.6.2 → 0.6.3

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.
@@ -12,7 +12,7 @@
12
12
  //
13
13
  // Ported from the original bash launcher; behaviour is intended to match exactly.
14
14
 
15
- import { spawn } from "node:child_process";
15
+ import { spawn, spawnSync } from "node:child_process";
16
16
  import fs from "node:fs";
17
17
  import os from "node:os";
18
18
  import path from "node:path";
@@ -80,7 +80,8 @@ if (sub === "update") {
80
80
  }
81
81
  } else {
82
82
  console.log("Updating privateer-agent to the latest release…");
83
- runToCompletion(isWin ? "npm.cmd" : "npm", ["install", "-g", "privateer-agent@latest"]);
83
+ // npm is npm.cmd on Windows; Node >=18.20 needs a shell to spawn a .cmd (EINVAL otherwise).
84
+ runToCompletion(isWin ? "npm.cmd" : "npm", ["install", "-g", "privateer-agent@latest"], { shell: isWin });
84
85
  }
85
86
  // runToCompletion exits via the child's exit handler.
86
87
  }
@@ -96,6 +97,12 @@ else if (sub === "daemon") {
96
97
 
97
98
  // --- normal launch: install the moat, then exec Pi's TUI -------------------
98
99
  else {
100
+ // Windows has no bash out of the box, but Privateer's command tool needs one. If a
101
+ // real bash isn't reachable, stop here with a clear, actionable message — otherwise
102
+ // the user boots fine and only hits a cryptic `'bash' is not recognized` the first
103
+ // time the agent tries to run a command. Unix always has a shell, so this is a no-op.
104
+ ensureShellOrExit();
105
+
99
106
  const AGENT_DIR = path.join(PRIVATEER_HOME, "agent");
100
107
  const EXT_DIR = path.join(AGENT_DIR, "extensions");
101
108
  fs.mkdirSync(EXT_DIR, { recursive: true });
@@ -176,6 +183,62 @@ else {
176
183
  }
177
184
 
178
185
  // --- helpers ---------------------------------------------------------------
186
+
187
+ // Preflight: on Windows, make sure a bash the command tool can use actually exists.
188
+ // Mirrors pi-coding-agent's resolver (utils/shell.js getShellConfig): an explicit
189
+ // shellPath override wins, then Git Bash in Program Files, then any bash.exe on PATH.
190
+ // If none resolve, print branded install guidance and exit before the TUI loads.
191
+ function ensureShellOrExit() {
192
+ if (!isWin) return; // macOS/Linux always ship /bin/sh (bash); nothing to check.
193
+
194
+ // Respect an explicit shellPath in the agent settings — if the user set one, defer
195
+ // to Pi's own resolver (it validates the path and reports its own error).
196
+ try {
197
+ const s = JSON.parse(fs.readFileSync(path.join(PRIVATEER_HOME, "agent", "settings.json"), "utf8"));
198
+ if (s && typeof s.shellPath === "string" && s.shellPath.trim()) return;
199
+ } catch { /* no settings file / no override — fall through to detection */ }
200
+
201
+ if (findWindowsBash()) return; // a usable bash is reachable — carry on.
202
+
203
+ const msg = [
204
+ "",
205
+ " ⚓ Privateer needs a bash shell to run commands, and Windows doesn't ship one.",
206
+ "",
207
+ " Fix it with any ONE of these, then run `privateer` again:",
208
+ "",
209
+ " 1. Install Git for Windows (recommended) — bundles Git Bash where Privateer",
210
+ " looks first, no config needed: https://git-scm.com/download/win",
211
+ "",
212
+ " 2. Use WSL — run Privateer inside a WSL shell, or install it with",
213
+ " `wsl --install` from an admin PowerShell.",
214
+ "",
215
+ " 3. Already have Cygwin/MSYS2? Add its bash.exe to PATH, or set \"shellPath\"",
216
+ " to your bash.exe in your Privateer settings.json.",
217
+ "",
218
+ " After installing, open a NEW terminal (PATH changes don't reach open windows).",
219
+ "",
220
+ ].join("\n");
221
+ process.stderr.write(msg + "\n");
222
+ process.exit(1);
223
+ }
224
+
225
+ // Return the path to a usable bash.exe on Windows, or null. Same search order as
226
+ // pi-coding-agent: Git Bash under %ProgramFiles%[(x86)], then `where bash.exe`.
227
+ function findWindowsBash() {
228
+ const candidates = [];
229
+ if (process.env.ProgramFiles) candidates.push(path.join(process.env.ProgramFiles, "Git", "bin", "bash.exe"));
230
+ if (process.env["ProgramFiles(x86)"]) candidates.push(path.join(process.env["ProgramFiles(x86)"], "Git", "bin", "bash.exe"));
231
+ for (const c of candidates) if (fs.existsSync(c)) return c;
232
+ try {
233
+ const r = spawnSync("where", ["bash.exe"], { encoding: "utf8", timeout: 5000, windowsHide: true });
234
+ if (r.status === 0 && r.stdout) {
235
+ const first = r.stdout.trim().split(/\r?\n/)[0];
236
+ if (first && fs.existsSync(first)) return first;
237
+ }
238
+ } catch { /* `where` unavailable — treat as not found */ }
239
+ return null;
240
+ }
241
+
179
242
  function haveTinfoilKey() {
180
243
  if (process.env.TINFOIL_API_KEY) return true;
181
244
  try { return /^TINFOIL_API_KEY=.+/m.test(fs.readFileSync(ENV_FILE, "utf8")); }
@@ -206,9 +269,17 @@ function refreshUpdateCache() {
206
269
  .then((j) => write(String(j?.tag_name || "").replace(/^v/, "")))
207
270
  .catch(() => { /* offline — keep stale cache */ });
208
271
  } else {
209
- const p = spawn(isWin ? "npm.cmd" : "npm", ["view", "privateer-agent", "version"], {
210
- stdio: ["ignore", "pipe", "ignore"],
211
- });
272
+ // On Windows `npm` is npm.cmd; Node >=18.20 throws EINVAL synchronously when
273
+ // spawning a .cmd without a shell, so run through a shell there and guard the
274
+ // call — this is a fire-and-forget cache refresh and must never break launch.
275
+ let p;
276
+ try {
277
+ p = spawn(isWin ? "npm.cmd" : "npm", ["view", "privateer-agent", "version"], {
278
+ stdio: ["ignore", "pipe", "ignore"],
279
+ shell: isWin,
280
+ windowsHide: true,
281
+ });
282
+ } catch { return; /* no npm / spawn refused — keep stale cache */ }
212
283
  let out = "";
213
284
  p.stdout.on("data", (d) => { out += d; });
214
285
  p.on("close", () => write(out.trim()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "privateer-agent",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Privateer — a provider-agnostic, safe-by-default terminal coding agent with TEE/Tinfoil attestation, rebuilt on the Pi toolkit. Bring your own model across 20 providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  "venice"
29
29
  ],
30
30
  "bin": {
31
- "privateer": "./bin/privateer-tui"
31
+ "privateer": "./bin/privateer-launch.mjs"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"