@remcp/remcp 0.2.208 → 0.2.210

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/README.md CHANGED
@@ -90,7 +90,7 @@ permission, and `remcp doctor` reports the state of each one.
90
90
 
91
91
  ## Updates
92
92
 
93
- The agent asks the server which versions it should run on every successful connection and every six hours, installs the exact trusted client/runtime release pair in the background, and restarts the managed service so it takes effect. The updater resolves itself and the sibling runtime from the package that is actually running instead of depending on an interactive shell PATH. When the same computer has been used from more than one Node manager (for example NVM plus Hermes/FNM/Homebrew), ReMCP keeps the canonical service installation stable and converges the known local installations on the same release pair. `remcp status` reports mixed installations instead of hiding the skew. A failed install is retried no more often than every thirty minutes and update-check failures are logged, so a broken release cannot become a silent install loop. `remcp update` does the same immediately; `remcp auto-update off` disables automatic checks.
93
+ The agent asks the server which versions it should run on every successful connection and every six hours, installs the exact trusted client/runtime release pair in the background, and restarts the managed service so it takes effect. The updater resolves itself and the sibling runtime from the package that is actually running instead of depending on an interactive shell PATH. When a computer has several Node.js environments (for example NVM, FNM or a system installation), ReMCP updates only the canonical managed service packages. It does not upgrade Node.js, rewrite unrelated npm packages, or silently remove old ReMCP copies. `remcp status` counts package roots verified from the current CLI/service and bounded installation history; aliases and vanished paths are not extra installations. `remcp doctor` adds the exact read-only package roots, corresponding client/runtime versions, aliases and historical/cache classifications. These are **physical package copies, not a count of running agents**. Old copies are never automatically removed; verifying process, startup and script dependencies is required before an explicit owner-approved cleanup. A failed install is retried no more often than every thirty minutes and update-check failures are logged, so a broken release cannot become a silent install loop. `remcp update` does the same immediately; `remcp auto-update off` disables automatic checks.
94
94
 
95
95
  ## Computer-use routing
96
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.208",
3
+ "version": "0.2.210",
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-deploy-reconnect.mjs && node --check src/agent-network-probe-client.mjs && node --check src/agent-relay-connection.mjs && node --check src/agent-responder.mjs && node --check src/agent-runtime-capabilities.mjs && node --check src/agent-update-monitor.mjs && node --check src/agent-update.mjs && node --check src/agent-utilities.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/mac-native-temperature.mjs && node --check src/cli/npm-global.mjs && node --check src/cli/pairing-qr.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service-macos.mjs && node --check src/cli/service-paths.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/update.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-user-supervisor.mjs && node --check src/fetch-update-manifest.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-deploy-reconnect.mjs && node --check src/agent-network-probe-client.mjs && node --check src/agent-relay-connection.mjs && node --check src/agent-responder.mjs && node --check src/agent-runtime-capabilities.mjs && node --check src/agent-update-monitor.mjs && node --check src/agent-update.mjs && node --check src/agent-utilities.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/installation-audit.mjs && node --check src/cli/mac-native-temperature.mjs && node --check src/cli/npm-global.mjs && node --check src/cli/pairing-qr.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service-macos.mjs && node --check src/cli/service-paths.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/update.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-user-supervisor.mjs && node --check src/fetch-update-manifest.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": {
@@ -2,7 +2,6 @@
2
2
  // the server uses to recognise this computer across pairings.
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import process from 'node:process';
6
5
  import { randomUUID } from 'node:crypto';
7
6
  import { normalizeRuntime } from '../runtime.mjs';
8
7
 
@@ -79,13 +78,12 @@ export function writeJsonFile(file, value) {
79
78
  fs.chmodSync(file, 0o600);
80
79
  }
81
80
 
