@ebowwa/osascript 1.1.0
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/README.md +123 -0
- package/dist/index.js +574 -0
- package/dist/mcp/index.js +866 -0
- package/dist/mcp/stdio.js +1704 -0
- package/package.json +79 -0
- package/src/index.ts +844 -0
- package/src/mcp/events.ts +468 -0
- package/src/mcp/index.ts +565 -0
- package/src/mcp/stdio.ts +1284 -0
- package/src/types.ts +189 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# @ebowwa/osascript
|
|
2
|
+
|
|
3
|
+
macOS automation via osascript (AppleScript/JXA) with MCP interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @ebowwa/osascript
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## MCP Server
|
|
12
|
+
|
|
13
|
+
Add to your Claude Code config:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"osascript": {
|
|
19
|
+
"command": "bunx",
|
|
20
|
+
"args": ["@ebowwa/osascript/mcp"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Available Tools
|
|
27
|
+
|
|
28
|
+
| Tool | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `osascript_execute` | Execute raw AppleScript or JXA code |
|
|
31
|
+
| `osascript_display_notification` | Display a macOS notification |
|
|
32
|
+
| `osascript_display_dialog` | Display a dialog with buttons |
|
|
33
|
+
| `osascript_get_volume` | Get system volume level |
|
|
34
|
+
| `osascript_set_volume` | Set system volume (0-100) |
|
|
35
|
+
| `osascript_get_clipboard` | Get clipboard contents |
|
|
36
|
+
| `osascript_set_clipboard` | Set clipboard contents |
|
|
37
|
+
| `osascript_list_apps` | List all running applications |
|
|
38
|
+
| `osascript_get_app_info` | Get detailed app information |
|
|
39
|
+
| `osascript_activate_app` | Activate (bring to front) an app |
|
|
40
|
+
| `osascript_quit_app` | Quit an application |
|
|
41
|
+
| `osascript_launch_app` | Launch an application |
|
|
42
|
+
| `osascript_get_frontmost_app` | Get the active application name |
|
|
43
|
+
| `osascript_get_windows` | Get window list for an app |
|
|
44
|
+
| `osascript_set_window_bounds` | Set window position and size |
|
|
45
|
+
| `osascript_keystroke` | Send keystroke with modifiers |
|
|
46
|
+
| `osascript_key_code` | Send key code (special keys) |
|
|
47
|
+
| `osascript_type_text` | Type text (simulate keyboard) |
|
|
48
|
+
| `osascript_safari_get_url` | Get current Safari URL |
|
|
49
|
+
| `osascript_safari_set_url` | Navigate Safari to URL |
|
|
50
|
+
| `osascript_safari_get_tabs` | Get Safari tab titles |
|
|
51
|
+
| `osascript_safari_exec_js` | Execute JavaScript in Safari |
|
|
52
|
+
| `osascript_finder_get_selection` | Get Finder selected files |
|
|
53
|
+
| `osascript_finder_open` | Open Finder window at path |
|
|
54
|
+
| `osascript_finder_create_folder` | Create new folder |
|
|
55
|
+
| `osascript_finder_trash` | Move file to trash |
|
|
56
|
+
| `osascript_get_displays` | Get display information |
|
|
57
|
+
| `osascript_get_resolution` | Get screen resolution |
|
|
58
|
+
| `osascript_say` | Say text using system voice |
|
|
59
|
+
| `osascript_beep` | Play system beep |
|
|
60
|
+
|
|
61
|
+
## Key Codes
|
|
62
|
+
|
|
63
|
+
Common key codes for `osascript_key_code`:
|
|
64
|
+
|
|
65
|
+
| Key | Code |
|
|
66
|
+
|-----|------|
|
|
67
|
+
| Return | 36 |
|
|
68
|
+
| Tab | 48 |
|
|
69
|
+
| Space | 49 |
|
|
70
|
+
| Delete | 51 |
|
|
71
|
+
| Escape | 53 |
|
|
72
|
+
| Arrow Left | 123 |
|
|
73
|
+
| Arrow Right | 124 |
|
|
74
|
+
| Arrow Down | 125 |
|
|
75
|
+
| Arrow Up | 126 |
|
|
76
|
+
| Home | 115 |
|
|
77
|
+
| End | 119 |
|
|
78
|
+
| Page Up | 116 |
|
|
79
|
+
| Page Down | 121 |
|
|
80
|
+
|
|
81
|
+
## Usage Examples
|
|
82
|
+
|
|
83
|
+
### Notification
|
|
84
|
+
```typescript
|
|
85
|
+
import { displayNotification } from "@ebowwa/osascript";
|
|
86
|
+
|
|
87
|
+
await displayNotification({
|
|
88
|
+
title: "Task Complete",
|
|
89
|
+
message: "Your file has been processed",
|
|
90
|
+
soundName: "Glass"
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Clipboard
|
|
95
|
+
```typescript
|
|
96
|
+
import { getClipboard, setClipboard } from "@ebowwa/osascript";
|
|
97
|
+
|
|
98
|
+
await setClipboard("Hello, World!");
|
|
99
|
+
const content = await getClipboard(); // "Hello, World!"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Safari
|
|
103
|
+
```typescript
|
|
104
|
+
import { getSafariURL, setSafariURL } from "@ebowwa/osascript";
|
|
105
|
+
|
|
106
|
+
const url = await getSafariURL();
|
|
107
|
+
await setSafariURL("https://github.com");
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Keyboard
|
|
111
|
+
```typescript
|
|
112
|
+
import { sendKeystroke, KeyCodes, sendKeyCode } from "@ebowwa/osascript";
|
|
113
|
+
|
|
114
|
+
// Send Command+C
|
|
115
|
+
await sendKeystroke({ key: "c", modifiers: ["command"] });
|
|
116
|
+
|
|
117
|
+
// Press Return
|
|
118
|
+
await sendKeyCode({ keyCode: KeyCodes.RETURN });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/index.ts
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
async function executeOsascript(script, options = {}) {
|
|
5
|
+
const { language = "applescript", timeout = 30000 } = options;
|
|
6
|
+
const args = [];
|
|
7
|
+
if (language === "javascript") {
|
|
8
|
+
args.push("-l", "JavaScript");
|
|
9
|
+
}
|
|
10
|
+
args.push("-e", script);
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const proc = spawn("osascript", args, { timeout });
|
|
13
|
+
let stdout = "";
|
|
14
|
+
let stderr = "";
|
|
15
|
+
proc.stdout.on("data", (data) => {
|
|
16
|
+
stdout += data.toString();
|
|
17
|
+
});
|
|
18
|
+
proc.stderr.on("data", (data) => {
|
|
19
|
+
stderr += data.toString();
|
|
20
|
+
});
|
|
21
|
+
const timer = setTimeout(() => {
|
|
22
|
+
proc.kill();
|
|
23
|
+
resolve({
|
|
24
|
+
success: false,
|
|
25
|
+
stdout: stdout.trim(),
|
|
26
|
+
stderr: "Timeout exceeded",
|
|
27
|
+
exitCode: 1
|
|
28
|
+
});
|
|
29
|
+
}, timeout);
|
|
30
|
+
proc.on("close", (code) => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
resolve({
|
|
33
|
+
success: code === 0,
|
|
34
|
+
stdout: stdout.trim(),
|
|
35
|
+
stderr: stderr.trim(),
|
|
36
|
+
exitCode: code ?? 1
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
proc.on("error", (error) => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
resolve({
|
|
42
|
+
success: false,
|
|
43
|
+
stdout: "",
|
|
44
|
+
stderr: error.message,
|
|
45
|
+
exitCode: 1
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function executeAppleScript(script, timeout = 30000) {
|
|
51
|
+
return executeOsascript(script, { language: "applescript", timeout });
|
|
52
|
+
}
|
|
53
|
+
async function executeJXA(script, timeout = 30000) {
|
|
54
|
+
return executeOsascript(script, { language: "javascript", timeout });
|
|
55
|
+
}
|
|
56
|
+
async function displayNotification(options) {
|
|
57
|
+
const { title = "Notification", subtitle, message, soundName } = options;
|
|
58
|
+
let script = `display notification "${escapeString(message)}"`;
|
|
59
|
+
if (title)
|
|
60
|
+
script += ` with title "${escapeString(title)}"`;
|
|
61
|
+
if (subtitle)
|
|
62
|
+
script += ` subtitle "${escapeString(subtitle)}"`;
|
|
63
|
+
if (soundName)
|
|
64
|
+
script += ` sound name "${escapeString(soundName)}"`;
|
|
65
|
+
return executeAppleScript(script);
|
|
66
|
+
}
|
|
67
|
+
async function displayDialog(options) {
|
|
68
|
+
const { title, message, buttons = ["OK"], defaultButton = 1, icon, givingUpAfter } = options;
|
|
69
|
+
let script = `display dialog "${escapeString(message)}"`;
|
|
70
|
+
script += ` buttons ${JSON.stringify(buttons)}`;
|
|
71
|
+
script += ` default button ${defaultButton}`;
|
|
72
|
+
if (icon)
|
|
73
|
+
script += ` with icon ${icon}`;
|
|
74
|
+
if (givingUpAfter)
|
|
75
|
+
script += ` giving up after ${givingUpAfter}`;
|
|
76
|
+
const result = await executeAppleScript(script);
|
|
77
|
+
if (!result.success) {
|
|
78
|
+
throw new Error(`Dialog failed: ${result.stderr}`);
|
|
79
|
+
}
|
|
80
|
+
const buttonMatch = result.stdout.match(/button returned:(\w+)/);
|
|
81
|
+
const gaveUpMatch = result.stdout.match(/gave up:(\w+)/);
|
|
82
|
+
return {
|
|
83
|
+
buttonReturned: buttonMatch ? buttonMatch[1] : buttons[0],
|
|
84
|
+
gaveUp: gaveUpMatch ? gaveUpMatch[1] === "true" : false
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function getVolume() {
|
|
88
|
+
const script = `
|
|
89
|
+
output volume of (get volume settings) as integer
|
|
90
|
+
`;
|
|
91
|
+
const volumeResult = await executeAppleScript(script);
|
|
92
|
+
const mutedScript = `
|
|
93
|
+
output muted of (get volume settings) as boolean
|
|
94
|
+
`;
|
|
95
|
+
const mutedResult = await executeAppleScript(mutedScript);
|
|
96
|
+
return {
|
|
97
|
+
volume: parseInt(volumeResult.stdout) || 0,
|
|
98
|
+
muted: mutedResult.stdout.toLowerCase() === "true"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function setVolume(volume, muted) {
|
|
102
|
+
const safeVolume = Math.max(0, Math.min(100, Math.round(volume)));
|
|
103
|
+
let script = `set volume output volume ${safeVolume}`;
|
|
104
|
+
if (muted !== undefined) {
|
|
105
|
+
script += muted ? " with output muted" : " without output muted";
|
|
106
|
+
}
|
|
107
|
+
return executeAppleScript(script);
|
|
108
|
+
}
|
|
109
|
+
async function getClipboard() {
|
|
110
|
+
const result = await executeAppleScript("the clipboard");
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
throw new Error(`Failed to get clipboard: ${result.stderr}`);
|
|
113
|
+
}
|
|
114
|
+
return result.stdout;
|
|
115
|
+
}
|
|
116
|
+
async function setClipboard(text) {
|
|
117
|
+
const script = `set the clipboard to "${escapeString(text)}"`;
|
|
118
|
+
return executeAppleScript(script);
|
|
119
|
+
}
|
|
120
|
+
async function clearClipboard() {
|
|
121
|
+
return executeAppleScript('set the clipboard to ""');
|
|
122
|
+
}
|
|
123
|
+
async function listRunningApplications() {
|
|
124
|
+
const script = `
|
|
125
|
+
tell application "System Events"
|
|
126
|
+
set appList to name of every process whose background only is false
|
|
127
|
+
set output to ""
|
|
128
|
+
repeat with appName in appList
|
|
129
|
+
set output to output & appName & linefeed
|
|
130
|
+
end repeat
|
|
131
|
+
return output
|
|
132
|
+
end tell
|
|
133
|
+
`;
|
|
134
|
+
const result = await executeAppleScript(script);
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
throw new Error(`Failed to list applications: ${result.stderr}`);
|
|
137
|
+
}
|
|
138
|
+
const apps = result.stdout.split(`
|
|
139
|
+
`).filter((name) => name.trim()).map((name) => ({
|
|
140
|
+
name: name.trim(),
|
|
141
|
+
running: true
|
|
142
|
+
}));
|
|
143
|
+
return apps;
|
|
144
|
+
}
|
|
145
|
+
async function getApplicationInfo(appName) {
|
|
146
|
+
const script = `
|
|
147
|
+
tell application "System Events"
|
|
148
|
+
if exists process "${escapeString(appName)}" then
|
|
149
|
+
set theProcess to process "${escapeString(appName)}"
|
|
150
|
+
set theName to name of theProcess
|
|
151
|
+
set thePath to POSIX path of (application file of theProcess as alias)
|
|
152
|
+
set thePID to unix id of theProcess
|
|
153
|
+
set windowCount to count of windows of theProcess
|
|
154
|
+
return theName & "|" & thePath & "|" & thePID & "|" & windowCount
|
|
155
|
+
else
|
|
156
|
+
return "NOT_RUNNING"
|
|
157
|
+
end if
|
|
158
|
+
end tell
|
|
159
|
+
`;
|
|
160
|
+
const result = await executeAppleScript(script);
|
|
161
|
+
if (!result.success || result.stdout === "NOT_RUNNING") {
|
|
162
|
+
return {
|
|
163
|
+
name: appName,
|
|
164
|
+
running: false
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const [name, path, pid, windowCount] = result.stdout.split("|");
|
|
168
|
+
return {
|
|
169
|
+
name,
|
|
170
|
+
path,
|
|
171
|
+
processId: parseInt(pid),
|
|
172
|
+
running: true,
|
|
173
|
+
windowCount: parseInt(windowCount)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function activateApplication(appName) {
|
|
177
|
+
const script = `
|
|
178
|
+
tell application "${escapeString(appName)}"
|
|
179
|
+
activate
|
|
180
|
+
end tell
|
|
181
|
+
`;
|
|
182
|
+
return executeAppleScript(script);
|
|
183
|
+
}
|
|
184
|
+
async function quitApplication(appName, force = false) {
|
|
185
|
+
let script;
|
|
186
|
+
if (force) {
|
|
187
|
+
script = `
|
|
188
|
+
tell application "System Events"
|
|
189
|
+
set theProcess to process "${escapeString(appName)}"
|
|
190
|
+
set thePID to unix id of theProcess
|
|
191
|
+
do shell script "kill -9 " & thePID
|
|
192
|
+
end tell
|
|
193
|
+
`;
|
|
194
|
+
} else {
|
|
195
|
+
script = `
|
|
196
|
+
tell application "${escapeString(appName)}"
|
|
197
|
+
quit
|
|
198
|
+
end tell
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
return executeAppleScript(script);
|
|
202
|
+
}
|
|
203
|
+
async function launchApplication(appName) {
|
|
204
|
+
const script = `
|
|
205
|
+
tell application "${escapeString(appName)}"
|
|
206
|
+
launch
|
|
207
|
+
end tell
|
|
208
|
+
`;
|
|
209
|
+
return executeAppleScript(script);
|
|
210
|
+
}
|
|
211
|
+
async function getFrontmostApplication() {
|
|
212
|
+
const script = `
|
|
213
|
+
tell application "System Events"
|
|
214
|
+
name of first process whose frontmost is true
|
|
215
|
+
end tell
|
|
216
|
+
`;
|
|
217
|
+
const result = await executeAppleScript(script);
|
|
218
|
+
if (!result.success) {
|
|
219
|
+
throw new Error(`Failed to get frontmost app: ${result.stderr}`);
|
|
220
|
+
}
|
|
221
|
+
return result.stdout;
|
|
222
|
+
}
|
|
223
|
+
async function getApplicationWindows(appName) {
|
|
224
|
+
const script = `
|
|
225
|
+
tell application "System Events"
|
|
226
|
+
tell process "${escapeString(appName)}"
|
|
227
|
+
set windowList to {}
|
|
228
|
+
repeat with theWindow in windows
|
|
229
|
+
set windowName to name of theWindow
|
|
230
|
+
set windowId to id of theWindow
|
|
231
|
+
set windowIndex to index of theWindow
|
|
232
|
+
set end of windowList to windowName & "|" & windowId & "|" & windowIndex
|
|
233
|
+
end repeat
|
|
234
|
+
return windowList as text
|
|
235
|
+
end tell
|
|
236
|
+
end tell
|
|
237
|
+
`;
|
|
238
|
+
const result = await executeAppleScript(script);
|
|
239
|
+
if (!result.success) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
if (!result.stdout.trim()) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const windows = result.stdout.split(", ").filter((w) => w.trim()).map((w) => {
|
|
246
|
+
const [name, id, index] = w.split("|");
|
|
247
|
+
return {
|
|
248
|
+
name: name || null,
|
|
249
|
+
id: parseInt(id) || 0,
|
|
250
|
+
index: parseInt(index) || 0
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
return windows;
|
|
254
|
+
}
|
|
255
|
+
async function setWindowBounds(appName, windowIndex, bounds) {
|
|
256
|
+
const script = `
|
|
257
|
+
tell application "System Events"
|
|
258
|
+
tell process "${escapeString(appName)}"
|
|
259
|
+
set position of window ${windowIndex} to {${bounds.x}, ${bounds.y}}
|
|
260
|
+
set size of window ${windowIndex} to {${bounds.width}, ${bounds.height}}
|
|
261
|
+
end tell
|
|
262
|
+
end tell
|
|
263
|
+
`;
|
|
264
|
+
return executeAppleScript(script);
|
|
265
|
+
}
|
|
266
|
+
async function minimizeWindow(appName, windowIndex) {
|
|
267
|
+
const script = `
|
|
268
|
+
tell application "System Events"
|
|
269
|
+
tell process "${escapeString(appName)}"
|
|
270
|
+
set value of attribute "AXMinimized" of window ${windowIndex} to true
|
|
271
|
+
end tell
|
|
272
|
+
end tell
|
|
273
|
+
`;
|
|
274
|
+
return executeAppleScript(script);
|
|
275
|
+
}
|
|
276
|
+
async function sendKeystroke(options) {
|
|
277
|
+
const { key, modifiers = [], delay = 0 } = options;
|
|
278
|
+
const modifierStr = modifiers.map((m) => m + " down").join(", ");
|
|
279
|
+
const usingStr = modifierStr ? ` using {${modifierStr}}` : "";
|
|
280
|
+
const script = `
|
|
281
|
+
tell application "System Events"
|
|
282
|
+
keystroke "${escapeString(key)}"${usingStr}
|
|
283
|
+
${delay > 0 ? `delay ${delay}` : ""}
|
|
284
|
+
end tell
|
|
285
|
+
`;
|
|
286
|
+
return executeAppleScript(script);
|
|
287
|
+
}
|
|
288
|
+
async function sendKeyCode(options) {
|
|
289
|
+
const { keyCode, modifiers = [] } = options;
|
|
290
|
+
const modifierStr = modifiers.map((m) => m + " down").join(", ");
|
|
291
|
+
const usingStr = modifierStr ? ` using {${modifierStr}}` : "";
|
|
292
|
+
const script = `
|
|
293
|
+
tell application "System Events"
|
|
294
|
+
key code ${keyCode}${usingStr}
|
|
295
|
+
end tell
|
|
296
|
+
`;
|
|
297
|
+
return executeAppleScript(script);
|
|
298
|
+
}
|
|
299
|
+
async function typeText(text, delayBetweenChars = 0.01) {
|
|
300
|
+
const script = `
|
|
301
|
+
tell application "System Events"
|
|
302
|
+
keystroke "${escapeString(text)}"
|
|
303
|
+
${delayBetweenChars > 0 ? `delay ${delayBetweenChars}` : ""}
|
|
304
|
+
end tell
|
|
305
|
+
`;
|
|
306
|
+
return executeAppleScript(script);
|
|
307
|
+
}
|
|
308
|
+
async function pressKeyCombination(key, modifiers) {
|
|
309
|
+
return sendKeystroke({ key, modifiers });
|
|
310
|
+
}
|
|
311
|
+
var KeyCodes = {
|
|
312
|
+
RETURN: 36,
|
|
313
|
+
TAB: 48,
|
|
314
|
+
SPACE: 49,
|
|
315
|
+
DELETE: 51,
|
|
316
|
+
ESCAPE: 53,
|
|
317
|
+
LEFT_ARROW: 123,
|
|
318
|
+
RIGHT_ARROW: 124,
|
|
319
|
+
DOWN_ARROW: 125,
|
|
320
|
+
UP_ARROW: 126,
|
|
321
|
+
F1: 122,
|
|
322
|
+
F2: 120,
|
|
323
|
+
F3: 99,
|
|
324
|
+
F4: 118,
|
|
325
|
+
F5: 96,
|
|
326
|
+
F6: 97,
|
|
327
|
+
F7: 98,
|
|
328
|
+
F8: 100,
|
|
329
|
+
F9: 101,
|
|
330
|
+
F10: 109,
|
|
331
|
+
F11: 103,
|
|
332
|
+
F12: 111,
|
|
333
|
+
HOME: 115,
|
|
334
|
+
END: 119,
|
|
335
|
+
PAGE_UP: 116,
|
|
336
|
+
PAGE_DOWN: 121
|
|
337
|
+
};
|
|
338
|
+
async function getSafariURL() {
|
|
339
|
+
const script = `
|
|
340
|
+
tell application "Safari"
|
|
341
|
+
return URL of current tab of front window
|
|
342
|
+
end tell
|
|
343
|
+
`;
|
|
344
|
+
const result = await executeAppleScript(script);
|
|
345
|
+
if (!result.success) {
|
|
346
|
+
throw new Error(`Failed to get Safari URL: ${result.stderr}`);
|
|
347
|
+
}
|
|
348
|
+
return result.stdout;
|
|
349
|
+
}
|
|
350
|
+
async function setSafariURL(url) {
|
|
351
|
+
const script = `
|
|
352
|
+
tell application "Safari"
|
|
353
|
+
set URL of current tab of front window to "${escapeString(url)}"
|
|
354
|
+
end tell
|
|
355
|
+
`;
|
|
356
|
+
return executeAppleScript(script);
|
|
357
|
+
}
|
|
358
|
+
async function getSafariTabTitles() {
|
|
359
|
+
const script = `
|
|
360
|
+
tell application "Safari"
|
|
361
|
+
set tabTitles to name of every tab of front window
|
|
362
|
+
return tabTitles as text
|
|
363
|
+
end tell
|
|
364
|
+
`;
|
|
365
|
+
const result = await executeAppleScript(script);
|
|
366
|
+
if (!result.success) {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
return result.stdout.split(", ").filter((t) => t.trim());
|
|
370
|
+
}
|
|
371
|
+
async function executeSafariJS(jsCode) {
|
|
372
|
+
const script = `
|
|
373
|
+
tell application "Safari"
|
|
374
|
+
do JavaScript "${escapeString(jsCode)}" in current tab of front window
|
|
375
|
+
end tell
|
|
376
|
+
`;
|
|
377
|
+
const result = await executeAppleScript(script);
|
|
378
|
+
if (!result.success) {
|
|
379
|
+
throw new Error(`Failed to execute Safari JS: ${result.stderr}`);
|
|
380
|
+
}
|
|
381
|
+
return result.stdout;
|
|
382
|
+
}
|
|
383
|
+
async function getFinderSelection() {
|
|
384
|
+
const script = `
|
|
385
|
+
tell application "Finder"
|
|
386
|
+
set selectedItems to selection
|
|
387
|
+
set output to ""
|
|
388
|
+
repeat with theItem in selectedItems
|
|
389
|
+
set itemName to name of theItem
|
|
390
|
+
set itemPath to POSIX path of (theItem as alias)
|
|
391
|
+
set itemKind to kind of theItem
|
|
392
|
+
set output to output & itemName & "|" & itemPath & "|" & itemKind & linefeed
|
|
393
|
+
end repeat
|
|
394
|
+
return output
|
|
395
|
+
end tell
|
|
396
|
+
`;
|
|
397
|
+
const result = await executeAppleScript(script);
|
|
398
|
+
if (!result.success || !result.stdout.trim()) {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
return result.stdout.split(`
|
|
402
|
+
`).filter((line) => line.trim()).map((line) => {
|
|
403
|
+
const [name, path, kind] = line.split("|");
|
|
404
|
+
const type = kind?.toLowerCase().includes("folder") ? "folder" : kind?.toLowerCase().includes("application") ? "application" : "file";
|
|
405
|
+
return {
|
|
406
|
+
name,
|
|
407
|
+
path,
|
|
408
|
+
type,
|
|
409
|
+
exists: true
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
async function openFinderWindow(path) {
|
|
414
|
+
const script = `
|
|
415
|
+
tell application "Finder"
|
|
416
|
+
activate
|
|
417
|
+
open POSIX file "${escapeString(path)}"
|
|
418
|
+
end tell
|
|
419
|
+
`;
|
|
420
|
+
return executeAppleScript(script);
|
|
421
|
+
}
|
|
422
|
+
async function createFolder(parentPath, folderName) {
|
|
423
|
+
const script = `
|
|
424
|
+
tell application "Finder"
|
|
425
|
+
make new folder at POSIX file "${escapeString(parentPath)}" with properties {name:"${escapeString(folderName)}"}
|
|
426
|
+
end tell
|
|
427
|
+
`;
|
|
428
|
+
return executeAppleScript(script);
|
|
429
|
+
}
|
|
430
|
+
async function moveToTrash(path) {
|
|
431
|
+
const script = `
|
|
432
|
+
tell application "Finder"
|
|
433
|
+
delete POSIX file "${escapeString(path)}"
|
|
434
|
+
end tell
|
|
435
|
+
`;
|
|
436
|
+
return executeAppleScript(script);
|
|
437
|
+
}
|
|
438
|
+
async function getDisplayInfo() {
|
|
439
|
+
const script = `
|
|
440
|
+
ObjC.import('Cocoa');
|
|
441
|
+
var screens = $.NSScreen.screens;
|
|
442
|
+
var count = screens.count;
|
|
443
|
+
var result = [];
|
|
444
|
+
for (var i = 0; i < count; i++) {
|
|
445
|
+
var screen = screens.objectAtIndex(i);
|
|
446
|
+
var frame = screen.frame;
|
|
447
|
+
result.push({
|
|
448
|
+
id: i,
|
|
449
|
+
width: frame.size.width,
|
|
450
|
+
height: frame.size.height,
|
|
451
|
+
main: i === 0
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
JSON.stringify(result);
|
|
455
|
+
`;
|
|
456
|
+
const result = await executeJXA(script);
|
|
457
|
+
if (!result.success) {
|
|
458
|
+
const fallbackScript = `
|
|
459
|
+
tell application "System Events"
|
|
460
|
+
set screenWidth to word 3 of (do shell script "system_profiler SPDisplaysDataType | grep Resolution")
|
|
461
|
+
set screenHeight to word 5 of (do shell script "system_profiler SPDisplaysDataType | grep Resolution")
|
|
462
|
+
return screenWidth & "x" & screenHeight
|
|
463
|
+
end tell
|
|
464
|
+
`;
|
|
465
|
+
const fallback = await executeAppleScript(fallbackScript);
|
|
466
|
+
const [width, height] = fallback.stdout.split("x").map((n) => parseInt(n));
|
|
467
|
+
return [
|
|
468
|
+
{
|
|
469
|
+
id: 0,
|
|
470
|
+
width: width || 1920,
|
|
471
|
+
height: height || 1080,
|
|
472
|
+
main: true
|
|
473
|
+
}
|
|
474
|
+
];
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
return JSON.parse(result.stdout);
|
|
478
|
+
} catch {
|
|
479
|
+
return [
|
|
480
|
+
{
|
|
481
|
+
id: 0,
|
|
482
|
+
width: 1920,
|
|
483
|
+
height: 1080,
|
|
484
|
+
main: true
|
|
485
|
+
}
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function getScreenResolution() {
|
|
490
|
+
const displays = await getDisplayInfo();
|
|
491
|
+
const mainDisplay = displays.find((d) => d.main) || displays[0];
|
|
492
|
+
return {
|
|
493
|
+
width: mainDisplay?.width || 1920,
|
|
494
|
+
height: mainDisplay?.height || 1080
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function isMacOS() {
|
|
498
|
+
return process.platform === "darwin";
|
|
499
|
+
}
|
|
500
|
+
function ensureMacOS() {
|
|
501
|
+
if (!isMacOS()) {
|
|
502
|
+
throw new Error("osascript MCP only works on macOS");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function escapeString(str) {
|
|
506
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
507
|
+
}
|
|
508
|
+
async function delay(seconds) {
|
|
509
|
+
await executeAppleScript(`delay ${seconds}`);
|
|
510
|
+
}
|
|
511
|
+
async function sayText(text, voice, rate) {
|
|
512
|
+
let script = `say "${escapeString(text)}"`;
|
|
513
|
+
if (voice)
|
|
514
|
+
script += ` using "${voice}"`;
|
|
515
|
+
if (rate)
|
|
516
|
+
script += ` speaking rate ${rate}`;
|
|
517
|
+
return executeAppleScript(script);
|
|
518
|
+
}
|
|
519
|
+
async function beep(count = 1) {
|
|
520
|
+
return executeAppleScript(`beep ${count}`);
|
|
521
|
+
}
|
|
522
|
+
async function getSystemDateTime() {
|
|
523
|
+
const script = `
|
|
524
|
+
get current date
|
|
525
|
+
`;
|
|
526
|
+
const result = await executeAppleScript(script);
|
|
527
|
+
if (!result.success) {
|
|
528
|
+
throw new Error(`Failed to get system date: ${result.stderr}`);
|
|
529
|
+
}
|
|
530
|
+
return result.stdout;
|
|
531
|
+
}
|
|
532
|
+
export {
|
|
533
|
+
typeText,
|
|
534
|
+
setWindowBounds,
|
|
535
|
+
setVolume,
|
|
536
|
+
setSafariURL,
|
|
537
|
+
setClipboard,
|
|
538
|
+
sendKeystroke,
|
|
539
|
+
sendKeyCode,
|
|
540
|
+
sayText,
|
|
541
|
+
quitApplication,
|
|
542
|
+
pressKeyCombination,
|
|
543
|
+
openFinderWindow,
|
|
544
|
+
moveToTrash,
|
|
545
|
+
minimizeWindow,
|
|
546
|
+
listRunningApplications,
|
|
547
|
+
launchApplication,
|
|
548
|
+
isMacOS,
|
|
549
|
+
getVolume,
|
|
550
|
+
getSystemDateTime,
|
|
551
|
+
getScreenResolution,
|
|
552
|
+
getSafariURL,
|
|
553
|
+
getSafariTabTitles,
|
|
554
|
+
getFrontmostApplication,
|
|
555
|
+
getFinderSelection,
|
|
556
|
+
getDisplayInfo,
|
|
557
|
+
getClipboard,
|
|
558
|
+
getApplicationWindows,
|
|
559
|
+
getApplicationInfo,
|
|
560
|
+
executeSafariJS,
|
|
561
|
+
executeOsascript,
|
|
562
|
+
executeJXA,
|
|
563
|
+
executeAppleScript,
|
|
564
|
+
escapeString,
|
|
565
|
+
ensureMacOS,
|
|
566
|
+
displayNotification,
|
|
567
|
+
displayDialog,
|
|
568
|
+
delay,
|
|
569
|
+
createFolder,
|
|
570
|
+
clearClipboard,
|
|
571
|
+
beep,
|
|
572
|
+
activateApplication,
|
|
573
|
+
KeyCodes
|
|
574
|
+
};
|