@remcp/remcp 0.2.228 → 0.2.230

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.228",
3
+ "version": "0.2.230",
4
4
  "description": "ReMCP device client: pair a computer with ReMCP and run the outbound-only agent that hosts the local MCP runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@
18
18
  "README.md"
19
19
  ],
20
20
  "scripts": {
21
- "check": "node --check bin/remcp.mjs && node --check src/agent-deploy-reconnect.mjs && node --check src/agent-network-probe-client.mjs && node --check src/agent-relay-connection.mjs && node --check src/agent-responder.mjs && node --check src/agent-runtime-capabilities.mjs && node --check src/agent-update-monitor.mjs && node --check src/agent-update.mjs && node --check src/agent-utilities.mjs && node --check src/agent.mjs && node --check src/cli.mjs && node --check src/cli/config.mjs && node --check src/cli/connect.mjs && node --check src/cli/doctor.mjs && node --check src/cli/env.mjs && node --check src/cli/installation-audit.mjs && node --check src/cli/mac-native-temperature.mjs && node --check src/cli/npm-global.mjs && node --check src/cli/pairing-qr.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service-macos.mjs && node --check src/cli/service-paths.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/supervised-agent-lifecycle.mjs && node --check src/cli/update.mjs && node --check src/cli/windows-owned-job-source.mjs && node --check src/cli/windows-owned-job.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-update-lock.mjs && node --check src/cli/windows-user-supervisor.mjs && node --check src/fetch-update-manifest.mjs && node --check src/fs-access.mjs && node --check src/npm.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
21
+ "check": "node --check bin/remcp.mjs && node --check src/agent-deploy-reconnect.mjs && node --check src/agent-network-probe-client.mjs && node --check src/agent-relay-connection.mjs && node --check src/agent-responder.mjs && node --check src/agent-runtime-capabilities.mjs && node --check src/agent-update-monitor.mjs && node --check src/agent-update.mjs && node --check src/agent-utilities.mjs && node --check src/agent.mjs && node --check src/cli.mjs && node --check src/cli/config.mjs && node --check src/cli/connect.mjs && node --check src/cli/doctor.mjs && node --check src/cli/env.mjs && node --check src/cli/installation-audit.mjs && node --check src/cli/mac-native-temperature.mjs && node --check src/cli/npm-global.mjs && node --check src/cli/pairing-qr.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service-macos.mjs && node --check src/cli/service-paths.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/supervised-agent-lifecycle.mjs && node --check src/cli/update.mjs && node --check src/cli/windows-cli-shim.mjs && node --check src/cli/windows-owned-job-source.mjs && node --check src/cli/windows-owned-job.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-update-lock.mjs && node --check src/cli/windows-user-supervisor.mjs && node --check src/fetch-update-manifest.mjs && node --check src/fs-access.mjs && node --check src/npm.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
22
22
  "test": "node --test test/*.test.mjs"
23
23
  },
24
24
  "dependencies": {
@@ -43,7 +43,9 @@ export function createAgentRelayConnection({
43
43
  reconnectTimer = null;
44
44
  connect();
45
45
  }, safeDelay);
46
- reconnectTimer.unref?.();
46
+ // A failed handshake may leave no other referenced handles. Keep the reconnect
47
+ // timer referenced, or Node exits 0 and a supervisor can restart it forever.
48
+ // clearReconnect() releases the timer on intentional shutdown.
47
49
  }
48
50
 
