@remcp/remcp 0.2.33 → 0.2.34

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.33",
3
+ "version": "0.2.34",
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",
@@ -18,7 +18,7 @@
18
18
  "README.md"
19
19
  ],
20
20
  "scripts": {
21
- "check": "node --check bin/remcp.mjs && node --check src/agent.mjs && node --check src/cli.mjs && node --check src/cli/config.mjs && node --check src/cli/connect.mjs && node --check src/cli/doctor.mjs && node --check src/cli/env.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/fs-access.mjs && node --check src/npm.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
21
+ "check": "node --check bin/remcp.mjs && node --check src/agent.mjs && node --check src/cli.mjs && node --check src/cli/config.mjs && node --check src/cli/connect.mjs && node --check src/cli/doctor.mjs && node --check src/cli/env.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/update.mjs && node --check src/fs-access.mjs && node --check src/npm.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
22
22
  "test": "node --test test/*.test.mjs"
23
23
  },
24
24
  "dependencies": {
@@ -8,13 +8,22 @@ import { ensureMachineId } from './config.mjs';
8
8
  import { officialOrigin } from './env.mjs';
9
9
  import { sleep } from './shell.mjs';
10
10
 
11
+ // Keep the server-provided approval URL as data all the way to the OS. In particular, do not route it
12
+ // through `cmd /c start` on Windows: cmd reparses metacharacters such as `&`, so a custom pairing
13
+ // server could otherwise turn a verification URL into a local shell command.
14
+ export function browserLaunchCommand(url, platform = process.platform) {
15
+ const target = String(url);
16
+ if (platform === 'darwin') return { command: 'open', args: [target] };
17
+ if (platform === 'win32') return { command: 'explorer.exe', args: [target] };
18
+ return { command: 'xdg-open', args: [target] };
19
+ }
20
+
11
21
  // Opens the approval page in the person's browser. A machine that nobody is looking at only gets the
12
22
  // printed URL, so every failure here is silent and non-fatal.
13
23
  export function openInBrowser(url) {
14
24
  try {
15
- const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
16
- const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
17
- const child = spawn(command, args, { stdio: 'ignore', detached: true });
25
+ const { command, args } = browserLaunchCommand(url);
26
+ const child = spawn(command, args, { stdio: 'ignore', detached: true, shell: false });
18
27
  child.on('error', () => {});
19
28
  child.unref();
20
29
  } catch {}
@@ -0,0 +1,74 @@
1
+ // `remcp update`: install what the server advertises, then let whoever supervises the agent restart
2
+ // it. Only a spec the configured package can satisfy is installed, so a pairing response from a
3
+ // custom server cannot point this machine at a different package or run a git/URL spec.
4
+ import fs from 'node:fs';
5
+ import process from 'node:process';
6
+
7
+ import { supervisorRestart } from '../agent.mjs';
8
+ import { isRuntimeSpecFor, normalizeRuntime } from '../runtime.mjs';
9
+ import { PACKAGE_NAME, VERSION } from '../version.mjs';
10
+
11
+ import { loadConfig, saveConfig } from './config.mjs';
12
+ import { installedVersion } from './doctor.mjs';
13
+ import { linuxServiceFile, macServiceFile, officialOrigin } from './env.mjs';
14
+ import { ensureServiceIfRecorded, npmGlobalInstall, restartPersistentServiceIfInstalled } from './service.mjs';
15
+
16
+ export async function updateCommand(flags) {
17
+ const cfg = loadConfig();
18
+ // The server advertises which runtime version it expects; an agent that is updating
19
+ // itself passes it through so client and runtime move together.
20
+ const requested = typeof flags.runtime === 'string' ? flags.runtime.trim() : '';
21
+ // Whatever the server advertises is installed globally, so it is validated exactly like the
22
+ // metadata from a pairing response: same package as the configured runtime, a spec that
23
+ // parses, and an explicit --trust-runtime before a custom server may change it.
24
+ let runtimeSpec = cfg.runtime.packageSpec;
25
+ if (requested) {
26
+ // Only `<configured package>@<semver>` is installable: an alias, a git/URL/file spec, a tag or
27
+ // a range would run code the user never agreed to.
28
+ if (!isRuntimeSpecFor(cfg.runtime.packageName, requested)) {
29
+ throw new Error(`--runtime must be ${cfg.runtime.packageName}@<version>`);
30
+ }
31
+ const trusted = cfg.trustRuntime === true
32
+ || Boolean(flags['trust-runtime'])
33
+ || process.env.REMCP_TRUST_RUNTIME === '1'
34
+ // A configuration written before the field existed paired with the official server, which is
35
+ // trusted by definition; refusing it would silently stop every existing device updating.
36
+ || new URL(cfg.serverUrl).origin === officialOrigin;
37
+ if (!trusted) {
38
+ throw new Error(`This machine was paired without trusting ${cfg.serverUrl} to choose a runtime version. Re-run with --trust-runtime if you trust that server.`);
39
+ }
40
+ runtimeSpec = normalizeRuntime({ kind: 'npm', packageName: cfg.runtime.packageName, packageSpec: requested, entry: cfg.runtime.entry }).packageSpec;
41
+ }
42
+ if (flags.check) {
43
+ console.log(JSON.stringify({
44
+ current: VERSION,
45
+ installedRuntime: installedVersion(cfg.runtime.packageName),
46
+ runtimeSpec: runtimeSpec,
47
+ available: `${PACKAGE_NAME}@latest`,
48
+ managedService: fs.existsSync(linuxServiceFile) || fs.existsSync(macServiceFile),
49
+ supervisor: supervisorRestart() ?? 'none',
50
+ }, null, 2));
51
+ return;
52
+ }
53
+ const before = { cli: VERSION, runtime: installedVersion(cfg.runtime.packageName) };
54
+ console.log(`Updating ReMCP to the latest published version (${runtimeSpec})…`);
55
+ npmGlobalInstall(`${PACKAGE_NAME}@latest`, runtimeSpec);
56
+ // The npm prefix can change across Node-manager upgrades. Repair an existing persistent-service
57
+ // launcher only after the install, when globalCliPath() points at the CLI we just installed.
58
+ ensureServiceIfRecorded(cfg);
59
+ // Only a validated spec is persisted, so a failed update cannot leave the install unable to start.
60
+ if (requested && runtimeSpec !== cfg.runtime.packageSpec) saveConfig({ ...cfg, runtime: { ...cfg.runtime, packageSpec: runtimeSpec } });
61
+ const after = { cli: installedVersion(PACKAGE_NAME), runtime: installedVersion(cfg.runtime.packageName) };
62
+ const restarted = restartPersistentServiceIfInstalled();
63
+ if (restarted) {
64
+ console.log(`ReMCP updated and ${restarted} restarted (client ${before.cli} → ${after.cli ?? '?'}, runtime ${before.runtime ?? '?'} → ${after.runtime ?? '?'}).`);
65
+ return;
66
+ }
67
+ // This process is the updater, not the agent: exiting here would restart nothing. The agent sees
68
+ // the exit status, verifies the installed version, and restarts itself.
69
+ console.log(`ReMCP updated (client ${before.cli} → ${after.cli ?? '?'}, runtime ${before.runtime ?? '?'} → ${after.runtime ?? '?'}). The running agent restarts itself to apply it.`);
70
+ if (after.cli === before.cli && after.runtime === before.runtime) {
71
+ console.log('Nothing changed: the installed versions already match the requested ones.');
72
+ }
73
+ return;
74
+ }
package/src/cli.mjs CHANGED
@@ -1,18 +1,18 @@
1
- import fs from 'node:fs';
2
1
  import os from 'node:os';
3
2
  import path from 'node:path';
4
3
  import process from 'node:process';
5
- import { runAgent, supervisorRestart } from './agent.mjs';
6
- import { isRuntimeSpecFor, normalizeRuntime } from './runtime.mjs';
4
+ import { runAgent } from './agent.mjs';
5
+ import { normalizeRuntime } from './runtime.mjs';
7
6
  import { PACKAGE_NAME, VERSION } from './version.mjs';
8
7
  import { probeFilesystemAccess } from './fs-access.mjs';
9
8
 
10
9
  import { ensureMachineId, loadConfig, readJsonFile, saveConfig, setTelemetry, telemetryState, writeJsonFile } from './cli/config.mjs';
11
10
  import { assertRuntimeTrust, pairWithDeviceCode } from './cli/connect.mjs';
12
- import { diagnoseLocalRuntime, installedVersion, runtimeAllowedRoots } from './cli/doctor.mjs';
13
- import { configFile, linuxServiceFile, macServiceFile, npm, officialOrigin, runtimeConfigFile } from './cli/env.mjs';
14
- import { ensureServiceIfRecorded, globalCliPath, installPersistentAgent, npmGlobalInstall, restartPersistentServiceIfInstalled, uninstallPersistentService } from './cli/service.mjs';
11
+ import { diagnoseLocalRuntime, runtimeAllowedRoots } from './cli/doctor.mjs';
12
+ import { configFile, npm, officialOrigin, runtimeConfigFile } from './cli/env.mjs';
13
+ import { installPersistentAgent, restartPersistentServiceIfInstalled, uninstallPersistentService } from './cli/service.mjs';
15
14
  import { run } from './cli/shell.mjs';
15
+ import { updateCommand } from './cli/update.mjs';
16
16
  function parse(argv) {
17
17
  const [command = 'help', ...rest] = argv;
18
18
  const flags = {};
@@ -208,62 +208,7 @@ export async function main(argv = process.argv.slice(2)) {
208
208
  }
209
209
 
210
210
  if (command === 'update') {
211
- const cfg = loadConfig();
212
- // The server advertises which runtime version it expects; an agent that is updating
213
- // itself passes it through so client and runtime move together.
214
- const requested = typeof flags.runtime === 'string' ? flags.runtime.trim() : '';
215
- // Whatever the server advertises is installed globally, so it is validated exactly like the
216
- // metadata from a pairing response: same package as the configured runtime, a spec that
217
- // parses, and an explicit --trust-runtime before a custom server may change it.
218
- let runtimeSpec = cfg.runtime.packageSpec;
219
- if (requested) {
220
- // Only `<configured package>@<semver>` is installable: an alias, a git/URL/file spec, a tag or
221
- // a range would run code the user never agreed to.
222
- if (!isRuntimeSpecFor(cfg.runtime.packageName, requested)) {
223
- throw new Error(`--runtime must be ${cfg.runtime.packageName}@<version>`);
224
- }
225
- const trusted = cfg.trustRuntime === true
226
- || Boolean(flags['trust-runtime'])
227
- || process.env.REMCP_TRUST_RUNTIME === '1'
228
- // A configuration written before the field existed paired with the official server, which is
229
- // trusted by definition; refusing it would silently stop every existing device updating.
230
- || new URL(cfg.serverUrl).origin === officialOrigin;
231
- if (!trusted) {
232
- throw new Error(`This machine was paired without trusting ${cfg.serverUrl} to choose a runtime version. Re-run with --trust-runtime if you trust that server.`);
233
- }
234
- runtimeSpec = normalizeRuntime({ kind: 'npm', packageName: cfg.runtime.packageName, packageSpec: requested, entry: cfg.runtime.entry }).packageSpec;
235
- }
236
- if (flags.check) {
237
- console.log(JSON.stringify({
238
- current: VERSION,
239
- installedRuntime: installedVersion(cfg.runtime.packageName),
240
- runtimeSpec: runtimeSpec,
241
- available: `${PACKAGE_NAME}@latest`,
242
- managedService: fs.existsSync(linuxServiceFile) || fs.existsSync(macServiceFile),
243
- supervisor: supervisorRestart() ?? 'none',
244
- }, null, 2));
245
- return;
246
- }
247
- const before = { cli: VERSION, runtime: installedVersion(cfg.runtime.packageName) };
248
- console.log(`Updating ReMCP to the latest published version (${runtimeSpec})…`);
249
- npmGlobalInstall(`${PACKAGE_NAME}@latest`, runtimeSpec);
250
- // The npm prefix can change across Node-manager upgrades. Repair an existing persistent-service
251
- // launcher only after the install, when globalCliPath() points at the CLI we just installed.
252
- ensureServiceIfRecorded(cfg);
253
- // Only a validated spec is persisted, so a failed update cannot leave the install unable to start.
254
- if (requested && runtimeSpec !== cfg.runtime.packageSpec) saveConfig({ ...cfg, runtime: { ...cfg.runtime, packageSpec: runtimeSpec } });
255
- const after = { cli: installedVersion(PACKAGE_NAME), runtime: installedVersion(cfg.runtime.packageName) };
256
- const restarted = restartPersistentServiceIfInstalled();
257
- if (restarted) {
258
- console.log(`ReMCP updated and ${restarted} restarted (client ${before.cli} → ${after.cli ?? '?'}, runtime ${before.runtime ?? '?'} → ${after.runtime ?? '?'}).`);
259
- return;
260
- }
261
- // This process is the updater, not the agent: exiting here would restart nothing. The agent sees
262
- // the exit status, verifies the installed version, and restarts itself.
263
- console.log(`ReMCP updated (client ${before.cli} → ${after.cli ?? '?'}, runtime ${before.runtime ?? '?'} → ${after.runtime ?? '?'}). The running agent restarts itself to apply it.`);
264
- if (after.cli === before.cli && after.runtime === before.runtime) {
265
- console.log('Nothing changed: the installed versions already match the requested ones.');
266
- }
211
+ await updateCommand(flags);
267
212
  return;
268
213
  }
269
214
 
@@ -278,4 +223,4 @@ export async function main(argv = process.argv.slice(2)) {
278
223
  }
279
224
 
280
225
  printHelp();
281
- }
226
+ }