pi-bridge 0.2.8 → 0.3.0
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/README.md +2 -3
- package/package.json +1 -1
- package/src/local/child-process-patch.js +43 -12
- package/src/local/fake-dir.js +6 -18
- package/src/local/fs-patch.js +7 -2
- package/src/local/preload.js +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ pii (bin/pii.js)
|
|
|
52
52
|
│ → prints PORT:TOKEN:PID to stdout
|
|
53
53
|
├─ SSH port forward localhost:PORT
|
|
54
54
|
├─ create fake local dir:
|
|
55
|
-
│
|
|
55
|
+
│ /tmp/pi-bridge/root/projects/my-app
|
|
56
56
|
├─ process.chdir(fakeLocalCwd)
|
|
57
57
|
├─ strip --ssh from process.argv
|
|
58
58
|
├─ patch fs.* (sync + promises) remote/index.js
|
|
@@ -67,7 +67,6 @@ pi loads — unaware of bridge restricts to remoteCwd
|
|
|
67
67
|
→ git branch in footer ✓
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
The fake local directory lives under `<home>/.pi-bridge/<remote>/` (not the OS temp directory) so it survives reboots and pi can always resume sessions correctly. On Windows this is `%USERPROFILE%\.pi-bridge\`; on macOS/Linux it is `~/.pi-bridge/`.
|
|
71
70
|
|
|
72
71
|
## What Gets Patched
|
|
73
72
|
|
|
@@ -121,7 +120,7 @@ Host myserver
|
|
|
121
120
|
```
|
|
122
121
|
|
|
123
122
|
**Files show as empty / ENOENT**
|
|
124
|
-
The fake local directory (
|
|
123
|
+
The fake local directory (`/tmp/pi-bridge/…`) is intentionally empty — all reads go to the remote. If you see ENOENT, the bridge may not have started correctly. Check that Node.js is available on the remote (`node --version`).
|
|
125
124
|
|
|
126
125
|
**Branch name not updating**
|
|
127
126
|
The footer polls `.git` every second. Switching branches from outside a pi session is reflected within ~1s. If the branch is stuck, restart `pii`.
|
package/package.json
CHANGED
|
@@ -5,19 +5,18 @@ const path = require('path');
|
|
|
5
5
|
const { isFakePath, toRemotePath } = require('./path-mapper');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Patch child_process
|
|
8
|
+
* Patch child_process spawn/spawnSync/execSync/execFileSync so that commands
|
|
9
9
|
* whose cwd lands inside fakeRoot are transparently redirected to run on
|
|
10
10
|
* the remote machine via SSH.
|
|
11
11
|
*
|
|
12
|
-
* Pi's bash tool uses spawn/spawnSync exclusively, so this covers all shell
|
|
13
|
-
* command execution (ls, cat, grep, npm run, etc.).
|
|
14
|
-
*
|
|
15
12
|
* @param {string} fakeRoot - Local fake root directory (real path, no symlinks)
|
|
16
13
|
* @param {string} remote - SSH target, e.g. 'user@host'
|
|
17
14
|
*/
|
|
18
15
|
function patchChildProcess(fakeRoot, remote) {
|
|
19
|
-
const origSpawn
|
|
20
|
-
const origSpawnSync
|
|
16
|
+
const origSpawn = cp.spawn.bind(cp);
|
|
17
|
+
const origSpawnSync = cp.spawnSync.bind(cp);
|
|
18
|
+
const origExecSync = cp.execSync.bind(cp);
|
|
19
|
+
const origExecFileSync = cp.execFileSync.bind(cp);
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Resolve the effective cwd from spawn options.
|
|
@@ -44,6 +43,7 @@ function patchChildProcess(fakeRoot, remote) {
|
|
|
44
43
|
* - node / node.exe: pi's internal tooling (session export, compaction, …)
|
|
45
44
|
* spawns Node with large payloads as args; redirecting those over SSH would
|
|
46
45
|
* produce an ENAMETOOLONG error on Windows and is never correct anyway.
|
|
46
|
+
* - pi / pii: need local internet access for the Claude API and auth.
|
|
47
47
|
*/
|
|
48
48
|
function isLocalOnly(file) {
|
|
49
49
|
const name = normalizeCmd(file).toLowerCase();
|
|
@@ -51,15 +51,14 @@ function patchChildProcess(fakeRoot, remote) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* Build
|
|
55
|
-
*
|
|
54
|
+
* Build SSH args for a file + args invocation:
|
|
55
|
+
* cd to remote cwd, then run the command with shell-quoted arguments.
|
|
56
56
|
*/
|
|
57
57
|
function buildSshArgs(file, args, opts) {
|
|
58
58
|
file = normalizeCmd(file);
|
|
59
|
-
const fakeCwd
|
|
60
|
-
const remoteCwd
|
|
59
|
+
const fakeCwd = effectiveCwd(opts);
|
|
60
|
+
const remoteCwd = toRemotePath(fakeCwd, fakeRoot);
|
|
61
61
|
|
|
62
|
-
// Reconstruct the command string with shell-safe quoting
|
|
63
62
|
const argStr = (args || [])
|
|
64
63
|
.map(function(a) { return "'" + String(a).replace(/'/g, "'\\''") + "'"; })
|
|
65
64
|
.join(' ');
|
|
@@ -68,10 +67,19 @@ function patchChildProcess(fakeRoot, remote) {
|
|
|
68
67
|
return ['-o', 'BatchMode=yes', remote, remoteCmd];
|
|
69
68
|
}
|
|
70
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Build SSH args for a raw shell command string (used by execSync).
|
|
72
|
+
*/
|
|
73
|
+
function buildSshArgsForShell(command, opts) {
|
|
74
|
+
const fakeCwd = effectiveCwd(opts);
|
|
75
|
+
const remoteCwd = toRemotePath(fakeCwd, fakeRoot);
|
|
76
|
+
const remoteCmd = 'cd ' + JSON.stringify(remoteCwd) + ' && ' + command;
|
|
77
|
+
return ['-o', 'BatchMode=yes', remote, remoteCmd];
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
// ── spawn ────────────────────────────────────────────────────────────────────
|
|
72
81
|
|
|
73
82
|
cp.spawn = function patchedSpawn(file, args, opts) {
|
|
74
|
-
// Normalise overloaded signature: spawn(file) / spawn(file, opts) / spawn(file, args, opts)
|
|
75
83
|
if (!Array.isArray(args)) { opts = args; args = []; }
|
|
76
84
|
opts = opts || {};
|
|
77
85
|
|
|
@@ -92,6 +100,29 @@ function patchChildProcess(fakeRoot, remote) {
|
|
|
92
100
|
|
|
93
101
|
return origSpawnSync('ssh', buildSshArgs(file, args, opts), Object.assign({}, opts, { cwd: undefined }));
|
|
94
102
|
};
|
|
103
|
+
|
|
104
|
+
// ── execSync ─────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
cp.execSync = function patchedExecSync(command, opts) {
|
|
107
|
+
opts = opts || {};
|
|
108
|
+
|
|
109
|
+
const cwd = effectiveCwd(opts);
|
|
110
|
+
if (!isFakePath(cwd, fakeRoot)) return origExecSync(command, opts);
|
|
111
|
+
|
|
112
|
+
return origExecFileSync('ssh', buildSshArgsForShell(command, opts), Object.assign({}, opts, { cwd: undefined }));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// ── execFileSync ─────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
cp.execFileSync = function patchedExecFileSync(file, args, opts) {
|
|
118
|
+
if (!Array.isArray(args)) { opts = args; args = []; }
|
|
119
|
+
opts = opts || {};
|
|
120
|
+
|
|
121
|
+
const cwd = effectiveCwd(opts);
|
|
122
|
+
if (!isFakePath(cwd, fakeRoot) || isLocalOnly(file)) return origExecFileSync(file, args, opts);
|
|
123
|
+
|
|
124
|
+
return origExecFileSync('ssh', buildSshArgs(file, args, opts), Object.assign({}, opts, { cwd: undefined }));
|
|
125
|
+
};
|
|
95
126
|
}
|
|
96
127
|
|
|
97
128
|
module.exports = { patchChildProcess };
|
package/src/local/fake-dir.js
CHANGED
|
@@ -8,33 +8,21 @@ const path = require('path');
|
|
|
8
8
|
// filesystem rather than being redirected through the SSH bridge.
|
|
9
9
|
const origRmSync = fs.rmSync.bind(fs);
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* Sanitise an SSH target string (user@host or alias) into a safe dir name.
|
|
13
|
-
*/
|
|
14
|
-
function sanitizeRemote(remote) {
|
|
15
|
-
return remote.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
11
|
/**
|
|
19
12
|
* Create the fake local directory skeleton for the given remote path.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* directory so it survives reboots — this prevents pi's session-cwd check
|
|
23
|
-
* from failing when it tries to resume a previous session whose cwd points
|
|
24
|
-
* at a directory that was cleaned up between runs.
|
|
13
|
+
* The structure mirrors the remote path so that path-mapper can convert
|
|
14
|
+
* between fake local paths and remote paths deterministically.
|
|
25
15
|
*
|
|
26
16
|
* Example:
|
|
27
|
-
* remote = 'myserver'
|
|
28
17
|
* remoteCwd = '/root/project-x'
|
|
29
|
-
* fakeRoot = '
|
|
30
|
-
* fakeLocalCwd = '
|
|
18
|
+
* fakeRoot = '/tmp/pi-bridge'
|
|
19
|
+
* fakeLocalCwd = '/tmp/pi-bridge/root/project-x'
|
|
31
20
|
*
|
|
32
|
-
* @param {string} remote - SSH target, e.g. 'user@host' or alias
|
|
33
21
|
* @param {string} remoteCwd - Absolute remote path, e.g. '/root/project-x'
|
|
34
22
|
* @returns {{ fakeRoot: string, fakeLocalCwd: string }}
|
|
35
23
|
*/
|
|
36
|
-
function createFakeDir(
|
|
37
|
-
const fakeRootRaw = path.join(os.
|
|
24
|
+
function createFakeDir(remoteCwd) {
|
|
25
|
+
const fakeRootRaw = path.join(os.tmpdir(), 'pi-bridge');
|
|
38
26
|
|
|
39
27
|
// Build the local mirror path from the remote path segments
|
|
40
28
|
const segments = remoteCwd.split('/').filter(Boolean);
|
package/src/local/fs-patch.js
CHANGED
|
@@ -192,10 +192,15 @@ function patchFs(fakeRoot, port, token) {
|
|
|
192
192
|
if (process.platform === 'win32' && isBashPath(s)) return true;
|
|
193
193
|
if (!s || !isFakePath(s, fakeRoot)) return orig.existsSync(p);
|
|
194
194
|
try {
|
|
195
|
-
|
|
195
|
+
const remoteExists = httpGet(port, token, '/exists?path=' + encodeURIComponent(rpath(s))).trim() === '1';
|
|
196
|
+
// Also check locally: the fake dir root always exists on disk (pi-bridge
|
|
197
|
+
// creates it in preload). This lets pi's session-cwd check pass even when
|
|
198
|
+
// Node.js 24's ESM existsSync bypasses our patch and the remote check
|
|
199
|
+
// returns an unexpected result for the project root directory.
|
|
200
|
+
return remoteExists || orig.existsSync(p);
|
|
196
201
|
} catch (e) {
|
|
197
202
|
if (e.message && (e.message.includes('403') || e.message.includes('ENOENT'))) return false;
|
|
198
|
-
|
|
203
|
+
return orig.existsSync(p);
|
|
199
204
|
}
|
|
200
205
|
};
|
|
201
206
|
|
package/src/local/preload.js
CHANGED
|
@@ -57,7 +57,7 @@ try {
|
|
|
57
57
|
const { port, token, sessionId, remotePid } = config;
|
|
58
58
|
|
|
59
59
|
// --- Step 3: Create fake local directory ---
|
|
60
|
-
const { fakeRoot, fakeLocalCwd } = createFakeDir(
|
|
60
|
+
const { fakeRoot, fakeLocalCwd } = createFakeDir(remoteCwd);
|
|
61
61
|
|
|
62
62
|
// --- Step 4: Change working directory to fake local cwd ---
|
|
63
63
|
process.chdir(fakeLocalCwd);
|