pi-bridge 0.3.4 → 0.3.5

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
@@ -1,77 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { spawnSync, execFileSync } = require('child_process');
4
+ const { spawnSync } = require('child_process');
5
5
  const path = require('path');
6
- const fs = require('fs');
7
- const os = require('os');
8
6
 
9
7
  const pkg = require('../package.json');
10
8
  const preload = path.join(__dirname, '..', 'src', 'local', 'preload.js');
9
+ const { findPi } = require('../src/local/find-pi.js');
11
10
 
12
11
  if (process.argv.includes('--version') || process.argv.includes('-v') || process.argv.includes('--v')) {
13
12
  process.stdout.write('pi-bridge/' + pkg.version + '\n');
14
13
  process.exit(0);
15
14
  }
16
15
 
17
- // List every `pi` on the PATH that pii inherited from its launching shell.
18
- // We resolve against that PATH (not a fresh `-NoProfile` shell) so that bundled
19
- // installs added to PATH via the user's profile are actually visible.
20
- function locateOnPath(cmd) {
21
- const finder = os.platform() === 'win32' ? 'where.exe' : 'which';
22
- try {
23
- const out = execFileSync(finder, [cmd], { encoding: 'utf8' });
24
- return out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
25
- } catch (_) {
26
- return [];
27
- }
28
- }
29
-
30
- function findPi() {
31
- // 1. Standard npm global: pii and pi share the same node_modules prefix.
32
- // Prefer the current @earendil-works scope; keep @mariozechner as a legacy
33
- // fallback for machines that installed pi before it moved off that scope.
34
- const sharedCandidates = [
35
- path.resolve(__dirname, '..', '..', '@earendil-works', 'pi-coding-agent', 'dist', 'cli.js'),
36
- path.resolve(__dirname, '..', '..', '@mariozechner', 'pi-coding-agent', 'dist', 'cli.js'),
37
- ];
38
- for (const cli of sharedCandidates) {
39
- if (fs.existsSync(cli)) return { cli, nodeExe: process.execPath };
40
- }
41
-
42
- // 2. Resolve `pi` from PATH and follow it to its real JS entrypoint. The
43
- // pi.dev installer (curl https://pi.dev/install.sh | sh) drops a bundled
44
- // Node plus a bare `bin/pi` symlink that points straight at the package's
45
- // cli.js — often outside any npm prefix — so we realpath the symlink rather
46
- // than guessing filenames. Also covers npm shims and dev clones (which
47
- // expose pi as a symlink to cli.js too), and a bundled node next to pi is
48
- // preferred for version-matched execution.
49
- const exe = os.platform() === 'win32' ? '.exe' : '';
50
- for (const cmd of locateOnPath('pi')) {
51
- let real;
52
- try { real = fs.realpathSync(cmd); } catch (_) { real = cmd; }
53
-
54
- let cli = null;
55
- if (/\.[cm]?js$/i.test(real) && fs.existsSync(real)) {
56
- cli = real; // symlink/shim -> cli.js
57
- } else if (fs.existsSync(path.join(path.dirname(cmd), 'pi.js'))) {
58
- cli = path.join(path.dirname(cmd), 'pi.js'); // bundled wrapper + sibling
59
- } else if (!/\.(cmd|ps1|bat|exe)$/i.test(real)) {
60
- cli = real; // shebang JS with no extension
61
- }
62
- if (!cli) continue;
63
-
64
- const localNode = path.join(path.dirname(cmd), 'node' + exe);
65
- const nodeExe = fs.existsSync(localNode) ? localNode : process.execPath;
66
- return { cli, nodeExe };
67
- }
68
-
69
- return null;
70
- }
71
-
72
16
  let pi;
73
17
  try {
74
- pi = findPi();
18
+ pi = findPi({ bridgeBinDir: __dirname });
75
19
  } catch (_) {}
76
20
 
77
21
  if (!pi) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-bridge",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Transparent filesystem bridge that makes pi run natively on a remote machine over SSH",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ // Locates the pi CLI entrypoint (a JS file) and the node binary to run it with.
