@remcp/remcp 0.2.27 → 0.2.28

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.mjs +19 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
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.mjs CHANGED
@@ -170,7 +170,10 @@ function macLaunchDomain() {
170
170
  function installMacService(cliPath = globalCliPath()) {
171
171
  const domain = macLaunchDomain();
172
172
  const target = `${domain}/${macServiceLabel}`;
173
- const cliScript = fs.realpathSync(cliPath);
173
+ // launchd wants an absolute path; a symlinked prefix that npm has not materialised yet (or a path
174
+ // that is about to be replaced by the next install) must not abort the repair — a stale plist is
175
+ // exactly the loop this function exists to break.
176
+ const cliScript = fs.existsSync(cliPath) ? fs.realpathSync(cliPath) : path.resolve(cliPath);
174
177
  fs.mkdirSync(path.dirname(macServiceFile), { recursive: true });
175
178
  fs.mkdirSync(path.dirname(macLogFile), { recursive: true });
176
179
  const plist = `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${macServiceLabel}</string>\n<key>ProgramArguments</key><array><string>${xmlEscape(process.execPath)}</string><string>${xmlEscape(cliScript)}</string><string>start</string></array>\n<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>\n<key>ProcessType</key><string>Background</string>\n<key>StandardOutPath</key><string>${xmlEscape(macLogFile)}</string>\n<key>StandardErrorPath</key><string>${xmlEscape(macLogFile)}</string>\n</dict></plist>\n`;
@@ -265,14 +268,24 @@ function ensureServiceIfRecorded(config) {
265
268
  const unit = fs.readFileSync(linuxServiceFile, 'utf8');
266
269
  const expected = `ExecStart=${quoteSystemd(cliPath)} start`;
267
270
  if (!unit.includes(expected)) {
268
- const repaired = unit.replace(/^ExecStart=.*$/m, expected);
269
- if (repaired === unit) throw new Error('remcp-agent.service has no ExecStart line to repair');
270
- fs.writeFileSync(linuxServiceFile, repaired);
271
- run('systemctl', ['--user', 'daemon-reload']);
271
+ const repaired = /^ExecStart=/m.test(unit) ? unit.replace(/^ExecStart=.*$/m, expected) : '';
272
+ if (!repaired) {
273
+ // A unit that lost its ExecStart line (edited by hand, or written as `ExecStart = …`) cannot
274
+ // be patched by substitution. Rewriting the whole unit is what keeps the machine out of the
275
+ // "old CLI forever" loop, so fall back to a fresh install instead of giving up.
276
+ installLinuxService(cliPath);
277
+ } else {
278
+ fs.writeFileSync(linuxServiceFile, repaired);
279
+ run('systemctl', ['--user', 'daemon-reload']);
280
+ }
272
281
  }
273
282
  }
274
283
  } else if (platform === 'darwin') {
275
- if (!fs.existsSync(macServiceFile)) installMacService(cliPath);
284
+ // launchd bakes the interpreter and the CLI path into the plist, so a Node manager that moves
285
+ // its global prefix leaves the agent launching a file that no longer exists — the same loop the
286
+ // Linux unit above is repaired for. Reinstalling is idempotent (bootout, bootstrap, enable,
287
+ // kickstart) and is what the Windows task already does on every update.
288
+ installMacService(cliPath);
276
289
  } else if (platform === 'win32') installWindowsService(cliPath);
277
290
  return true;
278
291
  } catch (error) {