@remcp/remcp 0.2.29 → 0.2.31

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 (3) hide show
  1. package/README.md +47 -0
  2. package/package.json +1 -1
  3. package/src/cli.mjs +32 -1
package/README.md CHANGED
@@ -27,12 +27,33 @@ remcp install Install or repair the user service
27
27
  remcp uninstall Remove the user service
28
28
  remcp uninstall --purge Remove the service and the global packages
29
29
  remcp telemetry [status|on|off]
30
+ remcp godmode [status|on|off]
30
31
  remcp --version
31
32
  ```
32
33
 
33
34
  `remcp status` reports the runtime the device would install, whether the agent service is running,
34
35
  and the current usage-metrics state, so a support request can be answered with one paste.
35
36
 
37
+ ## Unrestricted mode
38
+
39
+ `remcp godmode on` removes the runtime's own safety rails on **this** computer: file access is no
40
+ longer confined to the allowed roots, the configured command blocklist is ignored, and the
41
+ catastrophic-command guardrail is set to allow. `remcp godmode off` puts them back; `remcp godmode
42
+ status` says which state you are in and where it comes from.
43
+
44
+ Two things it deliberately does not do:
45
+
46
+ - **It is not reachable from a model.** `set_config_value` lists the settings a model may change and
47
+ this is not one of them; only a person at the computer (this command, `REMCP_RUNTIME_UNRESTRICTED=1`,
48
+ or `unrestricted: true` in `~/.config/remcp/runtime.json`) can turn it on. That is what keeps a
49
+ prompt injection from becoming root.
50
+ - **It does not make the agent root.** Commands run as the user the agent runs as. `sudo` is no longer
51
+ blocked, but the operating system still asks for a password unless your sudoers rules say otherwise;
52
+ a non-interactive command cannot type one. Nothing in this mode grants root by itself.
53
+
54
+ While it is on, `get_runtime_info` reports `policy.unrestricted: true`, so the model can see it and
55
+ say so instead of assuming the guardrails are still there.
56
+
36
57
  ## macOS folder permissions
37
58
 
38
59
  macOS protects Desktop, Documents, Downloads and iCloud Drive. Until it is granted access, ReMCP
@@ -43,6 +64,32 @@ background service: open **System Settings → Privacy & Security → Full Disk
43
64
  binary that `remcp doctor` prints, and run `remcp start`. Folders outside those four need no new
44
65
  permission, and `remcp doctor` reports the state of each one.
45
66
 
67
+ ## Updates
68
+
69
+ The agent asks the server which versions it should run when it connects and every six hours, installs
70
+ a newer client and runtime in the background, and restarts the service so they take effect. A failed
71
+ install is retried no more often than every thirty minutes, so a broken release cannot turn into an
72
+ install loop. `remcp update` does the same immediately; `remcp auto-update off` turns the automatic
73
+ check off for a machine that must not change on its own.
74
+
75
+ ## Screenshots
76
+
77
+ `take_screenshot` returns the screen of this computer as an image, and each desktop keeps its own
78
+ gate — ReMCP names the one that refused instead of printing a generic error:
79
+
80
+ - **macOS** wants Screen Recording for the binary that runs the tools (`remcp doctor` prints its
81
+ path), then `remcp start`.
82
+ - **Windows** needs an unlocked interactive session; a locked or signed-out machine cannot be
83
+ captured.
84
+ - **Linux on Wayland** needs a capture backend: `grim` on wlroots desktops (sway, hyprland), and
85
+ `gnome-screenshot` on GNOME — GNOME refuses the shell's own screenshot API to background processes
86
+ and `grim` cannot read a GNOME session. On X11, `scrot`, ImageMagick `import` or `gnome-screenshot`
87
+ all work.
88
+ - A machine with no graphical session (a server, a container) says so: there is nothing to capture.
89
+
90
+ A screenshot larger than the inline limit is saved on the computer, and the result says where it is
91
+ and how to fetch it in chunks.
92
+
46
93
  ## What runs on your computer
47
94
 
48
95
  - the agent (`remcp start`), which holds the device credential and dials
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
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
@@ -487,7 +487,7 @@ function assertRuntimeTrust(server, flags) {
487
487
  }
488
488
 
489
489
  function printHelp() {
490
- console.log(`ReMCP ${VERSION}\n\nCommands:\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 --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`);
490
+ console.log(`ReMCP ${VERSION}\n\nCommands:\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. It is deliberately not reachable from a model or an MCP tool.`);
491
491
  }
492
492
 
493
493
  export async function main(argv = process.argv.slice(2)) {
@@ -602,6 +602,37 @@ export async function main(argv = process.argv.slice(2)) {
602
602
  throw new Error('Usage: remcp telemetry [status|on|off]');
603
603
  }
604
604
 
605
+ // The switch that lifts every guardrail: no access roots, no command blocklist, no catastrophic
606
+ // command protection. It lives here, on the machine, because a model that could turn it on would
607
+ // turn any prompt injection into root. The runtime refuses to set it through MCP
608
+ // (`set_config_value` lists what it accepts, and this is not on the list).
609
+ if (command === 'godmode' || command === 'unrestricted') {
610
+ const action = String(positional[0] || 'status').toLowerCase();
611
+ if (action === 'status') {
612
+ const runtimeFile = readJsonFile(runtimeConfigFile);
613
+ const state = runtimeFile.unrestricted === true || process.env.REMCP_RUNTIME_UNRESTRICTED === '1';
614
+ console.log(JSON.stringify({
615
+ unrestricted: state,
616
+ source: runtimeFile.unrestricted === true ? runtimeConfigFile : process.env.REMCP_RUNTIME_UNRESTRICTED === '1' ? 'REMCP_RUNTIME_UNRESTRICTED' : 'default',
617
+ meaning: state
618
+ ? 'Every path and every command is allowed. Commands run as the user the agent runs as, so sudo still needs your own sudoers rules; nothing here grants root by itself.'
619
+ : 'The runtime confines file access to its allowed roots and applies its command guardrails.',
620
+ }, null, 2));
621
+ return;
622
+ }
623
+ if (!['on', 'off', 'enable', 'disable'].includes(action)) throw new Error('Usage: remcp godmode [status|on|off]');
624
+ const enabled = action === 'on' || action === 'enable';
625
+ writeJsonFile(runtimeConfigFile, { ...readJsonFile(runtimeConfigFile), unrestricted: enabled });
626
+ const restarted = restartPersistentServiceIfInstalled();
627
+ if (enabled) {
628
+ console.error('Unrestricted mode is ON for this computer: every path and every command is allowed, including sudo. Anything the model is asked to do -- and anything a prompt injection asks it to do -- can now change this machine. Turn it off with `remcp godmode off` when you are done.');
629
+ } else {
630
+ console.log('Unrestricted mode is off: the runtime is back to its allowed roots and command guardrails.');
631
+ }
632
+ console.log(JSON.stringify({ unrestricted: enabled, configFile: runtimeConfigFile, restarted }, null, 2));
633
+ return;
634
+ }
635
+
605
636
  if (command === 'status' || command === 'doctor') {
606
637
  const cfg = loadConfig();
607
638
  const health = await fetch(`${cfg.serverUrl}/health?fresh=${Date.now()}`, { cache: 'no-store' }).then(r => r.json()).catch(error => ({ error: error.message }));