82
- export function autoUpdateEnabled(config, platform = process.platform) {
83
- // Linux often runs unrelated applications through the same global npm prefix. Never
84
- // mutate that prefix implicitly: require `remcp auto-update on` for Linux agents.
85
- // Keep the historical preference for existing explicit opt-ins and other platforms.
86
- if (config?.autoUpdate === true) return true;
87
- if (config?.autoUpdate === false) return false;
88
- return platform !== 'linux';
81
+ export function autoUpdateEnabled(config) {
82
+ // This preference controls the two first-party ReMCP npm packages only. It never
83
+ // upgrades the Node.js interpreter or npm itself. All supported OSes keep the
84
+ // historical default of automatic ReMCP releases unless the owner opts out.
85
+ // Updates use the verified managed service prefix, not a shared global prefix.
86
+ return config?.autoUpdate !== false;
89
87
  }
90
88
 
91
89
  export function flagEnabled(value) {
@@ -0,0 +1,79 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const CLIENT_PACKAGE = '@remcp/remcp';
5
+ const MAX_HISTORY = 32;
6
+
7
+ // Inventory only paths previously recorded by this owner and the two current CLI
8
+ // entrypoints. Never crawl npm/node directories or infer running processes.
9
+ function installedPackageAt(cliPath) {
10
+ if (typeof cliPath !== 'string' || !cliPath.trim() || !fs.existsSync(cliPath)) return null;
11
+ let resolved;
12
+ try { resolved = fs.realpathSync(cliPath); } catch { return null; }
13
+ const normalized = resolved.replaceAll('\\', '/');
14
+ const marker = '/node_modules/@remcp/remcp/';
15
+ const markerAt = normalized.lastIndexOf(marker);
16
+ let root = markerAt < 0 ? null : resolved.slice(0, markerAt + marker.length - 1);
17
+ // npm on Windows uses a .cmd shim in the global prefix instead of a link.
18
+ if (!root && path.basename(resolved).toLowerCase() === 'remcp.cmd') {
19
+ root = path.join(path.dirname(resolved), 'node_modules', '@remcp', 'remcp');
20
+ }
21
+ if (!root) return null;
22
+ try {
23
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
24
+ if (manifest.name !== CLIENT_PACKAGE || typeof manifest.version !== 'string') return null;
25
+ let runtimeVersion = null;
26
+ try {
27
+ const runtime = JSON.parse(fs.readFileSync(path.join(root, '..', 'runtime', 'package.json'), 'utf8'));
28
+ if (runtime.name === '@remcp/runtime' && typeof runtime.version === 'string') runtimeVersion = runtime.version;
29
+ } catch {}
30
+ return { root:fs.realpathSync(root), resolved, version:manifest.version, runtimeVersion };
31
+ } catch { return null; }
32
+ }
33
+
34
+ export function auditKnownInstallations(config, { currentCliPath, serviceCliPath } = {}) {
35
+ const history = Array.isArray(config?.installations) ? config.installations.slice(-MAX_HISTORY) : [];
36
+ const candidates = [
37
+ { path:currentCliPath, source:'invoked_cli' },
38
+ { path:serviceCliPath, source:'managed_service' },
39
+ ...history.map(item => ({ path:item?.cliPath, source:'saved_history' })),
40
+ ];
41
+ const packages = new Map();
42
+ const aliases = [];
43
+ let ignoredUnverified = 0;
44
+ for (const candidate of candidates) {
45
+ if (typeof candidate.path !== 'string' || !candidate.path.trim() || !fs.existsSync(candidate.path)) continue;
46
+ const installed = installedPackageAt(candidate.path);
47
+ if (!installed) { ignoredUnverified++; continue; }
48
+ let copy = packages.get(installed.root);
49
+ if (!copy) {
50
+ copy = { packageRoot:installed.root, version:installed.version, runtimeVersion:installed.runtimeVersion, sources:new Set(), cliPaths:new Set() };
51
+ packages.set(installed.root, copy);
52
+ }
53
+ copy.sources.add(candidate.source);
54
+ copy.cliPaths.add(candidate.path);
55
+ if (candidate.path !== installed.resolved && !aliases.some(a => a.path === candidate.path)) {
56
+ aliases.push({ path:candidate.path, resolvesTo:installed.resolved });
57
+ }
58
+ }
59
+ const copies = [...packages.values()].map(item => ({
60
+ packageRoot:item.packageRoot,
61
+ version:item.version,
62
+ runtimeVersion:item.runtimeVersion,
63
+ sources:[...item.sources],
64
+ kind:item.packageRoot.replaceAll('\\', '/').includes('/_npx/') ? 'npx_cache' : 'npm_package',
65
+ isInvoked:item.sources.has('invoked_cli'),
66
+ isManagedService:item.sources.has('managed_service'),
67
+ cliPaths:[...item.cliPaths],
68
+ }));
69
+ return {
70
+ scope:'recorded_paths_only',
71
+ processConcurrency:'not_measured',
72
+ packageCopyCount:copies.length,
73
+ otherCopyCount:copies.filter(item => !item.isInvoked && !item.isManagedService).length,
74
+ ignoredUnverifiedPathCount:ignoredUnverified,
75
+ aliases,
76
+ copies,
77
+ removal:'disabled; explicit owner review and process/dependency audit required',
78
+ };
79
+ }
package/src/cli.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import fs from 'node:fs';
1
2
  import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import process from 'node:process';
@@ -12,6 +13,7 @@ import { assertRuntimeTrust, assertSecureServerUrl, pairWithDeviceCode } from '.
12
13
  import { diagnoseLocalRuntime, installedVersion, runtimeAllowedRoots } from './cli/doctor.mjs';
13
14
  import { configFile, npm, officialOrigin, runtimeConfigFile } from './cli/env.mjs';
14
15
  import { currentInstallationInfo, ensureMacCliCommand, installPersistentAgent, persistentServiceState, servicePlatform, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, uninstallPersistentService } from './cli/service.mjs';
16
+ import { auditKnownInstallations } from './cli/installation-audit.mjs';
15
17
  import { scheduleMacTemperatureRepair } from './cli/mac-native-temperature.mjs';
16
18
  import { repairMacTemperatureAfterStart } from './cli/npm-global.mjs';
17
19
  import { run } from './cli/shell.mjs';
@@ -36,7 +38,20 @@ function parse(argv) {
36
38
  }
37
39
 
38
40
  function printHelp() {
39
- console.log(`ReMCP ${VERSION}\n\nCommands:\n remcp connect [--no-qr]\n remcp start\n remcp status\n remcp doctor\n remcp update\n remcp install\n remcp uninstall\n remcp uninstall --purge\n remcp telemetry [status|on|off]\n remcp godmode [status|on|off]\n remcp --version\n\nPairing commands are generated in the ReMCP workspace.\n\nUsage metrics are opt-out (tool names, timings, outcomes only, sent to your own ReMCP\naccount through the paired agent). Disable them at any time with: remcp telemetry off\n\nUnrestricted mode (remcp godmode on) lifts the access roots and the command guardrails for this\ncomputer only. The MCP configuration tool cannot enable it; protect remote terminal access.`);
41
+ console.log(`ReMCP ${VERSION}\n\nCommands:\n remcp connect [--no-qr]\n remcp start\n remcp status\n remcp doctor\n remcp update\n remcp auto-update [status|on|off]\n remcp install\n remcp uninstall\n remcp uninstall --purge\n remcp telemetry [status|on|off]\n remcp godmode [status|on|off]\n remcp --version\n\nPairing commands are generated in the ReMCP workspace.\n\nUsage metrics are opt-out (tool names, timings, outcomes only, sent to your own ReMCP\naccount through the paired agent). Disable them at any time with: remcp telemetry off\n\nUnrestricted mode (remcp godmode on) lifts the access roots and the command guardrails for this\ncomputer only. The MCP configuration tool cannot enable it; protect remote terminal access.`);
42
+ }
43
+
44
+ // The running agent snapshots update policy at startup. A saved toggle alone does not
45
+ // change its six-hour polling loop, so reload a managed service after a policy change.
46
+ // Defer Linux handoff to avoid terminating a remote command before it returns.
47
+ export function applyAutoUpdatePreference(cfg, enabled, {
48
+ persist = saveConfig,
49
+ restart = restartPersistentServiceIfInstalled,
50
+ } = {}) {
51
+ const changed = autoUpdateEnabled(cfg) !== enabled;
52
+ const updated = { ...cfg, autoUpdate: enabled };
53
+ persist(updated);
54
+ return changed ? restart(updated, { deferLinux:true }) : null;
40
55
  }
41
56
 
42
57
  export async function main(argv = process.argv.slice(2)) {
@@ -201,8 +216,15 @@ export async function main(argv = process.argv.slice(2)) {
201
216
  console.log(JSON.stringify({ autoUpdate: autoUpdateEnabled(cfg), checkIntervalHours: 6 }, null, 2));
202
217
  return;
203
218
  }
204
- if (action === 'on' || action === 'enable') { saveConfig({ ...cfg, autoUpdate: true }); console.log('Auto-update enabled.'); return; }
205
- if (action === 'off' || action === 'disable') { saveConfig({ ...cfg, autoUpdate: false }); console.log('Auto-update disabled. Run `remcp update` yourself when you want a new version.'); return; }
219
+ if (['on', 'enable', 'off', 'disable'].includes(action)) {
220
+ const enabled = action === 'on' || action === 'enable';
221
+ const changed = autoUpdateEnabled(cfg) !== enabled;
222
+ const restarted = applyAutoUpdatePreference(cfg, enabled);
223
+ console.log(enabled ? 'Auto-update enabled.' : 'Auto-update disabled. Run `remcp update` yourself when you want a new version.');
224
+ if (restarted) console.log(`ReMCP agent ${restarted} to apply the update policy.`);
225
+ else if (changed) console.log('Restart any manually managed ReMCP agent to apply the update policy.');
226
+ return;
227
+ }
206
228
  throw new Error('Usage: remcp auto-update [status|on|off]');
207
229
  }
208
230
 
@@ -284,12 +306,13 @@ export async function main(argv = process.argv.slice(2)) {
284
306
  const targetClient = String(advertised?.cliVersion || '').trim() || null;
285
307
  const targetRuntime = String(advertised?.runtime || '').trim() || null;
286
308
  const configuredRuntime = String(cfg.runtime.packageSpec || '');
287
- const installationPaths = new Set([
288
- currentInstall.cliPath,
289
- serviceInstall.cliPath,
290
- ...(Array.isArray(cfg.installations) ? cfg.installations.map(item => item?.cliPath) : []),
291
- ].filter(Boolean));
292
- const multipleInstallations = installationPaths.size > 1;
309
+ // Count verified physical ReMCP package roots, not merely surviving paths or
310
+ // shell aliases. This is NOT a concurrent-agent/process count.
311
+ const installationAudit = auditKnownInstallations(cfg, {
312
+ currentCliPath:currentInstall.cliPath,
313
+ serviceCliPath:service.installed ? serviceInstall.cliPath : null,
314
+ });
315
+ const multipleInstallations = installationAudit.packageCopyCount > 1;
293
316
  const knownCliVersions = [VERSION, serviceInstall.cliVersion].filter(Boolean);
294
317
  const knownRuntimeVersions = [installedRuntime, serviceInstall.runtimeVersion].filter(Boolean);
295
318
  const versionConsistent = cfg.runtime.packageName === '@remcp/runtime'
@@ -343,6 +366,9 @@ export async function main(argv = process.argv.slice(2)) {
343
366
  targetClient,
344
367
  targetRuntime,
345
368
  multipleInstallations,
369
+ knownPackageCopies:installationAudit.packageCopyCount,
370
+ additionalKnownPackageCopies:installationAudit.otherCopyCount,
371
+ installationCountScope:'verified_physical_packages_not_running_agents',
346
372
  versionConsistent,
347
373
  state:updateState,
348
374
  upToDate,
@@ -357,6 +383,7 @@ export async function main(argv = process.argv.slice(2)) {
357
383
  serviceInstalled:service.installed,
358
384
  runtimeEntry:cfg.runtime.entry,
359
385
  npmManaged:true,
386
+ installations:installationAudit,
360
387
  };
361
388
  }
362
389
  console.log(JSON.stringify(report, null, 2));