@remcp/remcp 0.2.55 → 0.2.56
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 +100 -2
- package/src/cli/update.mjs +4 -1
- package/src/cli.mjs +5 -1
package/package.json
CHANGED
package/src/cli/service.mjs
CHANGED
|
@@ -158,6 +158,100 @@ function shellQuote(value) {
|
|
|
158
158
|
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
const remcpPathBlockStart = '# >>> ReMCP CLI PATH >>>';
|
|
162
|
+
const remcpPathBlockEnd = '# <<< ReMCP CLI PATH <<<';
|
|
163
|
+
const remcpCliShimMarker = '# ReMCP managed CLI shim';
|
|
164
|
+
|
|
165
|
+
function macCliShimPath() {
|
|
166
|
+
return path.join(home, '.local', 'bin', 'remcp');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function interactiveShellProfile() {
|
|
170
|
+
const shell = String(process.env.REMCP_TEST_SHELL || process.env.SHELL || os.userInfo().shell || '/bin/zsh');
|
|
171
|
+
const name = path.basename(shell);
|
|
172
|
+
if (name === 'zsh') return path.join(home, '.zshrc');
|
|
173
|
+
if (name === 'bash') return path.join(home, '.bash_profile');
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function upsertManagedPathBlock(file) {
|
|
178
|
+
if (!file) return false;
|
|
179
|
+
const block = [
|
|
180
|
+
remcpPathBlockStart,
|
|
181
|
+
'case ":$PATH:" in',
|
|
182
|
+
' *":$HOME/.local/bin:"*) ;;',
|
|
183
|
+
' *) export PATH="$HOME/.local/bin:$PATH" ;;',
|
|
184
|
+
'esac',
|
|
185
|
+
remcpPathBlockEnd,
|
|
186
|
+
].join('\n');
|
|
187
|
+
let current = '';
|
|
188
|
+
try { current = fs.readFileSync(file, 'utf8'); } catch {}
|
|
189
|
+
const start = current.indexOf(remcpPathBlockStart);
|
|
190
|
+
const end = start >= 0 ? current.indexOf(remcpPathBlockEnd, start + remcpPathBlockStart.length) : -1;
|
|
191
|
+
let next;
|
|
192
|
+
if (start >= 0 && end >= 0) {
|
|
193
|
+
next = current.slice(0, start) + block + current.slice(end + remcpPathBlockEnd.length);
|
|
194
|
+
} else {
|
|
195
|
+
const prefix = current && !current.endsWith('\n') ? current + '\n' : current;
|
|
196
|
+
next = prefix + (prefix ? '\n' : '') + block + '\n';
|
|
197
|
+
}
|
|
198
|
+
if (next === current) return false;
|
|
199
|
+
fs.writeFileSync(file, next, { mode:0o600 });
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function removeManagedPathBlock(file) {
|
|
204
|
+
if (!file) return;
|
|
205
|
+
let current = '';
|
|
206
|
+
try { current = fs.readFileSync(file, 'utf8'); } catch { return; }
|
|
207
|
+
const start = current.indexOf(remcpPathBlockStart);
|
|
208
|
+
const end = start >= 0 ? current.indexOf(remcpPathBlockEnd, start + remcpPathBlockStart.length) : -1;
|
|
209
|
+
if (start < 0 || end < 0) return;
|
|
210
|
+
let before = current.slice(0, start);
|
|
211
|
+
let after = current.slice(end + remcpPathBlockEnd.length);
|
|
212
|
+
if (before.endsWith('\n') && after.startsWith('\n')) after = after.slice(1);
|
|
213
|
+
const next = before + after;
|
|
214
|
+
fs.writeFileSync(file, next, { mode:0o600 });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function ensureMacCliCommand(config = {}, { cliPath = '', nodePath = '' } = {}) {
|
|
218
|
+
if (servicePlatform() !== 'darwin') return null;
|
|
219
|
+
const effectiveCli = cliPath || (persistentServiceExpected(config) ? canonicalServiceCliPath(config) : globalCliPath());
|
|
220
|
+
const effectiveNode = nodePath || (persistentServiceExpected(config) ? canonicalServiceNodePath(config) : process.execPath);
|
|
221
|
+
const cliScript = resolvedCliScript(effectiveCli);
|
|
222
|
+
const shim = macCliShimPath();
|
|
223
|
+
try {
|
|
224
|
+
const existing = fs.readFileSync(shim, 'utf8');
|
|
225
|
+
if (!existing.includes(remcpCliShimMarker)) {
|
|
226
|
+
console.error(`ReMCP did not replace ${shim} because it is not a ReMCP-managed command.`);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error?.code && error.code !== 'ENOENT') {
|
|
231
|
+
console.error(`ReMCP could not inspect ${shim}: ${error instanceof Error ? error.message : String(error)}`);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const launcher = `#!/bin/sh\n${remcpCliShimMarker}\nexec ${shellQuote(effectiveNode)} ${shellQuote(cliScript)} "$@"\n`;
|
|
236
|
+
fs.mkdirSync(path.dirname(shim), { recursive:true, mode:0o755 });
|
|
237
|
+
const temporary = `${shim}.${process.pid}.${Date.now()}.tmp`;
|
|
238
|
+
fs.writeFileSync(temporary, launcher, { mode:0o755 });
|
|
239
|
+
fs.chmodSync(temporary, 0o755);
|
|
240
|
+
fs.renameSync(temporary, shim);
|
|
241
|
+
upsertManagedPathBlock(interactiveShellProfile());
|
|
242
|
+
return shim;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function removeMacCliCommand() {
|
|
246
|
+
if (servicePlatform() !== 'darwin') return;
|
|
247
|
+
const shim = macCliShimPath();
|
|
248
|
+
try {
|
|
249
|
+
const existing = fs.readFileSync(shim, 'utf8');
|
|
250
|
+
if (existing.includes(remcpCliShimMarker)) fs.unlinkSync(shim);
|
|
251
|
+
} catch {}
|
|
252
|
+
removeManagedPathBlock(interactiveShellProfile());
|
|
253
|
+
}
|
|
254
|
+
|
|
161
255
|
function macServicePlist(cliPath, nodePath = process.execPath) {
|
|
162
256
|
// launchd wants an absolute path; a symlinked prefix that npm has not materialised yet (or a path
|
|
163
257
|
// that is about to be replaced by the next install) must not abort the repair — a stale plist is
|
|
@@ -486,8 +580,10 @@ export function installPersistentAgent(config) {
|
|
|
486
580
|
const cliScript = resolvedCliScript(cliPath);
|
|
487
581
|
const nodePath = process.execPath;
|
|
488
582
|
if (platform === 'linux') installLinuxService(cliPath, nodePath);
|
|
489
|
-
else if (platform === 'darwin')
|
|
490
|
-
|
|
583
|
+
else if (platform === 'darwin') {
|
|
584
|
+
installMacService(cliPath, { nodePath });
|
|
585
|
+
ensureMacCliCommand(config, { cliPath:cliScript, nodePath });
|
|
586
|
+
} else installWindowsService(cliPath);
|
|
491
587
|
configurePostInstallAccess();
|
|
492
588
|
saveConfig({ ...config, serviceInstalled: true, serviceCliPath: cliScript, serviceNodePath: nodePath });
|
|
493
589
|
console.log('ReMCP is installed as a background service. Future updates: remcp update');
|
|
@@ -530,6 +626,7 @@ export function ensureServiceIfRecorded(config) {
|
|
|
530
626
|
// installMacService either leaves an unchanged loaded job alone, bootstraps an unloaded job, or
|
|
531
627
|
// hands a changed launcher to an independent transient launchd helper.
|
|
532
628
|
const state = installMacService(cliPath, { restart: false, nodePath });
|
|
629
|
+
ensureMacCliCommand(config, { cliPath, nodePath });
|
|
533
630
|
if (state === 'reload-scheduled') restartScheduled = macServiceLabel;
|
|
534
631
|
} else if (platform === 'win32') installWindowsService(cliPath);
|
|
535
632
|
// Upgrade the legacy inferred state only after the supervisor repair succeeded. A failed repair
|
|
@@ -586,6 +683,7 @@ export function uninstallPersistentService() {
|
|
|
586
683
|
const domain = macLaunchDomain();
|
|
587
684
|
spawnSync('launchctl', ['bootout', domain, macServiceFile], { stdio: 'ignore' });
|
|
588
685
|
try { fs.unlinkSync(macServiceFile); } catch {}
|
|
686
|
+
removeMacCliCommand();
|
|
589
687
|
} else if (platform === 'win32') {
|
|
590
688
|
spawnSync('schtasks.exe', ['/End', '/TN', windowsTaskName], { stdio: 'ignore' });
|
|
591
689
|
spawnSync('schtasks.exe', ['/Delete', '/TN', windowsTaskName, '/F'], { stdio: 'ignore' });
|
package/src/cli/update.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { PACKAGE_NAME, VERSION } from '../version.mjs';
|
|
|
11
11
|
import { loadConfig, saveConfig } from './config.mjs';
|
|
12
12
|
import { installedVersion } from './doctor.mjs';
|
|
13
13
|
import { linuxServiceFile, macServiceFile, officialOrigin } from './env.mjs';
|
|
14
|
-
import { currentInstallationInfo, ensureServiceIfRecorded, installationVersionsAtCliPath, npmGlobalUpdate, persistentServiceExpected, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, syncKnownInstallations } from './service.mjs';
|
|
14
|
+
import { currentInstallationInfo, ensureMacCliCommand, ensureServiceIfRecorded, installationVersionsAtCliPath, npmGlobalUpdate, persistentServiceExpected, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, syncKnownInstallations } from './service.mjs';
|
|
15
15
|
|
|
16
16
|
const UPDATE_DISCOVERY_TIMEOUT_MS = 5000;
|
|
17
17
|
|
|
@@ -232,6 +232,9 @@ export async function updateCommand(flags) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
if (alreadyCurrent) {
|
|
235
|
+
// `npx @remcp/remcp@latest update` is the recovery path when a Node-manager prefix was never
|
|
236
|
+
// exported into the user's shell. Even with no package delta, repair the stable macOS command.
|
|
237
|
+
ensureMacCliCommand(cfg);
|
|
235
238
|
if (targets.persistRuntime) {
|
|
236
239
|
saveConfig({ ...cfg, runtime:{ ...cfg.runtime, packageSpec:targets.runtimeSpec } });
|
|
237
240
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { ensureMachineId, loadConfig, readJsonFile, saveConfig, setTelemetry, te
|
|
|
11
11
|
import { assertRuntimeTrust, pairWithDeviceCode } from './cli/connect.mjs';
|
|
12
12
|
import { diagnoseLocalRuntime, installedVersion, runtimeAllowedRoots } from './cli/doctor.mjs';
|
|
13
13
|
import { configFile, npm, officialOrigin, runtimeConfigFile } from './cli/env.mjs';
|
|
14
|
-
import { currentInstallationInfo, installPersistentAgent, persistentServiceState, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, uninstallPersistentService } from './cli/service.mjs';
|
|
14
|
+
import { currentInstallationInfo, ensureMacCliCommand, installPersistentAgent, persistentServiceState, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, uninstallPersistentService } from './cli/service.mjs';
|
|
15
15
|
import { run } from './cli/shell.mjs';
|
|
16
16
|
import { updateCommand } from './cli/update.mjs';
|
|
17
17
|
function parse(argv) {
|
|
@@ -97,6 +97,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
97
97
|
|| Boolean(process.env.INVOCATION_ID)
|
|
98
98
|
|| String(process.env.XPC_SERVICE_NAME || '').includes('com.remcp.agent');
|
|
99
99
|
const cfg = rememberCurrentInstallation(loadConfig(), { service:supervised });
|
|
100
|
+
// A service can start from an absolute fnm/nvm Node path even when an interactive macOS shell
|
|
101
|
+
// cannot resolve the npm-global `remcp` binary. Refresh the stable user shim on every start so
|
|
102
|
+
// an auto-update repairs the next Terminal session without requiring a manual PATH edit.
|
|
103
|
+
ensureMacCliCommand(cfg);
|
|
100
104
|
const service = persistentServiceState(cfg);
|
|
101
105
|
if (!supervised && service.active) {
|
|
102
106
|
console.log(`ReMCP is already running as a background service (${service.name}). Use \`remcp status\` to inspect it or \`remcp doctor\` to diagnose it.`);
|