@remcp/remcp 0.2.213 → 0.2.214

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.213",
3
+ "version": "0.2.214",
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",
package/src/cli/env.mjs CHANGED
@@ -17,6 +17,7 @@ export const macServiceFile = path.join(home, 'Library', 'LaunchAgents', `${macS
17
17
  export const macLogFile = path.join(home, 'Library', 'Logs', 'remcp-agent.log');
18
18
  export const windowsTaskName = 'ReMCP Agent';
19
19
  export const windowsServiceLauncherFile = path.join(configDir, 'remcp-agent.cmd');
20
+ export const windowsTaskScriptFile = path.join(configDir, 'remcp-agent.vbs');
20
21
  // Task Scheduler may refuse to create a task for a standard Windows account.
21
22
  // This per-user Startup folder runs without administrator privileges.
22
23
  export const windowsStartupScriptFile = path.join(
@@ -15,9 +15,9 @@ export { macLaunchDomain, ensureMacCliCommand, installMacService } from './servi
15
15
  import { PACKAGE_NAME, VERSION } from '../version.mjs';
16
16
 
17
17
  import { saveConfig } from './config.mjs';
18
- import { configDir, home, linuxServiceFile, linuxServiceLauncherFile, macServiceFile, macServiceLabel, windowsServiceLauncherFile, windowsStartupScriptFile, windowsTaskName } from './env.mjs';
18
+ import { configDir, home, linuxServiceFile, linuxServiceLauncherFile, macServiceFile, macServiceLabel, windowsServiceLauncherFile, windowsTaskScriptFile, windowsStartupScriptFile, windowsTaskName } from './env.mjs';
19
19
  import { run } from './shell.mjs';
20
- import { windowsServiceLauncherText, windowsTaskReliabilityCommand, windowsTaskAbsenceCommand, windowsBatchUnsafeProfile, windowsStartupScriptText, windowsStartupScriptInstalled } from './windows-supervisor.mjs';
20
+ import { windowsServiceLauncherText, windowsTaskReliabilityCommand, windowsTaskAbsenceCommand, windowsBatchUnsafeProfile, windowsTaskScriptText, windowsTaskScriptInstalled, windowsStartupScriptText, windowsStartupScriptInstalled } from './windows-supervisor.mjs';
21
21
  import { ensureWindowsSupervisorKey, windowsSupervisorCommand } from './windows-user-supervisor.mjs';
22
22
  import { scheduleWindowsTaskRestart } from './windows-task-restart.mjs';
23
23
 
@@ -79,6 +79,20 @@ export function writeWindowsServiceLauncher(cliPath = globalCliPath(), nodePath
79
79
  return windowsServiceLauncherFile;
80
80
  }
81
81
 
82
+ function writeWindowsTaskScript(launcherFile) {
83
+ if (fs.existsSync(windowsTaskScriptFile) && !windowsTaskScriptInstalled(windowsTaskScriptFile, windowsServiceLauncherFile)) {
84
+ throw new Error('The ReMCP scheduled task script is not owned by ReMCP; refusing to overwrite it');
85
+ }
86
+ const script = windowsTaskScriptText(launcherFile);
87
+ fs.mkdirSync(configDir, { recursive:true, mode:0o700 });
88
+ const temporary = `${windowsTaskScriptFile}.${process.pid}.${Date.now()}.tmp`;
89
+ try {
90
+ fs.writeFileSync(temporary, '\uFEFF' + script, { flag:'wx', encoding:'utf16le', mode:0o600 });
91
+ fs.renameSync(temporary, windowsTaskScriptFile);
92
+ } finally { try { fs.unlinkSync(temporary); } catch {} }
93
+ return windowsTaskScriptFile;
94
+ }
95
+
82
96
  function installWindowsUserStartup(cliPath, nodePath) {
83
97
  const existed = fs.existsSync(windowsStartupScriptFile);
84
98
  if (existed && !windowsStartupScriptInstalled(windowsStartupScriptFile)) {
@@ -149,7 +163,10 @@ export function installWindowsService(cliPath = globalCliPath(), { nodePath = pr
149
163
  return;
150
164
  }
151
165
  const launcherFile = writeWindowsServiceLauncher(cliPath, nodePath);
152
- const command = `"${launcherFile}"`;
166
+ const taskScript = writeWindowsTaskScript(launcherFile);
167
+ // WScript has no console; its hidden CMD child remains attached until it exits.
168
+ // /IT keeps desktop/UI automation in the signed-in user's session.
169
+ const command = `wscript.exe //B //Nologo "${taskScript}"`;
153
170
  // Current-user interactive tasks need /IT. /RL LIMITED avoids requiring elevation.
154
171
  // An organization may still disable creation of all tasks by standard users.
155
172
  const created = spawnSync('schtasks.exe',
@@ -411,6 +428,7 @@ export function uninstallPersistentService() {
411
428
  }
412
429
  spawnSync('schtasks.exe', ['/End', '/TN', windowsTaskName], { stdio: 'ignore' });
413
430
  spawnSync('schtasks.exe', ['/Delete', '/TN', windowsTaskName, '/F'], { stdio: 'ignore' });
431
+ try { if (windowsTaskScriptInstalled(windowsTaskScriptFile, windowsServiceLauncherFile)) fs.unlinkSync(windowsTaskScriptFile); } catch {}
414
432
  try { fs.unlinkSync(windowsServiceLauncherFile); } catch {}
415
433
  }
416
434
  }