49
51
  function scheduleBackoffReconnect() {
@@ -7,6 +7,7 @@ import { home, npm } from './env.mjs';
7
7
  import { buildVettedMacTemperatureAddon } from './mac-native-temperature.mjs';
8
8
  import { output, run } from './shell.mjs';
9
9
  import { withWindowsUpdateLock } from './windows-update-lock.mjs';
10
+ import { assertWindowsNpmPowerShellShimOwned, preferWindowsCmdShim } from './windows-cli-shim.mjs';
10
11
 
11
12
  export function servicePlatform() {
12
13
  return process.env.NODE_ENV === 'test' && process.env.REMCP_TEST_PLATFORM ? process.env.REMCP_TEST_PLATFORM : process.platform;
@@ -55,7 +56,14 @@ function npmGlobalInstallArgs(specs, { preferOnline = false } = {}) {
55
56
 
56
57
  export function npmGlobalInstall(...specs) {
57
58
  const prefix = assertSafeReMcpNpmPrefix(globalPrefix()); // Reject an unsafe prefix before npm writes anything.
58
- const install = () => run(npm.command, [...npm.args, ...npmGlobalInstallArgs(specs)]);
59
+ const install = () => {
60
+ const updatingClient = servicePlatform() === 'win32' && isManagedWindowsRuntimePrefix(prefix)
61
+ && specs.some(spec => /^@remcp\/remcp@/.test(spec));
62
+ // A custom remcp.ps1 must be refused BEFORE npm overwrites its contents.
63
+ if (updatingClient) assertWindowsNpmPowerShellShimOwned(prefix);
64
+ run(npm.command, [...npm.args, ...npmGlobalInstallArgs(specs)]);
65
+ if (updatingClient) preferWindowsCmdShim(prefix);
66
+ };
59
67
  if (servicePlatform() === 'win32') withWindowsUpdateLock(prefix, install);
60
68
  else install();
61
69
  if (servicePlatform() === 'darwin' && specs.some(spec => /^@remcp\/runtime@[0-9]/.test(spec))) {
@@ -184,6 +192,9 @@ function transactionalWindowsGlobalInstall({ resolved, prefix, packageNames, spe
184
192
  return;
185
193
  }
186
194
 
195
+ // The transaction deletes its previous shim backup after success. Reject a
196
+ // user-created remcp.ps1 before touching npm or moving any existing files.
197
+ if (packageNames.includes(PACKAGE_NAME)) assertWindowsNpmPowerShellShimOwned(prefix);
187
198
  const backupRoot = path.join(prefix, `.remcp-update-backup-${process.pid}-${Date.now()}`);
188
199
  const moved = [];
189
200
  const moveAside = (source, label) => {
@@ -221,6 +232,7 @@ function transactionalWindowsGlobalInstall({ resolved, prefix, packageNames, spe
221
232
  for (const shim of ['remcp', 'remcp.cmd', 'remcp.ps1']) moveAside(path.join(prefix, shim), `bin-${shim}`);
222
233
  }
223
234
  run(resolved.command, [...resolved.args, ...npmGlobalInstallArgs(specs, { preferOnline })]);
235
+ if (uniqueNames.includes(PACKAGE_NAME)) preferWindowsCmdShim(prefix);
224
236
  } catch (error) {
225
237
  restore(error);
226
238
  throw error;
@@ -0,0 +1,73 @@
1
+ // ReMCP's private Windows npm prefix is called from both cmd.exe and PowerShell.
2
+ // npm generates both remcp.cmd and remcp.ps1, but PowerShell prefers the .ps1
3
+ // even when ExecutionPolicy=Restricted. That makes a successful installation
4
+ // appear broken. Keep the batch shim, and remove only the recognizable npm shim.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+
8
+ // Exact npm cmd-shim template; partial markers cannot establish ownership.
9
+ export const WINDOWS_NPM_POWERSHELL_SHIM = String.raw`#!/usr/bin/env pwsh
10
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
11
+
12
+ $exe=""
13
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
14
+ # Fix case when both the Windows and Linux builds of Node
15
+ # are installed in the same directory
16
+ $exe=".exe"
17
+ }
18
+ $ret=0
19
+ if (Test-Path "$basedir/node$exe") {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "$basedir/node$exe" "$basedir/node_modules/@remcp/remcp/bin/remcp.mjs" $args
23
+ } else {
24
+ & "$basedir/node$exe" "$basedir/node_modules/@remcp/remcp/bin/remcp.mjs" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ } else {
28
+ # Support pipeline input
29
+ if ($MyInvocation.ExpectingInput) {
30
+ $input | & "node$exe" "$basedir/node_modules/@remcp/remcp/bin/remcp.mjs" $args
31
+ } else {
32
+ & "node$exe" "$basedir/node_modules/@remcp/remcp/bin/remcp.mjs" $args
33
+ }
34
+ $ret=$LASTEXITCODE
35
+ }
36
+ exit $ret
37
+ `;
38
+
39
+ function normalizedShim(text) {
40
+ return text.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n').replace(/\n+$/, '');
41
+ }
42
+
43
+ export function assertWindowsNpmPowerShellShimOwned(prefix) {
44
+ const powershell = path.join(prefix, 'remcp.ps1');
45
+ let metadata;
46
+ try { metadata = fs.lstatSync(powershell); }
47
+ catch (error) { if (error?.code === 'ENOENT') return false; throw error; }
48
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
49
+ throw new Error('Refusing to replace an unknown ReMCP PowerShell launcher');
50
+ }
51
+ const contents = fs.readFileSync(powershell, 'utf8');
52
+ if (contents.length > 16000 || normalizedShim(contents) !== normalizedShim(WINDOWS_NPM_POWERSHELL_SHIM)) {
53
+ throw new Error('Refusing to remove a non-npm ReMCP PowerShell launcher');
54
+ }
55
+ return true;
56
+ }
57
+
58
+ export function preferWindowsCmdShim(prefix) {
59
+ if (!assertWindowsNpmPowerShellShimOwned(prefix)) return false;
60
+ const batch = path.join(prefix, 'remcp.cmd');
61
+ const powershell = path.join(prefix, 'remcp.ps1');
62
+ let batchStat;
63
+ try { batchStat = fs.lstatSync(batch); }
64
+ catch (error) {
65
+ if (error?.code !== 'ENOENT') throw error;
66
+ throw new Error('Cannot remove the PowerShell launcher without a ReMCP .cmd launcher');
67
+ }
68
+ if (!batchStat.isFile() || batchStat.isSymbolicLink()) {
69
+ throw new Error('Cannot remove the PowerShell launcher without a trusted ReMCP .cmd launcher');
70
+ }
71
+ fs.unlinkSync(powershell);
72
+ return true;
73
+ }