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 +3 -59
- package/package.json +1 -1
- package/src/local/find-pi.js +127 -0
- package/src/local/fs-patch.js +4 -0
package/bin/pii.js
CHANGED
|
@@ -1,77 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const { spawnSync
|
|
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
|
@@ -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 };
|
package/src/local/fs-patch.js
CHANGED
|
@@ -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';
|