@@ -71,6 +71,44 @@ export function windowsServiceLauncherText(cliPath, nodePath, { localAppData = p
71
71
  ].join('\r\n');
72
72
  }
73
73
 
74
+ export const WINDOWS_TASK_MARKER = "' ReMCP managed scheduled task (do not edit)";
75
+
76
+ // WScript is a GUI subsystem executable: it starts the existing CMD supervisor
77
+ // with SW_HIDE, while waiting for it so Task Scheduler still owns its lifetime.
78
+ // A Task Scheduler "Hidden" setting only hides the task in its UI, not the console.
79
+ export function windowsTaskScriptText(launcherPath) {
80
+ if (typeof launcherPath !== 'string' || !launcherPath ||
81
+ /["\0\r\n]/.test(launcherPath) ||
82
+ !(path.isAbsolute(launcherPath) || path.win32.isAbsolute(launcherPath))) {
83
+ throw new Error('Invalid ReMCP Windows task launcher');
84
+ }
85
+ return [
86
+ WINDOWS_TASK_MARKER,
87
+ 'Set remcpShell = CreateObject("WScript.Shell")',
88
+ `remcpShell.Run Chr(34) & "${launcherPath}" & Chr(34), 0, True`,
89
+ '',
90
+ ].join('\r\n');
91
+ }
92
+
93
+ export function windowsTaskScriptInstalled(file, expectedLauncherPath = null) {
94
+ try {
95
+ const text = fs.readFileSync(file, 'utf16le');
96
+ const lines = text.replace(/^\uFEFF/, '').split('\r\n');
97
+ if (!text.startsWith('\uFEFF') || lines.length !== 4 ||
98
+ lines[0] !== WINDOWS_TASK_MARKER ||
99
+ lines[1] !== 'Set remcpShell = CreateObject("WScript.Shell")' ||
100
+ lines[3] !== '') return false;
101
+ const match = lines[2].match(/^remcpShell\.Run Chr\(34\) & "([^"]+)" & Chr\(34\), 0, True$/);
102
+ if (!match) return false;
103
+ const launcher = match[1];
104
+ const absolute = path.isAbsolute(launcher) || path.win32.isAbsolute(launcher);
105
+ const expected = expectedLauncherPath === null ||
106
+ path.win32.normalize(launcher).toLowerCase() === path.win32.normalize(expectedLauncherPath).toLowerCase();
107
+ return absolute && expected &&
108
+ path.posix.basename(launcher.replaceAll('\\', '/')) === 'remcp-agent.cmd';
109
+ } catch { return false; }
110
+ }
111
+
74
112
  export const WINDOWS_STARTUP_MARKER = "' ReMCP managed user startup (do not edit)";
75
113
 
76
114
  // Start the existing private launcher in the interactive user's session without displaying