@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 +1 -1
- package/src/cli/env.mjs +1 -0
- package/src/cli/service.mjs +21 -3
- package/src/cli/windows-supervisor.mjs +38 -0
package/package.json
CHANGED
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(
|
package/src/cli/service.mjs
CHANGED
|
@@ -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
|
|
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
|