@remcp/runtime 0.2.129 → 0.2.131
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 +11 -0
- package/README.md +4 -1
- package/package.json +1 -1
- package/src/extended/catalog.mjs +9 -9
- package/src/extended/common.mjs +9 -0
- package/src/extended/desktop-linux.mjs +3 -2
- package/src/extended/desktop.mjs +15 -12
- package/src/extended/diagnostics.mjs +49 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.131 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- `network action=summary` is now bounded for Docker-heavy hosts: non-container interfaces are prioritized, at most eight interfaces and four addresses per interface are returned, and count/truncation metadata shows what was omitted.
|
|
6
|
+
- `network action=interfaces` remains the complete adapter/address inventory. The MCP schema and tool names remain compatible; stable count/truncation fields are now declared for chaining.
|
|
7
|
+
|
|
8
|
+
## 0.2.130 — 2026-10-02
|
|
9
|
+
|
|
10
|
+
- `computer_snapshot` now skips desktop-only probes on headless Linux and returns stable empty/unavailable desktop fields without false error noise. The shared `desktopSessionAvailable()` helper keeps this behavior aligned with live capability discovery.
|
|
11
|
+
- Linux `screenshot_region` advertising now uses the same backend resolver as execution: `grim` is Wayland-only, portal capture requires a Wayland portal candidate plus `ffmpeg`, GNOME capture requires `gnome-screenshot` + `ffmpeg`, and X11 uses ImageMagick `import`.
|
|
12
|
+
- Wayland + `ffmpeg` alone no longer exposes a screenshot tool that can only fail at runtime. Tool names and MCP schemas remain unchanged.
|
|
13
|
+
|
|
3
14
|
## 0.2.129 — 2026-10-02
|
|
4
15
|
|
|
5
16
|
- Linux Desktop capability discovery now requires an active `DISPLAY` or `WAYLAND_DISPLAY`, preventing headless hosts from advertising desktop actions just because helper binaries are installed.
|
package/README.md
CHANGED
|
@@ -80,7 +80,10 @@ agent forwards the refreshed tool names to the hosted relay. Browser/CDP tools a
|
|
|
80
80
|
example: they are enabled only while the runtime can use a supported loopback browser endpoint. On Linux,
|
|
81
81
|
Desktop capabilities additionally require an active graphical session (`DISPLAY` or `WAYLAND_DISPLAY`):
|
|
82
82
|
headless hosts do not advertise window/UI/input/clipboard/display/screenshot/launch/open/reveal/notification
|
|
83
|
-
operations merely because desktop helper binaries happen to be installed.
|
|
83
|
+
operations merely because desktop helper binaries happen to be installed. The universal `computer_snapshot`
|
|
84
|
+
remains available on those hosts but skips unavailable desktop probes instead of reporting synthetic backend
|
|
85
|
+
errors. On Linux, `screenshot_region` is advertised only when the current session has a real matching capture
|
|
86
|
+
backend (Wayland grim/GNOME/portal path or X11 ImageMagick), not merely because `ffmpeg` is installed.
|
|
84
87
|
|
|
85
88
|
The generated hosted reference covers the current **10 hosted production definitions**, all model-visible
|
|
86
89
|
and routing to the 83 granular runtime operations described above; the optional widget-enabled surface is 15.
|
package/package.json
CHANGED
package/src/extended/catalog.mjs
CHANGED
|
@@ -7,8 +7,9 @@ const JSON_OUTPUT_SCHEMA = {
|
|
|
7
7
|
import { browserControlAvailable, browserHandlers, browserRemoteEnabled } from './browser.mjs';
|
|
8
8
|
import { isWaylandSession } from '../screenshot-portal.mjs';
|
|
9
9
|
import { waylandPortalCandidate } from '../wayland-remote-desktop.mjs';
|
|
10
|
-
import { commandExists, runFile } from './common.mjs';
|
|
10
|
+
import { commandExists, desktopSessionAvailable, runFile } from './common.mjs';
|
|
11
11
|
import { desktopHandlers } from './desktop.mjs';
|
|
12
|
+
import { regionScreenshotBackends } from './desktop-linux.mjs';
|
|
12
13
|
import { diagnosticHandlers, gnomeScreencastSupported, recordScreenAvailable } from './diagnostics.mjs';
|
|
13
14
|
import { documentHandlers } from './documents.mjs';
|
|
14
15
|
|
|
@@ -57,7 +58,7 @@ const AI_TOOL_DESCRIPTIONS = Object.freeze({
|
|
|
57
58
|
browser_evaluate: 'Use this as the powerful browser escape hatch only when browser_snapshot/find/action/wait cannot express the required page operation or inspection. JavaScript executes in page context and page content is untrusted.',
|
|
58
59
|
service: 'Use this for operating-system service inventory/status/start/stop/restart. Prefer status before a mutation when service state matters; use start_process for ordinary commands that are not service-manager operations.',
|
|
59
60
|
event_log: 'Use this to read bounded recent OS logs (Windows Event Log, macOS unified log or systemd journal) when diagnosing a service/app/system problem. Prefer it over an unbounded shell log dump.',
|
|
60
|
-
network: 'Use this for
|
|
61
|
+
network: 'Use this for a compact network summary, full interface inventory, DNS/routes/listeners inspection or a bounded TCP connectivity test. Prefer action=summary for orientation and action=interfaces only when the complete adapter/address inventory is required; prefer this tool over shell ip/netstat/nslookup commands for routine diagnostics.',
|
|
61
62
|
installed_apps: 'Use this to answer what software/packages are installed and their versions. Prefer a filter when looking for one product instead of returning the full inventory.',
|
|
62
63
|
environment: 'Use this for PATH, shell, Node/runtime environment paths, architecture and optionally sanitized environment variables. Use get_system_info for host health such as CPU/memory/disk/uptime, and get_runtime_info for ReMCP policy, roots and limits. Secret-looking values remain masked.',
|
|
63
64
|
audio: 'Use this to read or change the default output volume/mute state. Use action=status for inspection; set_volume requires a 0–100 value.',
|
|
@@ -210,7 +211,7 @@ const OUTPUT_FIELDS = Object.freeze({
|
|
|
210
211
|
browser_evaluate: { target_id:outputString(), value:{} },
|
|
211
212
|
service: { data:outputArray(outputObject('Service record.')), action:outputString(), name:outputString(), text:outputString() },
|
|
212
213
|
event_log: { text:outputString('Bounded log output.'), data:outputArray(outputObject()) },
|
|
213
|
-
network: { hostname:outputString(), interfaces:outputObject(), dns:outputArray(outputString()), servers:outputArray(outputString()), host:outputString(), port:outputNumber(), ok:outputBoolean(), latency_ms:outputNumber(), data:outputArray(outputObject()) },
|
|
214
|
+
network: { hostname:outputString(), interfaces:outputObject(), dns:outputArray(outputString()), servers:outputArray(outputString()), host:outputString(), port:outputNumber(), ok:outputBoolean(), latency_ms:outputNumber(), interface_count:outputNumber(), interface_count_returned:outputNumber(), address_count:outputNumber(), address_count_returned:outputNumber(), interfaces_truncated:outputBoolean(), addresses_truncated:outputBoolean(), data:outputArray(outputObject()) },
|
|
214
215
|
installed_apps: { data:outputArray(outputObject('Installed app/package with name/version/path where available.')) },
|
|
215
216
|
environment: { platform:outputString(), arch:outputString(), hostname:outputString(), release:outputString(), shell:outputString(), path:outputString(), node:outputString(), env:outputObject() },
|
|
216
217
|
audio: { volume:outputNumber(), muted:outputBoolean(), action:outputString(), text:outputString() },
|
|
@@ -561,7 +562,7 @@ export const extendedToolDefinitions = [
|
|
|
561
562
|
|
|
562
563
|
define('service', 'Service control', 'List, inspect, start, stop, or restart operating-system services using Service Control Manager, launchctl, or systemd. Mutating actions pass through ReMCP command policy.', o({ action:e(['list','status','start','stop','restart']), name:s('Service/unit/launchd label.'), scope:e(['user','system']) }, ['action']), mutating, diagnosticHandlers.service, ['services']),
|
|
563
564
|
define('event_log', 'Event logs', 'Read recent Windows Event Log, macOS unified log, or systemd journal entries with bounded time and result limits.', o({ log:s('Windows log name.'), since:s('Time window such as 30s, 10m, 2h, or 1d.'), filter:s(), query:s(), limit:n() }), readOnlyLive, diagnosticHandlers.event_log, ['logs']),
|
|
564
|
-
define('network', 'Network diagnostics', 'Inspect
|
|
565
|
+
define('network', 'Network diagnostics', 'Inspect a bounded network summary, full interface inventory, DNS servers, routes and listening sockets, or perform a bounded TCP connectivity test to a host and port. Use action=interfaces when the complete adapter/address inventory is needed.', o({ action:e(['summary','interfaces','dns','routes','listeners','test']), host:s(), port:n(), timeout_ms:n() }), readOnlyOpen, diagnosticHandlers.network),
|
|
565
566
|
define('installed_apps', 'Installed applications', 'Inventory installed applications or packages with versions and publishers where available using native package/application inventories.', o({ filter:s('Name substring.'), limit:n('Maximum results; default 1000.') }), readOnly, diagnosticHandlers.installed_apps, ['apps']),
|
|
566
567
|
define('environment', 'Runtime environment', 'Report platform, architecture, Node version, working/home/temp paths, shell, PATH, and optionally environment variables. Environment variables are opt-in and secret-looking or credential-bearing values are masked.', o({ include_env:b('Include sanitized environment variables; default false.') }), readOnly, diagnosticHandlers.environment),
|
|
567
568
|
define('audio', 'Audio control', 'Read or change default output volume or mute state using native operating-system audio controls when available.', o({ action:e(['status','set_volume','mute','unmute']), volume:n('0..100 for set_volume.') }, ['action']), mutatingNonDestructive, diagnosticHandlers.audio, ['audio']),
|
|
@@ -604,9 +605,7 @@ export function desktopShellCapabilitySnapshot({
|
|
|
604
605
|
commandExistsFn = commandExists,
|
|
605
606
|
} = {}) {
|
|
606
607
|
const nativeDesktop = platform === 'win32' || platform === 'darwin';
|
|
607
|
-
const
|
|
608
|
-
&& Boolean(String(env?.DISPLAY || '').trim() || String(env?.WAYLAND_DISPLAY || '').trim());
|
|
609
|
-
const desktopSession = nativeDesktop || linuxDesktop;
|
|
608
|
+
const desktopSession = desktopSessionAvailable({ platform, env });
|
|
610
609
|
return {
|
|
611
610
|
desktop_session:desktopSession,
|
|
612
611
|
desktop_open:desktopSession && (nativeDesktop || commandExistsFn('xdg-open')),
|
|
@@ -646,7 +645,8 @@ export async function capabilitySnapshot() {
|
|
|
646
645
|
const pyAtSpi = linux && desktopSession ? await linuxHasPyAtSpi() : false;
|
|
647
646
|
const wayland = linux && desktopSession && isWaylandSession();
|
|
648
647
|
const gnomeRecord = wayland ? await gnomeScreencastSupported() : false;
|
|
649
|
-
const
|
|
648
|
+
const portalCandidate = wayland && waylandPortalCandidate();
|
|
649
|
+
const portalInput = portalCandidate;
|
|
650
650
|
const x11Input = linux && desktopSession && !wayland && (commandExists('wdotool') || commandExists('xdotool'));
|
|
651
651
|
const browserCdp = browserRemoteEnabled() && await browserControlAvailable(undefined, 500);
|
|
652
652
|
const documentCapabilities = documentCapabilitySnapshot();
|
|
@@ -659,7 +659,7 @@ export async function capabilitySnapshot() {
|
|
|
659
659
|
drag: win || mac || (desktopSession && (portalInput || x11Input)),
|
|
660
660
|
clipboard: win || mac || (desktopSession && (commandExists('wl-paste') || commandExists('xclip') || commandExists('xsel'))),
|
|
661
661
|
displays: win || mac || (desktopSession && ((wayland && linux) || commandExists('wlr-randr') || commandExists('xrandr'))),
|
|
662
|
-
screen_region: win || mac || (desktopSession && (
|
|
662
|
+
screen_region: win || mac || (desktopSession && regionScreenshotBackends({ wayland, portalCandidate }).length > 0),
|
|
663
663
|
notifications: win || mac || (desktopSession && commandExists('notify-send')),
|
|
664
664
|
browser_cdp: browserCdp,
|
|
665
665
|
browser_evaluate: browserCdp && (process.env.NODE_ENV !== 'production' || ['1', 'true', 'yes', 'on'].includes(String(process.env.REMCP_BROWSER_ALLOW_EVALUATE || '').toLowerCase())),
|
package/src/extended/common.mjs
CHANGED
|
@@ -21,6 +21,15 @@ export function optionalString(value) {
|
|
|
21
21
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export function desktopSessionAvailable({
|
|
25
|
+
platform = process.platform,
|
|
26
|
+
env = process.env,
|
|
27
|
+
} = {}) {
|
|
28
|
+
if (platform === 'win32' || platform === 'darwin') return true;
|
|
29
|
+
if (platform !== 'linux') return false;
|
|
30
|
+
return Boolean(String(env?.DISPLAY || '').trim() || String(env?.WAYLAND_DISPLAY || '').trim());
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
export function requireEnum(value, field, allowed) {
|
|
25
34
|
const normalized = String(value ?? '');
|
|
26
35
|
if (!allowed.includes(normalized)) fail(`${field} must be one of: ${allowed.join(', ')}`);
|
|
@@ -1487,12 +1487,13 @@ export function regionScreenshotBackends({
|
|
|
1487
1487
|
gnomeScreenshot = commandExists('gnome-screenshot'),
|
|
1488
1488
|
ffmpeg = commandExists('ffmpeg'),
|
|
1489
1489
|
imagemagick = commandExists('import'),
|
|
1490
|
+
portalCandidate = waylandPortalCandidate(),
|
|
1490
1491
|
} = {}) {
|
|
1491
1492
|
const backends = [];
|
|
1492
|
-
if (grim) backends.push('grim');
|
|
1493
|
+
if (wayland && grim) backends.push('grim');
|
|
1493
1494
|
if (wayland) {
|
|
1494
1495
|
if (gnomeScreenshot && ffmpeg) backends.push('gnome-screenshot');
|
|
1495
|
-
if (ffmpeg) backends.push('portal');
|
|
1496
|
+
if (ffmpeg && portalCandidate) backends.push('portal');
|
|
1496
1497
|
} else if (imagemagick) {
|
|
1497
1498
|
backends.push('imagemagick');
|
|
1498
1499
|
}
|
package/src/extended/desktop.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { fileToolHandlers } from '../tools/files.mjs';
|
|
|
4
4
|
import { liveConfig, runtimeConfig } from '../config.mjs';
|
|
5
5
|
import { assertAllowedCommand } from '../policy.mjs';
|
|
6
6
|
import { multi, resolveSafePath, text } from '../util.mjs';
|
|
7
|
-
import { clamp, jsonResult, optionalString, requireEnum } from './common.mjs';
|
|
7
|
+
import { clamp, desktopSessionAvailable, jsonResult, optionalString, requireEnum } from './common.mjs';
|
|
8
8
|
import * as linux from './desktop-linux.mjs';
|
|
9
9
|
import * as macos from './desktop-macos.mjs';
|
|
10
10
|
import * as windows from './desktop-windows.mjs';
|
|
@@ -1026,12 +1026,14 @@ function computerScreenshotTask(args = {}) {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
1028
1028
|
export async function computerSnapshot(args = {}) {
|
|
1029
|
-
const
|
|
1029
|
+
const desktopAvailable = desktopSessionAvailable();
|
|
1030
|
+
const screenshotRequested = args.include_screenshot !== false || args.include_ocr === true;
|
|
1031
|
+
const captureScreenshot = desktopAvailable && screenshotRequested;
|
|
1030
1032
|
const includeUi = args.include_ui !== false;
|
|
1031
1033
|
const [windowsResult, displaysResult, uiResult, clipboardResult, cursorResult, shotResult] = await Promise.allSettled([
|
|
1032
|
-
listWindows(),
|
|
1033
|
-
displayInventory(),
|
|
1034
|
-
includeUi
|
|
1034
|
+
desktopAvailable ? listWindows() : Promise.resolve(null),
|
|
1035
|
+
desktopAvailable ? displayInventory() : Promise.resolve(null),
|
|
1036
|
+
includeUi && desktopAvailable
|
|
1035
1037
|
? uiSnapshot({
|
|
1036
1038
|
max_nodes:args.max_ui_nodes || 800,
|
|
1037
1039
|
max_depth:args.max_ui_depth || 12,
|
|
@@ -1040,8 +1042,8 @@ export async function computerSnapshot(args = {}) {
|
|
|
1040
1042
|
...(args.ui_browser_dom === true ? { browser_dom:true } : {}),
|
|
1041
1043
|
})
|
|
1042
1044
|
: Promise.resolve(null),
|
|
1043
|
-
clipboard({ action:'read' }),
|
|
1044
|
-
cursorPosition(),
|
|
1045
|
+
desktopAvailable ? clipboard({ action:'read' }) : Promise.resolve(null),
|
|
1046
|
+
desktopAvailable ? cursorPosition() : Promise.resolve(null),
|
|
1045
1047
|
captureScreenshot ? computerScreenshotTask(args) : Promise.resolve(null),
|
|
1046
1048
|
]);
|
|
1047
1049
|
const parse = (result, fallback = null) => {
|
|
@@ -1069,7 +1071,7 @@ export async function computerSnapshot(args = {}) {
|
|
|
1069
1071
|
if (includeUi && parsedUi !== ui && parsedUi != null) {
|
|
1070
1072
|
shapeErrors.push({ source:'ui', message:'accessibility snapshot returned an invalid result shape' });
|
|
1071
1073
|
}
|
|
1072
|
-
const clipboardOk = clipboardResult.status === 'fulfilled' && clipboardResult.value?.isError !== true;
|
|
1074
|
+
const clipboardOk = desktopAvailable && clipboardResult.status === 'fulfilled' && clipboardResult.value?.isError !== true && Boolean(clipboardResult.value);
|
|
1073
1075
|
const clipText = clipboardOk ? clipboardResult.value.content?.[0]?.text || '' : '';
|
|
1074
1076
|
const uiNodes = Array.isArray(ui?.nodes) ? ui.nodes : Array.isArray(ui) ? ui : [];
|
|
1075
1077
|
const focusedNodes = uiNodes.filter(node => node.focused === true);
|
|
@@ -1152,8 +1154,9 @@ export async function computerSnapshot(args = {}) {
|
|
|
1152
1154
|
}
|
|
1153
1155
|
}
|
|
1154
1156
|
|
|
1155
|
-
const
|
|
1156
|
-
|| (args.include_ocr !== false &&
|
|
1157
|
+
const ocrRequested = args.include_ocr === true
|
|
1158
|
+
|| (desktopAvailable && args.include_ocr !== false && screenshotRequested && uiNodes.length === 0 && !browser);
|
|
1159
|
+
const shouldOcr = desktopAvailable && ocrRequested;
|
|
1157
1160
|
let ocr = null;
|
|
1158
1161
|
let ocrCaptureBounds = null;
|
|
1159
1162
|
let ocrShotResult = shotResult;
|
|
@@ -1193,14 +1196,14 @@ export async function computerSnapshot(args = {}) {
|
|
|
1193
1196
|
...(browser ? { browser } : {}),
|
|
1194
1197
|
...(ocr ? { ocr } : {}),
|
|
1195
1198
|
fallback_chain: {
|
|
1196
|
-
accessibility: { requested:includeUi, available:includeUi && uiResult.status === 'fulfilled', nodes:uiNodes.length },
|
|
1199
|
+
accessibility: { requested:includeUi, available:includeUi && desktopAvailable && uiResult.status === 'fulfilled' && Boolean(uiResult.value), nodes:uiNodes.length },
|
|
1197
1200
|
native_browser_dom: {
|
|
1198
1201
|
requested:includeUi && args.ui_browser_dom === true,
|
|
1199
1202
|
available:Boolean(includeUi && ui?.browser_dom?.available),
|
|
1200
1203
|
provider:ui?.browser_dom?.provider || null,
|
|
1201
1204
|
},
|
|
1202
1205
|
browser_dom: { requested:browserRequested, available:Boolean(browser) },
|
|
1203
|
-
ocr: { requested:
|
|
1206
|
+
ocr: { requested:ocrRequested, available:Boolean(ocr?.available), backend:ocr?.backend || null },
|
|
1204
1207
|
vision_screenshot: { available:shotResult.status === 'fulfilled' && Boolean(shotResult.value?.content?.some(part => part.type === 'image')) },
|
|
1205
1208
|
},
|
|
1206
1209
|
visual: {
|
|
@@ -133,11 +133,59 @@ export function linuxListenerBackend({ commandExistsFn = commandExists } = {}) {
|
|
|
133
133
|
return null;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
const CONTAINER_INTERFACE_PATTERN = /^(?:veth|docker\d*$|br-[0-9a-f]{6,}$)/i;
|
|
137
|
+
|
|
138
|
+
export function compactNetworkSummary({
|
|
139
|
+
hostname = os.hostname(),
|
|
140
|
+
interfaces = os.networkInterfaces(),
|
|
141
|
+
dnsServers = dns.getServers(),
|
|
142
|
+
maxInterfaces = 8,
|
|
143
|
+
maxAddressesPerInterface = 4,
|
|
144
|
+
} = {}) {
|
|
145
|
+
const entries = Object.entries(interfaces || {}).map(([name, rows], index) => {
|
|
146
|
+
const normalizedRows = Array.isArray(rows) ? rows.filter(Boolean) : [];
|
|
147
|
+
const hasExternal = normalizedRows.some(row => row?.internal !== true);
|
|
148
|
+
const rank = hasExternal
|
|
149
|
+
? (CONTAINER_INTERFACE_PATTERN.test(name) ? 1 : 0)
|
|
150
|
+
: 2;
|
|
151
|
+
return { name, rows:normalizedRows, index, rank };
|
|
152
|
+
});
|
|
153
|
+
entries.sort((left, right) => left.rank - right.rank || left.index - right.index);
|
|
154
|
+
|
|
155
|
+
const interfaceLimit = clamp(maxInterfaces, 8, 1, 64);
|
|
156
|
+
const addressLimit = clamp(maxAddressesPerInterface, 4, 1, 16);
|
|
157
|
+
const selected = entries.slice(0, interfaceLimit);
|
|
158
|
+
const compact = {};
|
|
159
|
+
let addressCount = 0;
|
|
160
|
+
let addressCountReturned = 0;
|
|
161
|
+
let addressesTruncated = false;
|
|
162
|
+
|
|
163
|
+
for (const entry of entries) addressCount += entry.rows.length;
|
|
164
|
+
for (const entry of selected) {
|
|
165
|
+
const rows = entry.rows.slice(0, addressLimit);
|
|
166
|
+
compact[entry.name] = rows;
|
|
167
|
+
addressCountReturned += rows.length;
|
|
168
|
+
if (entry.rows.length > rows.length) addressesTruncated = true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
hostname,
|
|
173
|
+
dns:Array.isArray(dnsServers) ? dnsServers : [],
|
|
174
|
+
interfaces:compact,
|
|
175
|
+
interface_count:entries.length,
|
|
176
|
+
interface_count_returned:selected.length,
|
|
177
|
+
address_count:addressCount,
|
|
178
|
+
address_count_returned:addressCountReturned,
|
|
179
|
+
interfaces_truncated:entries.length > selected.length,
|
|
180
|
+
addresses_truncated:addressesTruncated,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
136
184
|
export async function networkTool(args) {
|
|
137
185
|
const action = requireEnum(args.action || 'summary', 'action', ['summary','interfaces','dns','routes','listeners','test']);
|
|
138
186
|
if (action === 'interfaces') return jsonResult(os.networkInterfaces());
|
|
139
187
|
if (action === 'dns') return jsonResult({ servers:dns.getServers(), hostname:os.hostname() });
|
|
140
|
-
if (action === 'summary') return jsonResult(
|
|
188
|
+
if (action === 'summary') return jsonResult(compactNetworkSummary());
|
|
141
189
|
if (action === 'test') {
|
|
142
190
|
const host = optionalString(args.host); if (!host) throw new Error('host is required for action=test');
|
|
143
191
|
return jsonResult(await connectivityTest(host, clamp(args.port, 443, 1, 65535), clamp(args.timeout_ms, 3000, 100, 30_000)));
|