@remcp/remcp 0.2.117 → 0.2.119
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/service.mjs +102 -14
package/package.json
CHANGED
package/src/cli/service.mjs
CHANGED
|
@@ -51,13 +51,30 @@ function pathEntryExists(file) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function globalPackagePath(prefix, packageName) {
|
|
54
|
+
function globalPackagePath(prefix, packageName, platform = servicePlatform()) {
|
|
55
55
|
const name = String(packageName || '').trim();
|
|
56
56
|
const parts = name.split('/');
|
|
57
57
|
const validUnscoped = parts.length === 1 && parts[0] && !parts[0].startsWith('@') && parts[0] !== '.' && parts[0] !== '..';
|
|
58
58
|
const validScoped = parts.length === 2 && /^@[^/]+$/.test(parts[0]) && parts[1] && parts[1] !== '.' && parts[1] !== '..';
|
|
59
59
|
if (!validUnscoped && !validScoped) throw new Error(`Invalid npm package name for update: ${name || '(empty)'}`);
|
|
60
|
-
|
|
60
|
+
const modulesRoot = platform === 'win32'
|
|
61
|
+
? path.join(prefix, 'node_modules')
|
|
62
|
+
: path.join(prefix, 'lib', 'node_modules');
|
|
63
|
+
return path.join(modulesRoot, ...parts);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function managedWindowsRuntimePrefix() {
|
|
67
|
+
return path.resolve(
|
|
68
|
+
process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'),
|
|
69
|
+
'ReMCP',
|
|
70
|
+
'runtime',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isManagedWindowsRuntimePrefix(prefix) {
|
|
75
|
+
const actual = path.resolve(String(prefix || ''));
|
|
76
|
+
const expected = managedWindowsRuntimePrefix();
|
|
77
|
+
return actual.toLowerCase() === expected.toLowerCase();
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
function transactionalMacGlobalInstall({ resolved, prefix, packageNames, specs, preferOnline = false }) {
|
|
@@ -111,26 +128,93 @@ function transactionalMacGlobalInstall({ resolved, prefix, packageNames, specs,
|
|
|
111
128
|
}
|
|
112
129
|
}
|
|
113
130
|
|
|
131
|
+
function transactionalWindowsGlobalInstall({ resolved, prefix, packageNames, specs, preferOnline = false }) {
|
|
132
|
+
prefix = String(prefix || '').trim();
|
|
133
|
+
if (!path.isAbsolute(prefix)) throw new Error(`npm global prefix must be absolute for a Windows update: ${prefix || '(empty)'}`);
|
|
134
|
+
if (!isManagedWindowsRuntimePrefix(prefix)) {
|
|
135
|
+
run(resolved.command, [...resolved.args, ...npmGlobalInstallArgs(specs, { preferOnline })]);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const backupRoot = path.join(prefix, `.remcp-update-backup-${process.pid}-${Date.now()}`);
|
|
140
|
+
const moved = [];
|
|
141
|
+
const moveAside = (source, label) => {
|
|
142
|
+
if (!pathEntryExists(source)) return;
|
|
143
|
+
const backup = path.join(backupRoot, label);
|
|
144
|
+
fs.renameSync(source, backup);
|
|
145
|
+
moved.push({ source, backup });
|
|
146
|
+
};
|
|
147
|
+
const restore = originalError => {
|
|
148
|
+
let rollbackError = null;
|
|
149
|
+
for (const item of [...moved].reverse()) {
|
|
150
|
+
try {
|
|
151
|
+
if (pathEntryExists(item.source)) fs.rmSync(item.source, { recursive:true, force:true });
|
|
152
|
+
fs.mkdirSync(path.dirname(item.source), { recursive:true });
|
|
153
|
+
fs.renameSync(item.backup, item.source);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
rollbackError ??= error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
try { fs.rmSync(backupRoot, { recursive:true, force:true }); } catch (error) { rollbackError ??= error; }
|
|
159
|
+
if (rollbackError) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`ReMCP update failed and the previous Windows installation could not be fully restored: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
|
|
162
|
+
{ cause:originalError },
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
fs.mkdirSync(prefix, { recursive:true });
|
|
168
|
+
fs.mkdirSync(backupRoot, { recursive:false, mode:0o700 });
|
|
169
|
+
try {
|
|
170
|
+
const uniqueNames = [...new Set(packageNames.map(name => String(name || '').trim()).filter(Boolean))];
|
|
171
|
+
uniqueNames.forEach((name, index) => moveAside(globalPackagePath(prefix, name, 'win32'), `package-${index}`));
|
|
172
|
+
if (uniqueNames.includes(PACKAGE_NAME)) {
|
|
173
|
+
for (const shim of ['remcp', 'remcp.cmd', 'remcp.ps1']) moveAside(path.join(prefix, shim), `bin-${shim}`);
|
|
174
|
+
}
|
|
175
|
+
run(resolved.command, [...resolved.args, ...npmGlobalInstallArgs(specs, { preferOnline })]);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
restore(error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
fs.rmSync(backupRoot, { recursive:true, force:true });
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(`ReMCP update: could not remove Windows rollback backup ${backupRoot}: ${error instanceof Error ? error.message : String(error)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
114
188
|
// Release updates use exact versions that may have been published only moments ago. npm can keep a
|
|
115
189
|
// cached pre-publication packument long enough to answer ETARGET after the registry already serves the
|
|
116
190
|
// version, so updates revalidate metadata while retaining npm's cache for package bytes.
|
|
117
191
|
export function npmGlobalUpdate(packageNames, ...specs) {
|
|
118
|
-
|
|
119
|
-
|
|
192
|
+
const platform = servicePlatform();
|
|
193
|
+
const prefix = globalPrefix();
|
|
194
|
+
if (platform === 'darwin') {
|
|
195
|
+
transactionalMacGlobalInstall({ resolved:npm, prefix, packageNames, specs, preferOnline:true });
|
|
120
196
|
return;
|
|
121
197
|
}
|
|
122
|
-
|
|
123
|
-
|
|
198
|
+
if (platform === 'win32') {
|
|
199
|
+
transactionalWindowsGlobalInstall({ resolved:npm, prefix, packageNames, specs, preferOnline:true });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
run(npm.command, [...npm.args, ...npmGlobalInstallArgs(specs, { preferOnline:true })]);
|
|
124
203
|
}
|
|
125
204
|
|
|
126
205
|
export function npmGlobalUpdateForNode(nodePath, packageNames, ...specs) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
206
|
+
const platform = servicePlatform();
|
|
207
|
+
const resolved = resolveNpm({ nodePath, home, platform });
|
|
208
|
+
const prefix = output(resolved.command, [...resolved.args, 'prefix', '--global']);
|
|
209
|
+
if (platform === 'darwin') {
|
|
210
|
+
transactionalMacGlobalInstall({ resolved, prefix, packageNames, specs, preferOnline:true });
|
|
130
211
|
return;
|
|
131
212
|
}
|
|
132
|
-
|
|
133
|
-
|
|
213
|
+
if (platform === 'win32') {
|
|
214
|
+
transactionalWindowsGlobalInstall({ resolved, prefix, packageNames, specs, preferOnline:true });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
run(resolved.command, [...resolved.args, ...npmGlobalInstallArgs(specs, { preferOnline:true })]);
|
|
134
218
|
}
|
|
135
219
|
|
|
136
220
|
export function quoteSystemd(value) {
|
|
@@ -376,7 +460,10 @@ export function writeWindowsServiceLauncher(cliPath = globalCliPath(), nodePath
|
|
|
376
460
|
export function installWindowsService(cliPath = globalCliPath(), { nodePath = process.execPath } = {}) {
|
|
377
461
|
const launcherFile = writeWindowsServiceLauncher(cliPath, nodePath);
|
|
378
462
|
const command = `"${launcherFile}"`;
|
|
379
|
-
|
|
463
|
+
// Computer-use needs the signed-in interactive desktop. Create the task with /IT from the start
|
|
464
|
+
// instead of changing it afterwards: schtasks /Change can request the account password even when
|
|
465
|
+
// the task already belongs to the current user, which turns a one-command install into a prompt.
|
|
466
|
+
run('schtasks.exe', ['/Create', '/TN', windowsTaskName, '/TR', command, '/SC', 'ONLOGON', '/RL', 'HIGHEST', '/IT', '/F']);
|
|
380
467
|
run('schtasks.exe', ['/Run', '/TN', windowsTaskName]);
|
|
381
468
|
}
|
|
382
469
|
|
|
@@ -601,12 +688,13 @@ export function configureMacWriteAccess() {
|
|
|
601
688
|
}
|
|
602
689
|
|
|
603
690
|
export function configureWindowsWriteAccess() {
|
|
604
|
-
|
|
691
|
+
// installWindowsService already creates the task as HighestAvailable + InteractiveToken. Keep
|
|
692
|
+
// post-install access setup non-interactive so a fresh install never asks for the Windows password.
|
|
605
693
|
try {
|
|
606
694
|
const workspaceDir = path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'ReMCP');
|
|
607
695
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
608
696
|
} catch {}
|
|
609
|
-
console.log('ReMCP configured with
|
|
697
|
+
console.log('ReMCP configured with the highest available user privileges for interactive desktop access.');
|
|
610
698
|
}
|
|
611
699
|
|
|
612
700
|
export function configureLinuxWriteAccess() {
|