@remcp/runtime 0.2.135 → 0.2.136
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 +7 -0
- package/package.json +1 -1
- package/src/extended/catalog.mjs +13 -1
- package/src/extended/diagnostics.mjs +71 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.136 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- `installed_apps` now normalizes Windows registry, macOS `system_profiler`, `dpkg-query`, and `rpm` inventories into one structured `data[]` contract with stable app name/version/publisher/path fields.
|
|
6
|
+
- Stable top-level `backend`, `count`, `returned`, and `truncated` metadata make result limits explicit and remove platform-specific text parsing from callers.
|
|
7
|
+
- Name filtering and de-duplication are shared across platforms. Windows no longer interpolates the filter into PowerShell.
|
|
8
|
+
- Public release CI exercises the live native inventory on Linux, macOS, and Windows. Tool names remain unchanged at 83; the output-schema digest changes intentionally.
|
|
9
|
+
|
|
3
10
|
## 0.2.135 — 2026-10-02
|
|
4
11
|
|
|
5
12
|
- Linux `audio` status is normalized across `wpctl`, `pactl`, and `amixer` into structured `volume`, `muted`, `action`, and `backend` fields; mutating audio actions re-read and return the verified state.
|
package/package.json
CHANGED
package/src/extended/catalog.mjs
CHANGED
|
@@ -212,7 +212,19 @@ const OUTPUT_FIELDS = Object.freeze({
|
|
|
212
212
|
service: { data:outputArray(outputObject('Service record.')), action:outputString(), name:outputString(), text:outputString() },
|
|
213
213
|
event_log: { text:outputString('Bounded log output.'), data:outputArray(outputObject()) },
|
|
214
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()) },
|
|
215
|
-
installed_apps: {
|
|
215
|
+
installed_apps: {
|
|
216
|
+
data:outputArray(outputRecord({
|
|
217
|
+
name:outputString('Installed application/package name.'),
|
|
218
|
+
version:{ type:['string','null'], description:'Installed version when available.' },
|
|
219
|
+
publisher:{ type:['string','null'], description:'Publisher/maintainer/vendor when available.' },
|
|
220
|
+
path:{ type:['string','null'], description:'Installation path when available.' },
|
|
221
|
+
signed_by:outputString('Optional macOS signing identity summary.'),
|
|
222
|
+
}, 'Normalized installed app/package record.')),
|
|
223
|
+
backend:outputString('Native inventory backend used on this platform.'),
|
|
224
|
+
count:outputNumber('Total matching records before the requested limit.'),
|
|
225
|
+
returned:outputNumber('Number of records returned in data.'),
|
|
226
|
+
truncated:outputBoolean('True when count is larger than returned because of the requested limit.'),
|
|
227
|
+
},
|
|
216
228
|
environment: { platform:outputString(), arch:outputString(), hostname:outputString(), release:outputString(), shell:outputString(), path:outputString(), node:outputString(), env:outputObject() },
|
|
217
229
|
audio: { volume:outputNumber(), muted:outputBoolean(), action:outputString(), backend:outputString(), text:outputString() },
|
|
218
230
|
power_action: { text:outputString(), action:outputString() },
|
|
@@ -218,26 +218,90 @@ export async function networkTool(args) {
|
|
|
218
218
|
unavailable('Listener inventory', 'ss or netstat is required');
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
function installedAppString(value) {
|
|
222
|
+
const normalized = value == null ? '' : String(value).trim();
|
|
223
|
+
return normalized || null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function normalizeInstalledApp(row = {}) {
|
|
227
|
+
const name = installedAppString(row.name ?? row.DisplayName ?? row._name);
|
|
228
|
+
if (!name) return null;
|
|
229
|
+
const signedBy = installedAppString(row.signed_by);
|
|
230
|
+
return {
|
|
231
|
+
name,
|
|
232
|
+
version:installedAppString(row.version ?? row.DisplayVersion),
|
|
233
|
+
publisher:installedAppString(row.publisher ?? row.Publisher),
|
|
234
|
+
path:installedAppString(row.path ?? row.InstallLocation),
|
|
235
|
+
...(signedBy ? { signed_by:signedBy } : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function parseInstalledAppsTsv(output) {
|
|
240
|
+
return String(output || '').split(/\r?\n/).filter(Boolean).map(line => {
|
|
241
|
+
const [name = '', version = '', publisher = '', pathValue = ''] = line.split('\t');
|
|
242
|
+
return normalizeInstalledApp({ name, version, publisher, path:pathValue });
|
|
243
|
+
}).filter(Boolean);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function installedAppsPayload(rows, { backend, filter = null, limit = 1000 } = {}) {
|
|
247
|
+
const wanted = optionalString(filter)?.toLowerCase() || null;
|
|
248
|
+
const boundedLimit = clamp(limit, 1000, 1, 10_000);
|
|
249
|
+
const seen = new Set();
|
|
250
|
+
const normalized = [];
|
|
251
|
+
for (const raw of Array.isArray(rows) ? rows : (rows ? [rows] : [])) {
|
|
252
|
+
const app = normalizeInstalledApp(raw);
|
|
253
|
+
if (!app) continue;
|
|
254
|
+
if (wanted && !app.name.toLowerCase().includes(wanted)) continue;
|
|
255
|
+
const key = [app.name.toLowerCase(), app.version || '', app.path || ''].join('\u0000');
|
|
256
|
+
if (seen.has(key)) continue;
|
|
257
|
+
seen.add(key);
|
|
258
|
+
normalized.push(app);
|
|
259
|
+
}
|
|
260
|
+
normalized.sort((left, right) => left.name.localeCompare(right.name, undefined, { sensitivity:'base' })
|
|
261
|
+
|| String(left.version || '').localeCompare(String(right.version || ''), undefined, { sensitivity:'base' }));
|
|
262
|
+
const data = normalized.slice(0, boundedLimit);
|
|
263
|
+
return {
|
|
264
|
+
data,
|
|
265
|
+
backend:String(backend || 'unknown'),
|
|
266
|
+
count:normalized.length,
|
|
267
|
+
returned:data.length,
|
|
268
|
+
truncated:normalized.length > data.length,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function windowsInstalledAppsPowerShell() {
|
|
273
|
+
return [
|
|
274
|
+
"$ErrorActionPreference='Stop'",
|
|
275
|
+
"$paths=@('HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*')",
|
|
276
|
+
"$items=foreach($path in $paths){Get-ItemProperty -Path $path -ErrorAction SilentlyContinue}",
|
|
277
|
+
"$items|Where-Object{$_.DisplayName}|ForEach-Object{[PSCustomObject]@{name=[string]$_.DisplayName;version=[string]$_.DisplayVersion;publisher=[string]$_.Publisher;path=[string]$_.InstallLocation}}|ConvertTo-Json -Compress",
|
|
278
|
+
].join(';');
|
|
279
|
+
}
|
|
280
|
+
|
|
221
281
|
export async function installedApps(args) {
|
|
222
282
|
const limit = clamp(args.limit, 1000, 1, 10_000);
|
|
223
283
|
const filter = optionalString(args.filter);
|
|
224
284
|
if (process.platform === 'win32') {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
285
|
+
const { stdout } = await runPowerShell(windowsInstalledAppsPowerShell(), { label:'installed apps', timeout:30_000, maxBuffer:32*1024*1024 });
|
|
286
|
+
let parsed;
|
|
287
|
+
try {
|
|
288
|
+
parsed = JSON.parse(stdout.trim() || '[]');
|
|
289
|
+
} catch {
|
|
290
|
+
throw new Error('Windows installed-app inventory returned invalid JSON');
|
|
291
|
+
}
|
|
292
|
+
return jsonResult(installedAppsPayload(parsed, { backend:'windows-registry', filter, limit }));
|
|
228
293
|
}
|
|
229
294
|
if (process.platform === 'darwin') {
|
|
230
295
|
const parsed = JSON.parse((await runFile('/usr/sbin/system_profiler', ['SPApplicationsDataType','-json'], { label:'installed apps', timeout:60_000, maxBuffer:64*1024*1024 })).stdout);
|
|
231
|
-
|
|
232
|
-
return jsonResult(apps.map(app => ({ name:app._name, version:app.version || null, path:app.path || null, signed_by:app.signed_by || null })));
|
|
296
|
+
return jsonResult(installedAppsPayload(parsed.SPApplicationsDataType || [], { backend:'system_profiler', filter, limit }));
|
|
233
297
|
}
|
|
234
298
|
if (commandExists('dpkg-query')) {
|
|
235
299
|
const { stdout } = await runFile('dpkg-query', ['-W','-f=${binary:Package}\t${Version}\t${Maintainer}\n'], { label:'installed apps', maxBuffer:32*1024*1024 });
|
|
236
|
-
return
|
|
300
|
+
return jsonResult(installedAppsPayload(parseInstalledAppsTsv(stdout), { backend:'dpkg-query', filter, limit }));
|
|
237
301
|
}
|
|
238
302
|
if (commandExists('rpm')) {
|
|
239
303
|
const { stdout } = await runFile('rpm', ['-qa','--qf','%{NAME}\t%{VERSION}-%{RELEASE}\t%{VENDOR}\n'], { label:'installed apps', maxBuffer:32*1024*1024 });
|
|
240
|
-
return
|
|
304
|
+
return jsonResult(installedAppsPayload(parseInstalledAppsTsv(stdout), { backend:'rpm', filter, limit }));
|
|
241
305
|
}
|
|
242
306
|
unavailable('Installed application inventory', 'dpkg-query or rpm is required');
|
|
243
307
|
}
|