- Standardized error handling across tool implementations - Improved return type consistency for tool execution results - Simplified configuration parsing and type definitions - Enhanced type safety for various configuration schemas - Cleaned up and normalized tool response structures - Updated SSE and event subscription tool implementations
224 lines
6.3 KiB
TypeScript
224 lines
6.3 KiB
TypeScript
import { exec } from "child_process";
|
|
import { promisify } from "util";
|
|
import { EventEmitter } from "events";
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
interface MacOSNotification {
|
|
title: string;
|
|
message: string;
|
|
subtitle?: string;
|
|
sound?: boolean;
|
|
}
|
|
|
|
interface MacOSPermissions {
|
|
notifications: boolean;
|
|
automation: boolean;
|
|
accessibility: boolean;
|
|
}
|
|
|
|
class MacOSIntegration extends EventEmitter {
|
|
private permissions: MacOSPermissions;
|
|
|
|
constructor() {
|
|
super();
|
|
this.permissions = {
|
|
notifications: false,
|
|
automation: false,
|
|
accessibility: false,
|
|
};
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
await this.checkPermissions();
|
|
await this.registerSystemEvents();
|
|
}
|
|
|
|
async checkPermissions(): Promise<MacOSPermissions> {
|
|
try {
|
|
// Check notification permissions
|
|
const { stdout: notifPerms } = await execAsync(
|
|
"osascript -e 'tell application \"System Events\" to get properties'",
|
|
);
|
|
this.permissions.notifications = notifPerms.includes(
|
|
"notifications enabled:true",
|
|
);
|
|
|
|
// Check automation permissions
|
|
const { stdout: autoPerms } = await execAsync(
|
|
"osascript -e 'tell application \"System Events\" to get UI elements enabled'",
|
|
);
|
|
this.permissions.automation = autoPerms.includes("true");
|
|
|
|
// Check accessibility permissions
|
|
const { stdout: accessPerms } = await execAsync(
|
|
"osascript -e 'tell application \"System Events\" to get processes'",
|
|
);
|
|
this.permissions.accessibility = !accessPerms.includes("error");
|
|
|
|
return this.permissions;
|
|
} catch (error) {
|
|
console.error("Error checking permissions:", error);
|
|
return this.permissions;
|
|
}
|
|
}
|
|
|
|
async sendNotification(notification: MacOSNotification): Promise<void> {
|
|
if (!this.permissions.notifications) {
|
|
throw new Error("Notification permission not granted");
|
|
}
|
|
|
|
const script = `
|
|
display notification "${notification.message}"${
|
|
notification.subtitle ? ` with subtitle "${notification.subtitle}"` : ""
|
|
} with title "${notification.title}"${
|
|
notification.sound ? ' sound name "default"' : ""
|
|
}
|
|
`;
|
|
|
|
try {
|
|
await execAsync(`osascript -e '${script}'`);
|
|
} catch (error) {
|
|
console.error("Error sending notification:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async registerSystemEvents(): Promise<void> {
|
|
if (!this.permissions.automation) {
|
|
throw new Error("Automation permission not granted");
|
|
}
|
|
|
|
// Monitor system events
|
|
const script = `
|
|
tell application "System Events"
|
|
set eventList to {}
|
|
|
|
-- Monitor display sleep/wake
|
|
tell application "System Events"
|
|
set displayState to get sleeping
|
|
if displayState then
|
|
set end of eventList to "display_sleep"
|
|
else
|
|
set end of eventList to "display_wake"
|
|
end if
|
|
end tell
|
|
|
|
-- Monitor power source changes
|
|
tell application "System Events"
|
|
set powerSource to get power source
|
|
set end of eventList to "power_" & powerSource
|
|
end tell
|
|
|
|
return eventList
|
|
end tell
|
|
`;
|
|
|
|
try {
|
|
const { stdout } = await execAsync(`osascript -e '${script}'`);
|
|
const events = stdout.split(",").map((e) => e.trim());
|
|
events.forEach((event) => this.emit("system_event", event));
|
|
} catch (error) {
|
|
console.error("Error monitoring system events:", error);
|
|
}
|
|
}
|
|
|
|
async executeAutomation(script: string): Promise<string> {
|
|
if (!this.permissions.automation) {
|
|
throw new Error("Automation permission not granted");
|
|
}
|
|
|
|
try {
|
|
const { stdout } = await execAsync(`osascript -e '${script}'`);
|
|
return stdout;
|
|
} catch (error) {
|
|
console.error("Error executing automation:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getSystemInfo(): Promise<Record<string, any>> {
|
|
const info: Record<string, any> = {};
|
|
|
|
try {
|
|
// Get macOS version
|
|
const { stdout: version } = await execAsync("sw_vers -productVersion");
|
|
info.os_version = version.trim();
|
|
|
|
// Get hardware info
|
|
const { stdout: hardware } = await execAsync(
|
|
"system_profiler SPHardwareDataType",
|
|
);
|
|
info.hardware = this.parseSystemProfile(hardware);
|
|
|
|
// Get power info
|
|
const { stdout: power } = await execAsync("pmset -g batt");
|
|
info.power = this.parsePowerInfo(power);
|
|
|
|
// Get network info
|
|
const { stdout: network } = await execAsync(
|
|
"networksetup -listallhardwareports",
|
|
);
|
|
info.network = this.parseNetworkInfo(network);
|
|
|
|
return info;
|
|
} catch (error) {
|
|
console.error("Error getting system info:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private parseSystemProfile(output: string): Record<string, any> {
|
|
const info: Record<string, any> = {};
|
|
const lines = output.split("\n");
|
|
|
|
for (const line of lines) {
|
|
const [key, value] = line.split(":").map((s) => s.trim());
|
|
if (key && value) {
|
|
info[key.toLowerCase().replace(/\s+/g, "_")] = value;
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
private parsePowerInfo(output: string): Record<string, any> {
|
|
const info: Record<string, any> = {};
|
|
const lines = output.split("\n");
|
|
|
|
for (const line of lines) {
|
|
if (line.includes("Now drawing from")) {
|
|
info.power_source = line.includes("Battery") ? "battery" : "ac_power";
|
|
} else if (line.includes("%")) {
|
|
const matches = line.match(/(\d+)%/);
|
|
if (matches) {
|
|
info.battery_percentage = parseInt(matches[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
private parseNetworkInfo(output: string): Record<string, any> {
|
|
const info: Record<string, any> = {};
|
|
const lines = output.split("\n");
|
|
let currentInterface: string | null = null;
|
|
|
|
for (const line of lines) {
|
|
if (line.includes("Hardware Port:")) {
|
|
currentInterface = line.split(":")[1].trim();
|
|
info[currentInterface] = {};
|
|
} else if (currentInterface && line.includes("Device:")) {
|
|
info[currentInterface].device = line.split(":")[1].trim();
|
|
} else if (currentInterface && line.includes("Ethernet Address:")) {
|
|
info[currentInterface].mac = line.split(":")[1].trim();
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
}
|
|
|
|
export default MacOSIntegration;
|