@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/src/index.ts
ADDED
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview osascript MCP - macOS automation via AppleScript/JXA
|
|
3
|
+
*
|
|
4
|
+
* Provides programmatic access to macOS automation through osascript.
|
|
5
|
+
* Supports AppleScript and JavaScript for Automation (JXA).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
OsascriptResult,
|
|
12
|
+
ExecuteOptions,
|
|
13
|
+
DialogOptions,
|
|
14
|
+
DialogResult,
|
|
15
|
+
NotificationOptions,
|
|
16
|
+
WindowInfo,
|
|
17
|
+
ApplicationInfo,
|
|
18
|
+
KeystrokeOptions,
|
|
19
|
+
KeyCodeOptions,
|
|
20
|
+
VolumeInfo,
|
|
21
|
+
DisplayInfo,
|
|
22
|
+
FinderItem,
|
|
23
|
+
ScriptLanguage,
|
|
24
|
+
} from "./types.js";
|
|
25
|
+
|
|
26
|
+
export type {
|
|
27
|
+
OsascriptResult,
|
|
28
|
+
ExecuteOptions,
|
|
29
|
+
DialogOptions,
|
|
30
|
+
DialogResult,
|
|
31
|
+
NotificationOptions,
|
|
32
|
+
WindowInfo,
|
|
33
|
+
ApplicationInfo,
|
|
34
|
+
KeystrokeOptions,
|
|
35
|
+
KeyCodeOptions,
|
|
36
|
+
VolumeInfo,
|
|
37
|
+
DisplayInfo,
|
|
38
|
+
FinderItem,
|
|
39
|
+
ScriptLanguage,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// ==============
|
|
43
|
+
// Core Executor
|
|
44
|
+
// ==============
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Execute osascript command
|
|
48
|
+
*/
|
|
49
|
+
export async function executeOsascript(
|
|
50
|
+
script: string,
|
|
51
|
+
options: ExecuteOptions = {}
|
|
52
|
+
): Promise<OsascriptResult> {
|
|
53
|
+
const { language = "applescript", timeout = 30000 } = options;
|
|
54
|
+
|
|
55
|
+
const args: string[] = [];
|
|
56
|
+
if (language === "javascript") {
|
|
57
|
+
args.push("-l", "JavaScript");
|
|
58
|
+
}
|
|
59
|
+
args.push("-e", script);
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
const proc = spawn("osascript", args, { timeout });
|
|
63
|
+
let stdout = "";
|
|
64
|
+
let stderr = "";
|
|
65
|
+
|
|
66
|
+
proc.stdout.on("data", (data) => {
|
|
67
|
+
stdout += data.toString();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
proc.stderr.on("data", (data) => {
|
|
71
|
+
stderr += data.toString();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
proc.kill();
|
|
76
|
+
resolve({
|
|
77
|
+
success: false,
|
|
78
|
+
stdout: stdout.trim(),
|
|
79
|
+
stderr: "Timeout exceeded",
|
|
80
|
+
exitCode: 1,
|
|
81
|
+
});
|
|
82
|
+
}, timeout);
|
|
83
|
+
|
|
84
|
+
proc.on("close", (code) => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
resolve({
|
|
87
|
+
success: code === 0,
|
|
88
|
+
stdout: stdout.trim(),
|
|
89
|
+
stderr: stderr.trim(),
|
|
90
|
+
exitCode: code ?? 1,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
proc.on("error", (error) => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
resolve({
|
|
97
|
+
success: false,
|
|
98
|
+
stdout: "",
|
|
99
|
+
stderr: error.message,
|
|
100
|
+
exitCode: 1,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Execute AppleScript
|
|
108
|
+
*/
|
|
109
|
+
export async function executeAppleScript(script: string, timeout = 30000): Promise<OsascriptResult> {
|
|
110
|
+
return executeOsascript(script, { language: "applescript", timeout });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Execute JavaScript for Automation (JXA)
|
|
115
|
+
*/
|
|
116
|
+
export async function executeJXA(script: string, timeout = 30000): Promise<OsascriptResult> {
|
|
117
|
+
return executeOsascript(script, { language: "javascript", timeout });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ==============
|
|
121
|
+
// System Operations
|
|
122
|
+
// ==============
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Display a macOS notification
|
|
126
|
+
*/
|
|
127
|
+
export async function displayNotification(options: NotificationOptions): Promise<OsascriptResult> {
|
|
128
|
+
const { title = "Notification", subtitle, message, soundName } = options;
|
|
129
|
+
|
|
130
|
+
let script = `display notification "${escapeString(message)}"`;
|
|
131
|
+
if (title) script += ` with title "${escapeString(title)}"`;
|
|
132
|
+
if (subtitle) script += ` subtitle "${escapeString(subtitle)}"`;
|
|
133
|
+
if (soundName) script += ` sound name "${escapeString(soundName)}"`;
|
|
134
|
+
|
|
135
|
+
return executeAppleScript(script);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Display a dialog
|
|
140
|
+
*/
|
|
141
|
+
export async function displayDialog(options: DialogOptions): Promise<DialogResult> {
|
|
142
|
+
const { title, message, buttons = ["OK"], defaultButton = 1, icon, givingUpAfter } = options;
|
|
143
|
+
|
|
144
|
+
let script = `display dialog "${escapeString(message)}"`;
|
|
145
|
+
script += ` buttons ${JSON.stringify(buttons)}`;
|
|
146
|
+
script += ` default button ${defaultButton}`;
|
|
147
|
+
if (icon) script += ` with icon ${icon}`;
|
|
148
|
+
if (givingUpAfter) script += ` giving up after ${givingUpAfter}`;
|
|
149
|
+
|
|
150
|
+
const result = await executeAppleScript(script);
|
|
151
|
+
|
|
152
|
+
if (!result.success) {
|
|
153
|
+
throw new Error(`Dialog failed: ${result.stderr}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Parse result: "button returned:OK" or "button returned:OK, gave up:true"
|
|
157
|
+
const buttonMatch = result.stdout.match(/button returned:(\w+)/);
|
|
158
|
+
const gaveUpMatch = result.stdout.match(/gave up:(\w+)/);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
buttonReturned: buttonMatch ? buttonMatch[1] : buttons[0],
|
|
162
|
+
gaveUp: gaveUpMatch ? gaveUpMatch[1] === "true" : false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get system volume
|
|
168
|
+
*/
|
|
169
|
+
export async function getVolume(): Promise<VolumeInfo> {
|
|
170
|
+
const script = `
|
|
171
|
+
output volume of (get volume settings) as integer
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
const volumeResult = await executeAppleScript(script);
|
|
175
|
+
const mutedScript = `
|
|
176
|
+
output muted of (get volume settings) as boolean
|
|
177
|
+
`;
|
|
178
|
+
const mutedResult = await executeAppleScript(mutedScript);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
volume: parseInt(volumeResult.stdout) || 0,
|
|
182
|
+
muted: mutedResult.stdout.toLowerCase() === "true",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Set system volume (0-100)
|
|
188
|
+
*/
|
|
189
|
+
export async function setVolume(volume: number, muted?: boolean): Promise<OsascriptResult> {
|
|
190
|
+
const safeVolume = Math.max(0, Math.min(100, Math.round(volume)));
|
|
191
|
+
let script = `set volume output volume ${safeVolume}`;
|
|
192
|
+
if (muted !== undefined) {
|
|
193
|
+
script += muted ? " with output muted" : " without output muted";
|
|
194
|
+
}
|
|
195
|
+
return executeAppleScript(script);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get clipboard contents
|
|
200
|
+
*/
|
|
201
|
+
export async function getClipboard(): Promise<string> {
|
|
202
|
+
const result = await executeAppleScript("the clipboard");
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
throw new Error(`Failed to get clipboard: ${result.stderr}`);
|
|
205
|
+
}
|
|
206
|
+
return result.stdout;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set clipboard contents
|
|
211
|
+
*/
|
|
212
|
+
export async function setClipboard(text: string): Promise<OsascriptResult> {
|
|
213
|
+
const script = `set the clipboard to "${escapeString(text)}"`;
|
|
214
|
+
return executeAppleScript(script);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clear clipboard
|
|
219
|
+
*/
|
|
220
|
+
export async function clearClipboard(): Promise<OsascriptResult> {
|
|
221
|
+
return executeAppleScript("set the clipboard to \"\"");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ==============
|
|
225
|
+
// Application Control
|
|
226
|
+
// ==============
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* List running applications
|
|
230
|
+
*/
|
|
231
|
+
export async function listRunningApplications(): Promise<ApplicationInfo[]> {
|
|
232
|
+
const script = `
|
|
233
|
+
tell application "System Events"
|
|
234
|
+
set appList to name of every process whose background only is false
|
|
235
|
+
set output to ""
|
|
236
|
+
repeat with appName in appList
|
|
237
|
+
set output to output & appName & linefeed
|
|
238
|
+
end repeat
|
|
239
|
+
return output
|
|
240
|
+
end tell
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
const result = await executeAppleScript(script);
|
|
244
|
+
if (!result.success) {
|
|
245
|
+
throw new Error(`Failed to list applications: ${result.stderr}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const apps = result.stdout
|
|
249
|
+
.split("\n")
|
|
250
|
+
.filter((name) => name.trim())
|
|
251
|
+
.map((name) => ({
|
|
252
|
+
name: name.trim(),
|
|
253
|
+
running: true,
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
return apps;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get detailed application info
|
|
261
|
+
*/
|
|
262
|
+
export async function getApplicationInfo(appName: string): Promise<ApplicationInfo> {
|
|
263
|
+
const script = `
|
|
264
|
+
tell application "System Events"
|
|
265
|
+
if exists process "${escapeString(appName)}" then
|
|
266
|
+
set theProcess to process "${escapeString(appName)}"
|
|
267
|
+
set theName to name of theProcess
|
|
268
|
+
set thePath to POSIX path of (application file of theProcess as alias)
|
|
269
|
+
set thePID to unix id of theProcess
|
|
270
|
+
set windowCount to count of windows of theProcess
|
|
271
|
+
return theName & "|" & thePath & "|" & thePID & "|" & windowCount
|
|
272
|
+
else
|
|
273
|
+
return "NOT_RUNNING"
|
|
274
|
+
end if
|
|
275
|
+
end tell
|
|
276
|
+
`;
|
|
277
|
+
|
|
278
|
+
const result = await executeAppleScript(script);
|
|
279
|
+
if (!result.success || result.stdout === "NOT_RUNNING") {
|
|
280
|
+
return {
|
|
281
|
+
name: appName,
|
|
282
|
+
running: false,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const [name, path, pid, windowCount] = result.stdout.split("|");
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
name,
|
|
290
|
+
path,
|
|
291
|
+
processId: parseInt(pid),
|
|
292
|
+
running: true,
|
|
293
|
+
windowCount: parseInt(windowCount),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Activate (bring to front) an application
|
|
299
|
+
*/
|
|
300
|
+
export async function activateApplication(appName: string): Promise<OsascriptResult> {
|
|
301
|
+
const script = `
|
|
302
|
+
tell application "${escapeString(appName)}"
|
|
303
|
+
activate
|
|
304
|
+
end tell
|
|
305
|
+
`;
|
|
306
|
+
return executeAppleScript(script);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Quit an application
|
|
311
|
+
*/
|
|
312
|
+
export async function quitApplication(appName: string, force = false): Promise<OsascriptResult> {
|
|
313
|
+
let script: string;
|
|
314
|
+
if (force) {
|
|
315
|
+
script = `
|
|
316
|
+
tell application "System Events"
|
|
317
|
+
set theProcess to process "${escapeString(appName)}"
|
|
318
|
+
set thePID to unix id of theProcess
|
|
319
|
+
do shell script "kill -9 " & thePID
|
|
320
|
+
end tell
|
|
321
|
+
`;
|
|
322
|
+
} else {
|
|
323
|
+
script = `
|
|
324
|
+
tell application "${escapeString(appName)}"
|
|
325
|
+
quit
|
|
326
|
+
end tell
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
return executeAppleScript(script);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Launch an application
|
|
334
|
+
*/
|
|
335
|
+
export async function launchApplication(appName: string): Promise<OsascriptResult> {
|
|
336
|
+
const script = `
|
|
337
|
+
tell application "${escapeString(appName)}"
|
|
338
|
+
launch
|
|
339
|
+
end tell
|
|
340
|
+
`;
|
|
341
|
+
return executeAppleScript(script);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get frontmost application name
|
|
346
|
+
*/
|
|
347
|
+
export async function getFrontmostApplication(): Promise<string> {
|
|
348
|
+
const script = `
|
|
349
|
+
tell application "System Events"
|
|
350
|
+
name of first process whose frontmost is true
|
|
351
|
+
end tell
|
|
352
|
+
`;
|
|
353
|
+
|
|
354
|
+
const result = await executeAppleScript(script);
|
|
355
|
+
if (!result.success) {
|
|
356
|
+
throw new Error(`Failed to get frontmost app: ${result.stderr}`);
|
|
357
|
+
}
|
|
358
|
+
return result.stdout;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ==============
|
|
362
|
+
// Window Management
|
|
363
|
+
// ==============
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get window list for an application
|
|
367
|
+
*/
|
|
368
|
+
export async function getApplicationWindows(appName: string): Promise<WindowInfo[]> {
|
|
369
|
+
const script = `
|
|
370
|
+
tell application "System Events"
|
|
371
|
+
tell process "${escapeString(appName)}"
|
|
372
|
+
set windowList to {}
|
|
373
|
+
repeat with theWindow in windows
|
|
374
|
+
set windowName to name of theWindow
|
|
375
|
+
set windowId to id of theWindow
|
|
376
|
+
set windowIndex to index of theWindow
|
|
377
|
+
set end of windowList to windowName & "|" & windowId & "|" & windowIndex
|
|
378
|
+
end repeat
|
|
379
|
+
return windowList as text
|
|
380
|
+
end tell
|
|
381
|
+
end tell
|
|
382
|
+
`;
|
|
383
|
+
|
|
384
|
+
const result = await executeAppleScript(script);
|
|
385
|
+
if (!result.success) {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!result.stdout.trim()) {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const windows = result.stdout
|
|
394
|
+
.split(", ")
|
|
395
|
+
.filter((w) => w.trim())
|
|
396
|
+
.map((w) => {
|
|
397
|
+
const [name, id, index] = w.split("|");
|
|
398
|
+
return {
|
|
399
|
+
name: name || null,
|
|
400
|
+
id: parseInt(id) || 0,
|
|
401
|
+
index: parseInt(index) || 0,
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return windows;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Set window bounds (position and size)
|
|
410
|
+
*/
|
|
411
|
+
export async function setWindowBounds(
|
|
412
|
+
appName: string,
|
|
413
|
+
windowIndex: number,
|
|
414
|
+
bounds: { x: number; y: number; width: number; height: number }
|
|
415
|
+
): Promise<OsascriptResult> {
|
|
416
|
+
const script = `
|
|
417
|
+
tell application "System Events"
|
|
418
|
+
tell process "${escapeString(appName)}"
|
|
419
|
+
set position of window ${windowIndex} to {${bounds.x}, ${bounds.y}}
|
|
420
|
+
set size of window ${windowIndex} to {${bounds.width}, ${bounds.height}}
|
|
421
|
+
end tell
|
|
422
|
+
end tell
|
|
423
|
+
`;
|
|
424
|
+
return executeAppleScript(script);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Minimize window
|
|
429
|
+
*/
|
|
430
|
+
export async function minimizeWindow(
|
|
431
|
+
appName: string,
|
|
432
|
+
windowIndex: number
|
|
433
|
+
): Promise<OsascriptResult> {
|
|
434
|
+
const script = `
|
|
435
|
+
tell application "System Events"
|
|
436
|
+
tell process "${escapeString(appName)}"
|
|
437
|
+
set value of attribute "AXMinimized" of window ${windowIndex} to true
|
|
438
|
+
end tell
|
|
439
|
+
end tell
|
|
440
|
+
`;
|
|
441
|
+
return executeAppleScript(script);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ==============
|
|
445
|
+
// Keyboard Input
|
|
446
|
+
// ==============
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Send keystroke (text input)
|
|
450
|
+
*/
|
|
451
|
+
export async function sendKeystroke(options: KeystrokeOptions): Promise<OsascriptResult> {
|
|
452
|
+
const { key, modifiers = [], delay = 0 } = options;
|
|
453
|
+
|
|
454
|
+
const modifierStr = modifiers.map((m) => m + " down").join(", ");
|
|
455
|
+
const usingStr = modifierStr ? ` using {${modifierStr}}` : "";
|
|
456
|
+
|
|
457
|
+
const script = `
|
|
458
|
+
tell application "System Events"
|
|
459
|
+
keystroke "${escapeString(key)}"${usingStr}
|
|
460
|
+
${delay > 0 ? `delay ${delay}` : ""}
|
|
461
|
+
end tell
|
|
462
|
+
`;
|
|
463
|
+
|
|
464
|
+
return executeAppleScript(script);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Send key code (for special keys)
|
|
469
|
+
*/
|
|
470
|
+
export async function sendKeyCode(options: KeyCodeOptions): Promise<OsascriptResult> {
|
|
471
|
+
const { keyCode, modifiers = [] } = options;
|
|
472
|
+
|
|
473
|
+
const modifierStr = modifiers.map((m) => m + " down").join(", ");
|
|
474
|
+
const usingStr = modifierStr ? ` using {${modifierStr}}` : "";
|
|
475
|
+
|
|
476
|
+
const script = `
|
|
477
|
+
tell application "System Events"
|
|
478
|
+
key code ${keyCode}${usingStr}
|
|
479
|
+
end tell
|
|
480
|
+
`;
|
|
481
|
+
|
|
482
|
+
return executeAppleScript(script);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Type text (simulate typing)
|
|
487
|
+
*/
|
|
488
|
+
export async function typeText(text: string, delayBetweenChars = 0.01): Promise<OsascriptResult> {
|
|
489
|
+
const script = `
|
|
490
|
+
tell application "System Events"
|
|
491
|
+
keystroke "${escapeString(text)}"
|
|
492
|
+
${delayBetweenChars > 0 ? `delay ${delayBetweenChars}` : ""}
|
|
493
|
+
end tell
|
|
494
|
+
`;
|
|
495
|
+
|
|
496
|
+
return executeAppleScript(script);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Press key combination (e.g., Command+C)
|
|
501
|
+
*/
|
|
502
|
+
export async function pressKeyCombination(
|
|
503
|
+
key: string,
|
|
504
|
+
modifiers: ("command" | "shift" | "option" | "control")[]
|
|
505
|
+
): Promise<OsascriptResult> {
|
|
506
|
+
return sendKeystroke({ key, modifiers });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Common key codes
|
|
510
|
+
export const KeyCodes = {
|
|
511
|
+
RETURN: 36,
|
|
512
|
+
TAB: 48,
|
|
513
|
+
SPACE: 49,
|
|
514
|
+
DELETE: 51,
|
|
515
|
+
ESCAPE: 53,
|
|
516
|
+
LEFT_ARROW: 123,
|
|
517
|
+
RIGHT_ARROW: 124,
|
|
518
|
+
DOWN_ARROW: 125,
|
|
519
|
+
UP_ARROW: 126,
|
|
520
|
+
F1: 122,
|
|
521
|
+
F2: 120,
|
|
522
|
+
F3: 99,
|
|
523
|
+
F4: 118,
|
|
524
|
+
F5: 96,
|
|
525
|
+
F6: 97,
|
|
526
|
+
F7: 98,
|
|
527
|
+
F8: 100,
|
|
528
|
+
F9: 101,
|
|
529
|
+
F10: 109,
|
|
530
|
+
F11: 103,
|
|
531
|
+
F12: 111,
|
|
532
|
+
HOME: 115,
|
|
533
|
+
END: 119,
|
|
534
|
+
PAGE_UP: 116,
|
|
535
|
+
PAGE_DOWN: 121,
|
|
536
|
+
} as const;
|
|
537
|
+
|
|
538
|
+
// ==============
|
|
539
|
+
// Safari
|
|
540
|
+
// ==============
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Get current Safari URL
|
|
544
|
+
*/
|
|
545
|
+
export async function getSafariURL(): Promise<string> {
|
|
546
|
+
const script = `
|
|
547
|
+
tell application "Safari"
|
|
548
|
+
return URL of current tab of front window
|
|
549
|
+
end tell
|
|
550
|
+
`;
|
|
551
|
+
|
|
552
|
+
const result = await executeAppleScript(script);
|
|
553
|
+
if (!result.success) {
|
|
554
|
+
throw new Error(`Failed to get Safari URL: ${result.stderr}`);
|
|
555
|
+
}
|
|
556
|
+
return result.stdout;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Set Safari URL (navigate)
|
|
561
|
+
*/
|
|
562
|
+
export async function setSafariURL(url: string): Promise<OsascriptResult> {
|
|
563
|
+
const script = `
|
|
564
|
+
tell application "Safari"
|
|
565
|
+
set URL of current tab of front window to "${escapeString(url)}"
|
|
566
|
+
end tell
|
|
567
|
+
`;
|
|
568
|
+
return executeAppleScript(script);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Get Safari tab titles
|
|
573
|
+
*/
|
|
574
|
+
export async function getSafariTabTitles(): Promise<string[]> {
|
|
575
|
+
const script = `
|
|
576
|
+
tell application "Safari"
|
|
577
|
+
set tabTitles to name of every tab of front window
|
|
578
|
+
return tabTitles as text
|
|
579
|
+
end tell
|
|
580
|
+
`;
|
|
581
|
+
|
|
582
|
+
const result = await executeAppleScript(script);
|
|
583
|
+
if (!result.success) {
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return result.stdout.split(", ").filter((t) => t.trim());
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Execute JavaScript in Safari
|
|
592
|
+
*/
|
|
593
|
+
export async function executeSafariJS(jsCode: string): Promise<string> {
|
|
594
|
+
const script = `
|
|
595
|
+
tell application "Safari"
|
|
596
|
+
do JavaScript "${escapeString(jsCode)}" in current tab of front window
|
|
597
|
+
end tell
|
|
598
|
+
`;
|
|
599
|
+
|
|
600
|
+
const result = await executeAppleScript(script);
|
|
601
|
+
if (!result.success) {
|
|
602
|
+
throw new Error(`Failed to execute Safari JS: ${result.stderr}`);
|
|
603
|
+
}
|
|
604
|
+
return result.stdout;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ==============
|
|
608
|
+
// Finder
|
|
609
|
+
// ==============
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Get Finder selection
|
|
613
|
+
*/
|
|
614
|
+
export async function getFinderSelection(): Promise<FinderItem[]> {
|
|
615
|
+
const script = `
|
|
616
|
+
tell application "Finder"
|
|
617
|
+
set selectedItems to selection
|
|
618
|
+
set output to ""
|
|
619
|
+
repeat with theItem in selectedItems
|
|
620
|
+
set itemName to name of theItem
|
|
621
|
+
set itemPath to POSIX path of (theItem as alias)
|
|
622
|
+
set itemKind to kind of theItem
|
|
623
|
+
set output to output & itemName & "|" & itemPath & "|" & itemKind & linefeed
|
|
624
|
+
end repeat
|
|
625
|
+
return output
|
|
626
|
+
end tell
|
|
627
|
+
`;
|
|
628
|
+
|
|
629
|
+
const result = await executeAppleScript(script);
|
|
630
|
+
if (!result.success || !result.stdout.trim()) {
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return result.stdout
|
|
635
|
+
.split("\n")
|
|
636
|
+
.filter((line) => line.trim())
|
|
637
|
+
.map((line) => {
|
|
638
|
+
const [name, path, kind] = line.split("|");
|
|
639
|
+
const type = kind?.toLowerCase().includes("folder")
|
|
640
|
+
? "folder"
|
|
641
|
+
: kind?.toLowerCase().includes("application")
|
|
642
|
+
? "application"
|
|
643
|
+
: "file";
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
name,
|
|
647
|
+
path,
|
|
648
|
+
type: type as FinderItem["type"],
|
|
649
|
+
exists: true,
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Open Finder window at path
|
|
656
|
+
*/
|
|
657
|
+
export async function openFinderWindow(path: string): Promise<OsascriptResult> {
|
|
658
|
+
const script = `
|
|
659
|
+
tell application "Finder"
|
|
660
|
+
activate
|
|
661
|
+
open POSIX file "${escapeString(path)}"
|
|
662
|
+
end tell
|
|
663
|
+
`;
|
|
664
|
+
return executeAppleScript(script);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Create new folder in Finder
|
|
669
|
+
*/
|
|
670
|
+
export async function createFolder(
|
|
671
|
+
parentPath: string,
|
|
672
|
+
folderName: string
|
|
673
|
+
): Promise<OsascriptResult> {
|
|
674
|
+
const script = `
|
|
675
|
+
tell application "Finder"
|
|
676
|
+
make new folder at POSIX file "${escapeString(parentPath)}" with properties {name:"${escapeString(folderName)}"}
|
|
677
|
+
end tell
|
|
678
|
+
`;
|
|
679
|
+
return executeAppleScript(script);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Move file/folder to trash
|
|
684
|
+
*/
|
|
685
|
+
export async function moveToTrash(path: string): Promise<OsascriptResult> {
|
|
686
|
+
const script = `
|
|
687
|
+
tell application "Finder"
|
|
688
|
+
delete POSIX file "${escapeString(path)}"
|
|
689
|
+
end tell
|
|
690
|
+
`;
|
|
691
|
+
return executeAppleScript(script);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ==============
|
|
695
|
+
// Display
|
|
696
|
+
// ==============
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Get display information
|
|
700
|
+
*/
|
|
701
|
+
export async function getDisplayInfo(): Promise<DisplayInfo[]> {
|
|
702
|
+
// Using JXA for better display info
|
|
703
|
+
const script = `
|
|
704
|
+
ObjC.import('Cocoa');
|
|
705
|
+
var screens = $.NSScreen.screens;
|
|
706
|
+
var count = screens.count;
|
|
707
|
+
var result = [];
|
|
708
|
+
for (var i = 0; i < count; i++) {
|
|
709
|
+
var screen = screens.objectAtIndex(i);
|
|
710
|
+
var frame = screen.frame;
|
|
711
|
+
result.push({
|
|
712
|
+
id: i,
|
|
713
|
+
width: frame.size.width,
|
|
714
|
+
height: frame.size.height,
|
|
715
|
+
main: i === 0
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
JSON.stringify(result);
|
|
719
|
+
`;
|
|
720
|
+
|
|
721
|
+
const result = await executeJXA(script);
|
|
722
|
+
if (!result.success) {
|
|
723
|
+
// Fallback to AppleScript
|
|
724
|
+
const fallbackScript = `
|
|
725
|
+
tell application "System Events"
|
|
726
|
+
set screenWidth to word 3 of (do shell script "system_profiler SPDisplaysDataType | grep Resolution")
|
|
727
|
+
set screenHeight to word 5 of (do shell script "system_profiler SPDisplaysDataType | grep Resolution")
|
|
728
|
+
return screenWidth & "x" & screenHeight
|
|
729
|
+
end tell
|
|
730
|
+
`;
|
|
731
|
+
const fallback = await executeAppleScript(fallbackScript);
|
|
732
|
+
const [width, height] = fallback.stdout.split("x").map((n) => parseInt(n));
|
|
733
|
+
|
|
734
|
+
return [
|
|
735
|
+
{
|
|
736
|
+
id: 0,
|
|
737
|
+
width: width || 1920,
|
|
738
|
+
height: height || 1080,
|
|
739
|
+
main: true,
|
|
740
|
+
},
|
|
741
|
+
];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
return JSON.parse(result.stdout);
|
|
746
|
+
} catch {
|
|
747
|
+
return [
|
|
748
|
+
{
|
|
749
|
+
id: 0,
|
|
750
|
+
width: 1920,
|
|
751
|
+
height: 1080,
|
|
752
|
+
main: true,
|
|
753
|
+
},
|
|
754
|
+
];
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Get screen resolution
|
|
760
|
+
*/
|
|
761
|
+
export async function getScreenResolution(): Promise<{ width: number; height: number }> {
|
|
762
|
+
const displays = await getDisplayInfo();
|
|
763
|
+
const mainDisplay = displays.find((d) => d.main) || displays[0];
|
|
764
|
+
return {
|
|
765
|
+
width: mainDisplay?.width || 1920,
|
|
766
|
+
height: mainDisplay?.height || 1080,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// ==============
|
|
771
|
+
// Utilities
|
|
772
|
+
// ==============
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Check if running on macOS
|
|
776
|
+
*/
|
|
777
|
+
export function isMacOS(): boolean {
|
|
778
|
+
return process.platform === "darwin";
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Ensure running on macOS
|
|
783
|
+
*/
|
|
784
|
+
export function ensureMacOS(): void {
|
|
785
|
+
if (!isMacOS()) {
|
|
786
|
+
throw new Error("osascript MCP only works on macOS");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Escape string for AppleScript
|
|
792
|
+
*/
|
|
793
|
+
export function escapeString(str: string): string {
|
|
794
|
+
return str
|
|
795
|
+
.replace(/\\/g, "\\\\")
|
|
796
|
+
.replace(/"/g, '\\"')
|
|
797
|
+
.replace(/\n/g, "\\n")
|
|
798
|
+
.replace(/\r/g, "\\r")
|
|
799
|
+
.replace(/\t/g, "\\t");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Delay execution
|
|
804
|
+
*/
|
|
805
|
+
export async function delay(seconds: number): Promise<void> {
|
|
806
|
+
await executeAppleScript(`delay ${seconds}`);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Say text using system voice
|
|
811
|
+
*/
|
|
812
|
+
export async function sayText(
|
|
813
|
+
text: string,
|
|
814
|
+
voice?: string,
|
|
815
|
+
rate?: number
|
|
816
|
+
): Promise<OsascriptResult> {
|
|
817
|
+
let script = `say "${escapeString(text)}"`;
|
|
818
|
+
if (voice) script += ` using "${voice}"`;
|
|
819
|
+
if (rate) script += ` speaking rate ${rate}`;
|
|
820
|
+
|
|
821
|
+
return executeAppleScript(script);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Beep
|
|
826
|
+
*/
|
|
827
|
+
export async function beep(count = 1): Promise<OsascriptResult> {
|
|
828
|
+
return executeAppleScript(`beep ${count}`);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Get current date/time from system
|
|
833
|
+
*/
|
|
834
|
+
export async function getSystemDateTime(): Promise<string> {
|
|
835
|
+
const script = `
|
|
836
|
+
get current date
|
|
837
|
+
`;
|
|
838
|
+
|
|
839
|
+
const result = await executeAppleScript(script);
|
|
840
|
+
if (!result.success) {
|
|
841
|
+
throw new Error(`Failed to get system date: ${result.stderr}`);
|
|
842
|
+
}
|
|
843
|
+
return result.stdout;
|
|
844
|
+
}
|