@remcp/remcp 0.2.212 → 0.2.213

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.212",
3
+ "version": "0.2.213",
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/update.mjs && node --check src/cli/windows-supervisor.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/update.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-task-restart.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": {
@@ -9,14 +9,13 @@ import { officialOrigin } from './env.mjs';
9
9
  import { sleep } from './shell.mjs';
10
10
  import { printPairingQr } from './pairing-qr.mjs';
11
11
 
12
- // Keep the server-provided approval URL as one argument all the way to the OS.
13
- // Windows Explorer can treat a URL as a shell path and open Documents instead.
14
- // FileProtocolHandler dispatches to the default browser without a cmd/PowerShell shell,
15
- // where a URL query containing '&' could otherwise be interpreted as a command.
12
+ // Keep the server-provided approval URL as data all the way to the OS. In particular, do not route it
13
+ // through `cmd /c start` on Windows: cmd reparses metacharacters such as `&`, so a custom pairing
14
+ // server could otherwise turn a verification URL into a local shell command.
16
15
  export function browserLaunchCommand(url, platform = process.platform) {
17
16
  const target = String(url);
18
17
  if (platform === 'darwin') return { command: 'open', args: [target] };
19
- if (platform === 'win32') return { command: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', target] };
18
+ if (platform === 'win32') return { command: 'explorer.exe', args: [target] };
20
19
  return { command: 'xdg-open', args: [target] };
21
20
  }
22
21
 
@@ -72,6 +72,19 @@ function inferServiceIdentity() {
72
72
 
73
73
  export function canonicalServiceCliPath(config) {
74
74
  const configured = String(config?.serviceCliPath || '').trim();
75
+ if (servicePlatform() === 'win32' && configured) {
76
+ // Saved serviceCliPath is the resolved npm package entry (remcp.mjs), not
77
+ // the Windows executable shim. CMD `call remcp.mjs` opens the Windows
78
+ // "Choose an app" dialog instead of starting Node, and dirname(entry) is
79
+ // an invalid NPM_CONFIG_PREFIX. Recover the sibling npm global shim.
80
+ const suffix = '/node_modules/@remcp/remcp/bin/remcp.mjs';
81
+ if (configured.replaceAll('\\', '/').toLowerCase().endsWith(suffix)) {
82
+ const prefix = configured.slice(0, -suffix.length);
83
+ const shim = path.join(prefix, 'remcp.cmd');
84
+ if (!fs.existsSync(shim)) throw new Error('Windows ReMCP npm shim is missing; refusing a .mjs Scheduled Task');
85
+ return shim;
86
+ }
87
+ }
75
88
  if (configured && fs.existsSync(configured)) return configured;
76
89
  const inferred = inferServiceIdentity().cliPath;
77
90
  if (inferred && fs.existsSync(inferred)) return inferred;
@@ -19,6 +19,7 @@ import { configDir, home, linuxServiceFile, linuxServiceLauncherFile, macService
19
19
  import { run } from './shell.mjs';
20
20
  import { windowsServiceLauncherText, windowsTaskReliabilityCommand, windowsTaskAbsenceCommand, windowsBatchUnsafeProfile, windowsStartupScriptText, windowsStartupScriptInstalled } from './windows-supervisor.mjs';
21
21
  import { ensureWindowsSupervisorKey, windowsSupervisorCommand } from './windows-user-supervisor.mjs';
22
+ import { scheduleWindowsTaskRestart } from './windows-task-restart.mjs';
22
23
 
23
24
  export function quoteSystemd(value) {
24
25
  return `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
@@ -147,17 +148,6 @@ export function installWindowsService(cliPath = globalCliPath(), { nodePath = pr
147
148
  installWindowsUserStartup(cliPath, nodePath);
148
149
  return;
149
150
  }
150
- // Task Scheduler runs an /IT .cmd inside a *visible* terminal window.
151
- // For fresh installations prefer our existing hidden WSH + Node supervisor.
152
- // Keep already-registered scheduled tasks intact until an explicit migration;
153
- // never start two supervisors for the same machine.
154
- const previousTask = spawnSync('schtasks.exe', ['/Query', '/TN', windowsTaskName],
155
- { stdio:'ignore', windowsHide:true, timeout:10000 });
156
- if (previousTask.status !== 0) {
157
- requireWindowsScheduledTaskAbsent();
158
- installWindowsUserStartup(cliPath, nodePath);
159
- return;
160
- }
161
151
  const launcherFile = writeWindowsServiceLauncher(cliPath, nodePath);
162
152
  const command = `"${launcherFile}"`;
163
153
  // Current-user interactive tasks need /IT. /RL LIMITED avoids requiring elevation.
@@ -347,7 +337,7 @@ export function deferredLinuxAgentRestartArgs(pid) {
347
337
  'systemctl', '--user', 'restart', 'remcp-agent.service'];
348
338
  }
349
339
 
350
- export function restartPersistentServiceIfInstalled(config, { deferLinux = false } = {}) {
340
+ export function restartPersistentServiceIfInstalled(config, { deferLinux = false, deferWindows = false } = {}) {
351
341
  if (!persistentServiceExpected(config)) return null;
352
342
  const platform = servicePlatform();
353
343
  if (platform === 'linux' && fs.existsSync(linuxServiceFile)) {
@@ -382,6 +372,7 @@ export function restartPersistentServiceIfInstalled(config, { deferLinux = false
382
372
  }
383
373
  const result = spawnSync('schtasks.exe', ['/Query', '/TN', windowsTaskName], { stdio: 'ignore' });
384
374
  if (result.status === 0) {
375
+ if (deferWindows) return scheduleWindowsTaskRestart(canonicalServiceNodePath(config));
385
376
  // /Run does not restart a task that is already running; stop the previous
386
377
  // process first so a rotated credential is loaded by the replacement.
387
378
  spawnSync('schtasks.exe', ['/End', '/TN', windowsTaskName], { stdio: 'ignore' });
@@ -24,6 +24,12 @@ export function windowsBatchUnsafeProfile(...paths) {
24
24
  }
25
25
 
26
26
  export function windowsServiceLauncherText(cliPath, nodePath, { localAppData = process.env.LOCALAPPDATA } = {}) {
27
+ // CMD's `call` executes batch shims, not the underlying .mjs source. A saved
28
+ // package entry is valid for `node.exe remcp.mjs`, but invalid as a Task
29
+ // Scheduler command and must never be written into a persistent launcher.
30
+ if (/\.mjs$/i.test(String(cliPath))) {
31
+ throw new Error('Windows Scheduled Task requires the ReMCP .cmd shim, not a .mjs module');
32
+ }
27
33
  // Tests can simulate win32 from a POSIX runner with injected temporary paths.
28
34
  // Native Windows paths always use win32 parsing; POSIX-shaped fixtures keep their roots.
29
35
  const dirname = file => /^[A-Za-z]:[\\/]/.test(file) || file.includes('\\')
@@ -0,0 +1,53 @@
1
+ // ReMCP's confirmed remote God Mode change must acknowledge its command BEFORE
2
+ // killing the agent's own Task Scheduler process tree. Launch a fixed-purpose
3
+ // helper outside the CLI process; never interpolate a user-provided task or shell.
4
+ import { spawn, spawnSync } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import process from 'node:process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { windowsTaskName } from './env.mjs';
10
+
11
+ const helperPath = fileURLToPath(import.meta.url);
12
+ const options = { windowsHide:true, encoding:'utf8', timeout:15000, maxBuffer:32768 };
13
+
14
+ export function scheduleWindowsTaskRestart(nodePath = process.execPath) {
15
+ if (!path.isAbsolute(nodePath) || !fs.existsSync(nodePath)) {
16
+ throw new Error('Windows deferred restart requires the existing Node interpreter');
17
+ }
18
+ const child = spawn(nodePath, [helperPath, '--relaunch'], {
19
+ detached:true, windowsHide:true, stdio:'ignore',
20
+ });
21
+ if (!Number.isSafeInteger(child.pid) || child.pid <= 0) {
22
+ throw new Error('Could not schedule independent Windows ReMCP restart');
23
+ }
24
+ child.unref();
25
+ return 'ReMCP Agent (restart scheduled)';
26
+ }
27
+
28
+ export async function executeWindowsTaskRestart({
29
+ pause = ms => new Promise(resolve => setTimeout(resolve,ms)),
30
+ invoke = spawnSync,
31
+ } = {}) {
32
+ await pause(2500);
33
+ // Do not run a second agent if Windows refused to stop the owned task.
34
+ const stopped = invoke('schtasks.exe', ['/End','/TN',windowsTaskName], options);
35
+ if (stopped.error || stopped.status !== 0) {
36
+ throw new Error('Windows ReMCP task could not be stopped safely');
37
+ }
38
+ const started = invoke('schtasks.exe', ['/Run','/TN',windowsTaskName], options);
39
+ if (started.error || started.status !== 0) {
40
+ throw new Error('Windows ReMCP task could not be started after stop');
41
+ }
42
+ }
43
+
44
+ if (process.argv[1] && path.resolve(process.argv[1]) === helperPath) {
45
+ if (process.argv[2] !== '--relaunch' || process.argv.length !== 3 || process.platform !== 'win32') {
46
+ process.exitCode = 2;
47
+ } else {
48
+ executeWindowsTaskRestart().catch(error => {
49
+ console.error(error.message);
50
+ process.exitCode = 1;
51
+ });
52
+ }
53
+ }
package/src/cli.mjs CHANGED
@@ -271,11 +271,12 @@ export async function main(argv = process.argv.slice(2)) {
271
271
  if (!['on', 'off', 'enable', 'disable'].includes(action)) throw new Error('Usage: remcp godmode [status|on|off]');
272
272
  const enabled = action === 'on' || action === 'enable';
273
273
  writeJsonFile(runtimeConfigFile, { ...readJsonFile(runtimeConfigFile), unrestricted: enabled });
274
- // The authenticated Workspace supplies this explicit flag to allow the
275
- // remote command to finish before its agent schedules a Linux restart.
276
- // Local CLI and normal update operations retain synchronous semantics.
274
+ // The authenticated Workspace supplies this explicit flag so the remote
275
+ // command can acknowledge a policy change before Linux/Windows kills its
276
+ // own agent session. Local CLI operations retain synchronous semantics.
277
277
  const restarted = restartPersistentServiceIfInstalled(loadConfig(false),
278
- { deferLinux: flags['defer-restart'] === true });
278
+ { deferLinux: flags['defer-restart'] === true,
279
+ deferWindows: flags['defer-restart'] === true });
279
280
  if (enabled) {
280
281
  console.error('Unrestricted mode is ON for this computer: every path and every command is allowed, including sudo. Anything the model is asked to do -- and anything a prompt injection asks it to do -- can now change this machine. Turn it off with `remcp godmode off` when you are done.');
281
282
  } else {