pi-bridge 0.2.2 → 0.2.6

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/bin/pii.js CHANGED
@@ -14,20 +14,24 @@ if (process.argv.includes('--version') || process.argv.includes('-v') || process
14
14
  process.exit(0);
15
15
  }
16
16
 
17
- function findCli() {
18
- // 1. Standard npm global: pi-bridge and pi share the same node_modules prefix
17
+ function findPi() {
18
+ // 1. Standard npm global: both packages share the same node_modules prefix
19
19
  const shared = path.resolve(__dirname, '..', '..', '@mariozechner', 'pi-coding-agent', 'dist', 'cli.js');
20
- if (fs.existsSync(shared)) return shared;
20
+ if (fs.existsSync(shared)) {
21
+ return { cli: shared, nodeExe: process.execPath };
22
+ }
21
23
 
22
24
  // 2. Unix GitHub clone: which pi returns the JS file directly
23
25
  if (os.platform() !== 'win32') {
24
26
  try {
25
27
  const wrapper = execFileSync('which', ['pi'], { encoding: 'utf8' }).trim();
26
- if (fs.existsSync(wrapper)) return wrapper;
28
+ if (fs.existsSync(wrapper)) return { cli: wrapper, nodeExe: process.execPath };
27
29
  } catch (_) {}
28
30
  }
29
31
 
30
- // 3. Windows fallback: PowerShell Get-Command finds pi even in custom/non-PATH locations
32
+ // 3. Fallback: PowerShell Get-Command — handles custom/bundled Windows installs
33
+ // where pi lives outside the standard npm prefix. Also picks up the bundled
34
+ // node.exe that shipped alongside pi (required for version-matched execution).
31
35
  if (os.platform() === 'win32') {
32
36
  try {
33
37
  const wrapper = execFileSync(
@@ -35,16 +39,23 @@ function findCli() {
35
39
  ['-NoProfile', '-Command', '(Get-Command pi -ErrorAction Stop).Source'],
36
40
  { encoding: 'utf8' }
37
41
  ).trim();
38
- const sibling = wrapper.replace(/\.(ps1|cmd)$/i, '.js');
39
- if (fs.existsSync(sibling)) return sibling;
42
+ const basedir = path.dirname(wrapper);
43
+ const cli = wrapper.replace(/\.(ps1|cmd)$/i, '.js');
44
+ const localNode = path.join(basedir, 'node.exe');
45
+ const nodeExe = fs.existsSync(localNode) ? localNode : process.execPath;
46
+ if (fs.existsSync(cli)) return { cli, nodeExe };
40
47
  } catch (_) {}
41
48
  }
42
49
 
43
50
  return null;
44
51
  }
45
52
 
46
- const cli = findCli();
47
- if (!cli) {
53
+ let pi;
54
+ try {
55
+ pi = findPi();
56
+ } catch (_) {}
57
+
58
+ if (!pi) {
48
59
  process.stderr.write(
49
60
  'pii: Could not find pi.\n' +
50
61
  'Install it with: npm install -g @mariozechner/pi-coding-agent\n'
@@ -53,8 +64,8 @@ if (!cli) {
53
64
  }
54
65
 
55
66
  const result = spawnSync(
56
- process.execPath,
57
- ['--require', preload, cli, ...process.argv.slice(2)],
67
+ pi.nodeExe,
68
+ ['--require', preload, pi.cli, ...process.argv.slice(2)],
58
69
  { stdio: 'inherit' }
59
70
  );
60
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-bridge",
3
- "version": "0.2.2",
3
+ "version": "0.2.6",
4
4
  "description": "Transparent filesystem bridge that makes pi run natively on a remote machine over SSH",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -29,11 +29,33 @@ function patchChildProcess(fakeRoot, remote) {
29
29
  return typeof c === 'string' ? path.resolve(c) : c.toString();
30
30
  }
31
31
 
32
+ /**
33
+ * On Windows, pi may call spawn with a full exe path like
34
+ * C:\Program Files\Git\bin\bash.exe. Strip it to the bare command name
35
+ * ('bash') so the remote Linux shell can find it.
36
+ */
37
+ function normalizeCmd(file) {
38
+ if (process.platform !== 'win32') return file;
39
+ return path.basename(String(file)).replace(/\.exe$/i, '');
40
+ }
41
+
42
+ /**
43
+ * Returns true for commands that must always run locally, never over SSH.
44
+ * - node / node.exe: pi's internal tooling (session export, compaction, …)
45
+ * spawns Node with large payloads as args; redirecting those over SSH would
46
+ * produce an ENAMETOOLONG error on Windows and is never correct anyway.
47
+ */
48
+ function isLocalOnly(file) {
49
+ const name = normalizeCmd(file).toLowerCase();
50
+ return name === 'node';
51
+ }
52
+
32
53
  /**
33
54
  * Build the SSH-redirected command: cd to remote cwd, then run the
34
55
  * original command with its arguments, all shell-quoted.
35
56
  */
36
57
  function buildSshArgs(file, args, opts) {
58
+ file = normalizeCmd(file);
37
59
  const fakeCwd = effectiveCwd(opts);
38
60
  const remoteCwd = toRemotePath(fakeCwd, fakeRoot);
39
61
 
@@ -54,7 +76,7 @@ function patchChildProcess(fakeRoot, remote) {
54
76
  opts = opts || {};
55
77
 
56
78
  const cwd = effectiveCwd(opts);
57
- if (!isFakePath(cwd, fakeRoot)) return origSpawn(file, args, opts);
79
+ if (!isFakePath(cwd, fakeRoot) || isLocalOnly(file)) return origSpawn(file, args, opts);
58
80
 
59
81
  return origSpawn('ssh', buildSshArgs(file, args, opts), Object.assign({}, opts, { cwd: undefined }));
60
82
  };
@@ -66,7 +88,7 @@ function patchChildProcess(fakeRoot, remote) {
66
88
  opts = opts || {};
67
89
 
68
90
  const cwd = effectiveCwd(opts);
69
- if (!isFakePath(cwd, fakeRoot)) return origSpawnSync(file, args, opts);
91
+ if (!isFakePath(cwd, fakeRoot) || isLocalOnly(file)) return origSpawnSync(file, args, opts);
70
92
 
71
93
  return origSpawnSync('ssh', buildSshArgs(file, args, opts), Object.assign({}, opts, { cwd: undefined }));
72
94
  };
@@ -38,6 +38,15 @@ function makeEisdir(p) {
38
38
  );
39
39
  }
40
40
 
41
+ /**
42
+ * Returns true if the path looks like a bash executable.
43
+ * Used on Windows to fake bash presence so pi's shell-detection passes.
44
+ */
45
+ function isBashPath(p) {
46
+ const n = String(p).replace(/\\/g, '/').toLowerCase();
47
+ return n.endsWith('/bash.exe') || n.endsWith('/bash') || n === '/bin/bash';
48
+ }
49
+
41
50
  function rethrowRemote(e, p) {
42
51
  if (!e || !e.message) throw e;
43
52
  if (e.message.includes('403')) throw makeEnoent(p);
@@ -176,6 +185,11 @@ function patchFs(fakeRoot, port, token) {
176
185
 
177
186
  fs.existsSync = function patchedExistsSync(p) {
178
187
  const s = abs(p);
188
+ // On Windows, pi checks for bash locally before using the shell tool.
189
+ // The check never reaches our SSH bridge (it's just an existsSync call).
190
+ // Fake it so pi believes bash is available; the actual spawn is intercepted
191
+ // and the Windows exe path is normalised to 'bash' before going over SSH.
192
+ if (process.platform === 'win32' && isBashPath(s)) return true;
179
193
  if (!s || !isFakePath(s, fakeRoot)) return orig.existsSync(p);
180
194
  try {
181
195
  return httpGet(port, token, '/exists?path=' + encodeURIComponent(rpath(s))).trim() === '1';