4
+ // Used by bin/pii.js; kept separate so it can be unit-tested.
5
+
6
+ const { execFileSync } = require('child_process');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+
10
+ // Prefer the current @earendil-works scope; keep @mariozechner as a legacy
11
+ // fallback for machines that installed pi before it moved off that scope.
12
+ const PI_PACKAGES = [
13
+ path.join('@earendil-works', 'pi-coding-agent'),
14
+ path.join('@mariozechner', 'pi-coding-agent'),
15
+ ];
16
+
17
+ // List every `pi` on the PATH that pii inherited from its launching shell.
18
+ // We resolve against that PATH (not a fresh `-NoProfile` shell) so that bundled
19
+ // installs added to PATH via the user's profile are actually visible.
20
+ function locateOnPath(cmd, platform = process.platform) {
21
+ const finder = platform === 'win32' ? 'where.exe' : 'which';
22
+ try {
23
+ const out = execFileSync(finder, [cmd], { encoding: 'utf8' });
24
+ return out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
25
+ } catch (_) {
26
+ return [];
27
+ }
28
+ }
29
+
30
+ // Resolve a pi package's CLI from its package.json `bin` field, so we follow
31
+ // whatever entrypoint the installed version declares (dist/cli.js in older
32
+ // releases, dist/bundle/cli.js since 1.5).
33
+ function cliFromPackage(pkgDir) {
34
+ let rel = null;
35
+ try {
36
+ const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
37
+ rel = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin && pkg.bin.pi;
38
+ } catch (_) {
39
+ return null;
40
+ }
41
+ const candidates = [rel, 'dist/bundle/cli.js', 'dist/cli.js'].filter(Boolean);
42
+ for (const c of candidates) {
43
+ const cli = path.resolve(pkgDir, c);
44
+ if (fs.existsSync(cli)) return cli;
45
+ }
46
+ return null;
47
+ }
48
+
49
+ function cliFromNodeModules(nodeModulesDir) {
50
+ for (const name of PI_PACKAGES) {
51
+ const cli = cliFromPackage(path.join(nodeModulesDir, name));
52
+ if (cli) return cli;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ // True if the file is something node can execute: either it has a node
58
+ // shebang or no shebang at all. Rejects npm's extensionless `sh` shims
59
+ // (`#!/bin/sh` + `basedir=$(dirname ...)`), which node would choke on.
60
+ function looksLikeNodeScript(file) {
61
+ let head;
62
+ try {
63
+ const fd = fs.openSync(file, 'r');
64
+ try {
65
+ const buf = Buffer.alloc(256);
66
+ const n = fs.readSync(fd, buf, 0, buf.length, 0);
67
+ head = buf.toString('utf8', 0, n);
68
+ } finally {
69
+ fs.closeSync(fd);
70
+ }
71
+ } catch (_) {
72
+ return false;
73
+ }
74
+ if (!head.startsWith('#!')) return true;
75
+ return /\bnode\b/.test(head.split(/\r?\n/, 1)[0]);
76
+ }
77
+
78
+ function findPi({
79
+ bridgeBinDir,
80
+ locate = locateOnPath,
81
+ platform = process.platform,
82
+ execPath = process.execPath,
83
+ } = {}) {
84
+ // 1. Standard npm global: pii and pi share the same node_modules dir
85
+ // (<node_modules>/pi-bridge/bin -> <node_modules>).
86
+ if (bridgeBinDir) {
87
+ const cli = cliFromNodeModules(path.resolve(bridgeBinDir, '..', '..'));
88
+ if (cli) return { cli, nodeExe: execPath };
89
+ }
90
+
91
+ // 2. Resolve `pi` from PATH. Covers npm globals when pi-bridge itself is
92
+ // linked from a local checkout, the pi.dev installer (bundled Node plus a
93
+ // bare `bin/pi` symlink straight to cli.js, often outside any npm prefix)
94
+ // and dev clones.
95
+ const exe = platform === 'win32' ? '.exe' : '';
96
+ for (const cmd of locate('pi', platform)) {
97
+ const dir = path.dirname(cmd);
98
+ const localNode = path.join(dir, 'node' + exe);
99
+ const nodeExe = fs.existsSync(localNode) ? localNode : execPath;
100
+
101
+ // 2a. npm prefix layouts next to the launcher:
102
+ // Windows: <prefix>/pi(.cmd) + <prefix>/node_modules/...
103
+ // POSIX: <prefix>/bin/pi + <prefix>/lib/node_modules/...
104
+ const npmCli =
105
+ cliFromNodeModules(path.join(dir, 'node_modules')) ||
106
+ cliFromNodeModules(path.join(dir, '..', 'lib', 'node_modules'));
107
+ if (npmCli) return { cli: npmCli, nodeExe };
108
+
109
+ // 2b. Follow the launcher to its real JS entrypoint.
110
+ let real;
111
+ try { real = fs.realpathSync(cmd); } catch (_) { real = cmd; }
112
+
113
+ let cli = null;
114
+ if (/\.[cm]?js$/i.test(real) && fs.existsSync(real)) {
115
+ cli = real; // symlink/shim -> cli.js
116
+ } else if (fs.existsSync(path.join(dir, 'pi.js'))) {
117
+ cli = path.join(dir, 'pi.js'); // bundled wrapper + sibling
118
+ } else if (!/\.(cmd|ps1|bat|exe)$/i.test(real) && looksLikeNodeScript(real)) {
119
+ cli = real; // shebang JS with no extension
120
+ }
121
+ if (cli) return { cli, nodeExe };
122
+ }
123
+
124
+ return null;
125
+ }
126
+
127
+ module.exports = { findPi, cliFromPackage, looksLikeNodeScript, locateOnPath };
@@ -235,6 +235,9 @@ function patchFs(fakeRoot, port, token) {
235
235
 
236
236
  fs.accessSync = function patchedAccessSync(p, mode) {
237
237
  const s = abs(p);
238
+ // Same Windows bash fake as existsSync: newer pi probes the shell with
239
+ // accessSync, which would otherwise hit the local disk and fail.
240
+ if (process.platform === 'win32' && isBashPath(s)) return;
238
241
  if (!s || !isFakePath(s, fakeRoot)) return orig.accessSync(p, mode);
239
242
  try {
240
243
  const exists = httpGet(port, token, '/exists?path=' + encodeURIComponent(rpath(s))).trim() === '1';
@@ -406,6 +409,7 @@ function patchFs(fakeRoot, port, token) {
406
409
 
407
410
  fs.promises.access = async function patchedAccess(p, mode) {
408
411
  const s = abs(p);
412
+ if (process.platform === 'win32' && isBashPath(s)) return;
409
413
  if (!s || !isFakePath(s, fakeRoot)) return origP.access(p, mode);
410
414
  try {
411
415
  const exists = (await httpGetAsync(port, token, '/exists?path=' + encodeURIComponent(rpath(s)))).trim() === '1';