add mac software support

Signed-off-by: si458 <simonsmith5521@gmail.com>

si458 committed May 30, 2026 at 18:24 UTC 6a3f7878f1664f30a01a801f3d8473bef51ae7d5
2 files changed +108
agents/meshcore.js
+16
@@ -1729,6 +1729,12 @@ function handleServerCommand(data) {
1729 require('linux-info').packages().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1730 } else { sendSoftwareResponse({ error: "Not supported" }); }
1731 } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1732 + } else if (process.platform == 'darwin') {
1733 + try {
1734 + if (require('mac-info').apps) {
1735 + require('mac-info').apps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1736 + } else { sendSoftwareResponse({ error: "Not supported" }); }
1737 + } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1738 } else {
1739 sendSoftwareResponse({ success: false, error: "Not supported" });
1740 }
@@ -6052,6 +6058,16 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
6058 } catch (ex) {
6059 sendConsoleText("Module linux-info error: " + ex, sessionid);
6060 }
6061 + } else if (process.platform == 'darwin') {
6062 + try {
6063 + require('mac-info').apps().then(function (apps) {
6064 + sendConsoleText(JSON.stringify(apps, null, 1), sessionid);
6065 + }).catch(function(e) {
6066 + sendConsoleText("Error: " + e, sessionid);
6067 + });
6068 + } catch (ex) {
6069 + sendConsoleText("Module mac-info error: " + ex, sessionid);
6070 + }
6071 } else {
6072 sendConsoleText("Installed apps not supported on this platform.", sessionid);
6073 }
agents/modules_meshcore/mac-info.js new
+92
@@ -0,0 +1,92 @@
1 +
2 +var promise = require('promise');
3 +
4 +function apps() {
5 + var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
6 +
7 + function runSh(script) {
8 + try {
9 + var child = require('child_process').execFile('/bin/sh', ['sh']);
10 + child.stdout.str = '';
11 + child.stdout.on('data', function (d) { child.stdout.str += d; });
12 + child.stdin.write(script + '\nexit\n');
13 + child.waitExit();
14 + return child.stdout.str.trim();
15 + } catch (ex) { return ''; }
16 + }
17 +
18 + // Single shell process: PlistBuddy handles both XML and binary plists.
19 + // Outputs tab-separated: name, version, publisher, date, scope — one app per line.
20 + var script = [
21 + 'PB=/usr/libexec/PlistBuddy',
22 + 'process_app() {',
23 + ' app="$1" scope="$2"',
24 + ' plist="$app/Contents/Info.plist"',
25 + ' [ -f "$plist" ] || return',
26 + ' name=$(basename "$app" .app)',
27 + ' version=$("$PB" -c "Print :CFBundleShortVersionString" "$plist" 2>/dev/null)',
28 + ' publisher=$("$PB" -c "Print :NSHumanReadableCopyright" "$plist" 2>/dev/null)',
29 + ' if [ -z "$publisher" ]; then',
30 + ' bid=$("$PB" -c "Print :CFBundleIdentifier" "$plist" 2>/dev/null)',
31 + ' publisher=$(echo "$bid" | awk -F. \'{if(NF>=2){s=$2; print toupper(substr(s,1,1)) substr(s,2)}}\')',
32 + ' fi',
33 + ' date=$(stat -f "%Sm" -t "%Y-%m-%d" "$app" 2>/dev/null)',
34 + ' arch=""',
35 + ' exe=$("$PB" -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)',
36 + ' if [ -n "$exe" ]; then',
37 + ' lipoout=$(lipo -info "$app/Contents/MacOS/$exe" 2>/dev/null)',
38 + ' if echo "$lipoout" | grep -q "x86_64" && echo "$lipoout" | grep -q "arm64"; then',
39 + ' arch="universal"',
40 + ' elif echo "$lipoout" | grep -q "arm64"; then',
41 + ' arch="arm64"',
42 + ' elif echo "$lipoout" | grep -q "x86_64"; then',
43 + ' arch="x86_64"',
44 + ' fi',
45 + ' fi',
46 + ' printf "%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n" \\',
47 + ' "$(printf "%s" "$name" | tr "\\t\\n" " ")" \\',
48 + ' "$(printf "%s" "$version" | tr "\\t\\n" " ")" \\',
49 + ' "$(printf "%s" "$publisher" | tr "\\t\\n" " ")" \\',
50 + ' "$date" "$arch" "$scope"',
51 + '}',
52 + 'for sysdir in /Applications /System/Applications /System/Applications/Utilities; do',
53 + ' [ -d "$sysdir" ] || continue',
54 + ' find "$sysdir" -maxdepth 1 -name "*.app" -type d 2>/dev/null | while IFS= read -r app; do',
55 + ' process_app "$app" system',
56 + ' done',
57 + 'done',
58 + 'for userdir in /Users/*/; do',
59 + ' user=$(basename "$userdir")',
60 + ' [ "$user" = Shared ] && continue',
61 + ' case "$user" in .*) continue;; esac',
62 + ' appsdir="${userdir}Applications"',
63 + ' [ -d "$appsdir" ] || continue',
64 + ' find "$appsdir" -maxdepth 1 -name "*.app" -type d 2>/dev/null | while IFS= read -r app; do',
65 + ' process_app "$app" user',
66 + ' done',
67 + 'done'
68 + ].join('\n');
69 +
70 + try {
71 + var out = runSh(script);
72 + var results = [];
73 + if (out) {
74 + out.split('\n').forEach(function (line) {
75 + var p = line.split('\t');
76 + if (p.length < 6 || !p[0]) return;
77 + results.push({ name: p[0], version: p[1] || '', publisher: p[2] || '', date: p[3] || '', arch: p[4] || '', location: p[5] || 'system' });
78 + });
79 + }
80 + ret._res(results);
81 + } catch (e) {
82 + ret._rej(e);
83 + }
84 + return (ret);
85 +}
86 +
87 +if (process.platform == 'darwin') {
88 + module.exports = { apps: apps };
89 +} else {
90 + var not_supported = function () { throw (process.platform + ' not supported'); };
91 + module.exports = { apps: not_supported };
92 +}