Add system_get_info and system_speech_api_status tools - check macOS version and SpeechAnalyzer availability (for Apple Speech API benchmark verification)
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { execFile, execFile as execFileCb } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export async function getSystemInfo() {
|
||||
const results = {};
|
||||
// sw_vers for macOS version
|
||||
try {
|
||||
const { stdout } = await execFileAsync("/usr/bin/sw_vers", { timeout: 5000 });
|
||||
const info = {};
|
||||
for (const line of stdout.splitlines()) {
|
||||
const [k, ...rest] = line.split(":");
|
||||
if (!k) continue;
|
||||
info[k.trim()] = rest.join(":").trim();
|
||||
}
|
||||
results.sw_vers = info;
|
||||
results.macos_version = info.ProductVersion || info.productVersion || "";
|
||||
results.build = info.BuildVersion || info.buildVersion || "";
|
||||
} catch (e) {
|
||||
results.sw_vers_error = e.message;
|
||||
}
|
||||
|
||||
// uname -a
|
||||
try {
|
||||
const { stdout } = await execFileAsync("/usr/bin/uname", ["-a"], { timeout: 3000 });
|
||||
results.uname = stdout.trim();
|
||||
} catch (e) {
|
||||
results.uname_error = e.message;
|
||||
}
|
||||
|
||||
// Check for SpeechAnalyzer / SpeechTranscriber availability (macOS 26+)
|
||||
// These frameworks exist only on macOS 26+. We probe via swift/python check.
|
||||
try {
|
||||
const { stdout } = await execFileAsync("/usr/bin/python3", ["-c", `
|
||||
import os, glob, sys
|
||||
# Check if Speech framework contains new symbols (macOS 26)
|
||||
frameworks = glob.glob('/System/Library/Frameworks/Speech.framework/*') + glob.glob('/System/Library/PrivateFrameworks/*Speech*')
|
||||
# Simplest: check macOS version parse
|
||||
import platform
|
||||
print(platform.mac_ver()[0])
|
||||
`], { timeout: 5000 });
|
||||
results.python_mac_ver = stdout.trim();
|
||||
} catch (e) {
|
||||
results.python_mac_ver_error = e.message;
|
||||
}
|
||||
|
||||
// Try to detect SpeechAnalyzer via file existence / Swift availability
|
||||
try {
|
||||
// On macOS 26, Speech.framework/Versions should have newer build
|
||||
const { stdout } = await execFileAsync("/bin/ls", ["-la", "/System/Library/Frameworks/Speech.framework/"], { timeout: 3000 });
|
||||
results.speech_framework_ls = stdout.trim().slice(0, 2000);
|
||||
} catch (e) {
|
||||
results.speech_framework_error = e.message;
|
||||
}
|
||||
|
||||
// Check hardware model
|
||||
try {
|
||||
const { stdout } = await execFileAsync("/usr/sbin/sysctl", ["-n", "hw.model"], { timeout: 2000 });
|
||||
results.hw_model = stdout.trim();
|
||||
} catch {}
|
||||
try {
|
||||
const { stdout } = await execFileAsync("/usr/sbin/sysctl", ["-n", "machdep.cpu.brand_string"], { timeout: 2000 });
|
||||
results.cpu_brand = stdout.trim();
|
||||
} catch {}
|
||||
|
||||
// Is this macOS 26+ ?
|
||||
if (results.macos_version) {
|
||||
const major = parseInt(results.macos_version.split(".")[0], 10);
|
||||
results.is_macos_26_plus = major >= 26;
|
||||
results.speech_analyzer_expected = major >= 26 ? "likely available (macOS 26+)" : "not available - requires macOS 26+";
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function getSpeechApiStatus() {
|
||||
const sysInfo = await getSystemInfo();
|
||||
// Try to run a tiny Swift snippet that imports Speech and checks for SpeechAnalyzer
|
||||
// Fallback to reporting version info if swiftc not available
|
||||
let swiftCheck = null;
|
||||
try {
|
||||
// Write temp swift file that checks API availability
|
||||
const { stdout: swiftPath } = await execFileAsync("/usr/bin/which", ["swift"], { timeout: 2000 });
|
||||
const swiftBin = swiftPath.trim();
|
||||
if (swiftBin) {
|
||||
// Create a small swift program to test SpeechAnalyzer availability
|
||||
const swiftCode = `
|
||||
import Speech
|
||||
import Foundation
|
||||
#if canImport(Speech)
|
||||
if #available(macOS 26.0, *) {
|
||||
print("SpeechAnalyzer: available")
|
||||
// Try to reference the type
|
||||
let _ = SpeechTranscriber.self
|
||||
print("SpeechTranscriber: available")
|
||||
} else {
|
||||
print("SpeechAnalyzer: requires macOS 26")
|
||||
}
|
||||
#else
|
||||
print("Speech framework not importable")
|
||||
#endif
|
||||
`;
|
||||
const tmpFile = `/tmp/speech_check_${Date.now()}.swift`;
|
||||
const { writeFile, unlink } = await import("node:fs/promises");
|
||||
await writeFile(tmpFile, swiftCode, "utf8");
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync(swiftBin, [tmpFile], { timeout: 15000, maxBuffer: 2 * 1024 * 1024 });
|
||||
swiftCheck = { stdout: stdout.trim(), stderr: stderr.trim(), ok: true };
|
||||
} catch (e) {
|
||||
swiftCheck = { stdout: e.stdout?.toString().trim() || "", stderr: (e.stderr?.toString() || e.message).trim().slice(0, 2000), ok: false };
|
||||
} finally {
|
||||
try { await unlink(tmpFile); } catch {}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
swiftCheck = { error: e.message };
|
||||
}
|
||||
|
||||
return {
|
||||
system: sysInfo,
|
||||
swift_availability: swiftCheck,
|
||||
conclusion: sysInfo.is_macos_26_plus
|
||||
? "macOS 26+ detected — SpeechAnalyzer/SpeechTranscriber should be available per Apple docs."
|
||||
: `macOS ${sysInfo.macos_version || "unknown"} detected — SpeechAnalyzer requires macOS 26+. ${sysInfo.macos_version ? `You are on ${sysInfo.macos_version}, need to upgrade to 26.` : ""}`,
|
||||
};
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
listReminderLists,
|
||||
listReminders,
|
||||
} from "./integrations/reminders.js";
|
||||
import { getSystemInfo, getSpeechApiStatus } from "./integrations/system.js";
|
||||
|
||||
const jsonResult = (value) => ({
|
||||
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
||||
@@ -226,5 +227,19 @@ export function createMacMiniMcpServer() {
|
||||
handled(() => getDecoStats("firmware")),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"system_get_info",
|
||||
"Get Mac mini system info: macOS version (sw_vers), uname, hardware model, and whether macOS 26+ SpeechAnalyzer is available.",
|
||||
{},
|
||||
handled(() => getSystemInfo()),
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"system_speech_api_status",
|
||||
"Check if Apple's new SpeechAnalyzer / SpeechTranscriber (macOS 26+) is available — returns macOS version, build, and Swift availability probe.",
|
||||
{},
|
||||
handled(() => getSpeechApiStatus()),
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user