@remcp/runtime 0.2.127 → 0.2.128

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.128 — 2026-10-02
4
+
5
+ - Linux `power_action` is advertised only when both `loginctl` and `systemctl` exist, matching the full lock/sleep/restart/shutdown schema instead of exposing a partially unusable tool.
6
+ - Linux `network listeners` keeps `ss -lntup` as the preferred backend and changes the `netstat` fallback from `-an` to `-lntu`, excluding established/non-listening connections.
7
+ - Tool names and schemas remain unchanged; dynamic capability updates continue to use `tools/list_changed`.
8
+
3
9
  ## 0.2.127 — 2026-10-01
4
10
 
5
11
  - macOS `network listeners` now returns only TCP `LISTEN` rows from the native network table instead of all active connections.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.127",
3
+ "version": "0.2.128",
4
4
  "description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -598,6 +598,15 @@ async function linuxHasPyAtSpi() {
598
598
  return pyAtSpiCache.value;
599
599
  }
600
600
 
601
+ export function powerCapabilitySnapshot({
602
+ platform = process.platform,
603
+ commandExistsFn = commandExists,
604
+ } = {}) {
605
+ if (platform === 'win32' || platform === 'darwin') return true;
606
+ if (platform !== 'linux') return false;
607
+ return commandExistsFn('loginctl') && commandExistsFn('systemctl');
608
+ }
609
+
601
610
  export function documentCapabilitySnapshot({
602
611
  platform = process.platform,
603
612
  commandExistsFn = commandExists,
@@ -641,7 +650,7 @@ export async function capabilitySnapshot() {
641
650
  logs: win || mac || commandExists('journalctl'),
642
651
  apps: win || mac || commandExists('dpkg-query') || commandExists('rpm'),
643
652
  audio: win || mac || commandExists('wpctl') || commandExists('pactl') || commandExists('amixer'),
644
- power: win || mac || commandExists('systemctl') || commandExists('loginctl'),
653
+ power: powerCapabilitySnapshot(),
645
654
  record: recordScreenAvailable({ platform:process.platform, wayland, gnomeSupported:gnomeRecord }),
646
655
  ...documentCapabilities,
647
656
  };
@@ -127,6 +127,12 @@ async function connectivityTest(host, port, timeoutMs) {
127
127
  });
128
128
  }
129
129
 
130
+ export function linuxListenerBackend({ commandExistsFn = commandExists } = {}) {
131
+ if (commandExistsFn('ss')) return { command:'ss', args:['-lntup'] };
132
+ if (commandExistsFn('netstat')) return { command:'netstat', args:['-lntu'] };
133
+ return null;
134
+ }
135
+
130
136
  export async function networkTool(args) {
131
137
  const action = requireEnum(args.action || 'summary', 'action', ['summary','interfaces','dns','routes','listeners','test']);
132
138
  if (action === 'interfaces') return jsonResult(os.networkInterfaces());
@@ -154,8 +160,8 @@ export async function networkTool(args) {
154
160
  if (commandExists('route')) return text((await runFile('route', ['-n'], { label:'routes' })).stdout);
155
161
  unavailable('Route inventory', 'ip or route is required');
156
162
  }
157
- if (commandExists('ss')) return text((await runFile('ss', ['-lntup'], { label:'listeners', allowFailure:true })).stdout);
158
- if (commandExists('netstat')) return text((await runFile('netstat', ['-an'], { label:'listeners' })).stdout);
163
+ const listenerBackend = linuxListenerBackend();
164
+ if (listenerBackend) return text((await runFile(listenerBackend.command, listenerBackend.args, { label:'listeners', allowFailure:true })).stdout);
159
165
  unavailable('Listener inventory', 'ss or netstat is required');
160
166
  }
161
167