@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
|
@@ -0,0 +1,1704 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
async function executeOsascript(script, options = {}) {
|
|
7
|
+
const { language = "applescript", timeout = 30000 } = options;
|
|
8
|
+
const args = [];
|
|
9
|
+
if (language === "javascript") {
|
|
10
|
+
args.push("-l", "JavaScript");
|
|
11
|
+
}
|
|
12
|
+
args.push("-e", script);
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const proc = spawn("osascript", args, { timeout });
|
|
15
|
+
let stdout = "";
|
|
16
|
+
let stderr = "";
|
|
17
|
+
proc.stdout.on("data", (data) => {
|
|
18
|
+
stdout += data.toString();
|
|
19
|
+
});
|
|
20
|
+
proc.stderr.on("data", (data) => {
|
|
21
|
+
stderr += data.toString();
|
|
22
|
+
});
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
proc.kill();
|
|
25
|
+
resolve({
|
|
26
|
+
success: false,
|
|
27
|
+
stdout: stdout.trim(),
|
|
28
|
+
stderr: "Timeout exceeded",
|
|
29
|
+
exitCode: 1
|
|
30
|
+
});
|
|
31
|
+
}, timeout);
|
|
32
|
+
proc.on("close", (code) => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
resolve({
|
|
35
|
+
success: code === 0,
|
|
36
|
+
stdout: stdout.trim(),
|
|
37
|
+
stderr: stderr.trim(),
|
|
38
|
+
exitCode: code ?? 1
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
proc.on("error", (error) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
resolve({
|
|
44
|
+
success: false,
|
|
45
|
+
stdout: "",
|
|
46
|
+
stderr: error.message,
|
|
47
|
+
exitCode: 1
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function executeAppleScript(script, timeout = 30000) {
|
|
53
|
+
return executeOsascript(script, { language: "applescript", timeout });
|
|
54
|
+
}
|
|
55
|
+
async function executeJXA(script, timeout = 30000) {
|
|
56
|
+
return executeOsascript(script, { language: "javascript", timeout });
|
|
57
|
+
}
|
|
58
|
+
async function displayNotification(options) {
|
|
59
|
+
const { title = "Notification", subtitle, message, soundName } = options;
|
|
60
|
+
let script = `display notification "${escapeString(message)}"`;
|
|
61
|
+
if (title)
|
|
62
|
+
script += ` with title "${escapeString(title)}"`;
|
|
63
|
+
if (subtitle)
|
|
64
|
+
script += ` subtitle "${escapeString(subtitle)}"`;
|
|
65
|
+
if (soundName)
|
|
66
|
+
script += ` sound name "${escapeString(soundName)}"`;
|
|
67
|
+
return executeAppleScript(script);
|
|
68
|
+
}
|
|
69
|
+
async function displayDialog(options) {
|
|
70
|
+
const { title, message, buttons = ["OK"], defaultButton = 1, icon, givingUpAfter } = options;
|
|
71
|
+
let script = `display dialog "${escapeString(message)}"`;
|
|
72
|
+
script += ` buttons ${JSON.stringify(buttons)}`;
|
|
73
|
+
script += ` default button ${defaultButton}`;
|
|
74
|
+
if (icon)
|
|
75
|
+
script += ` with icon ${icon}`;
|
|
76
|
+
if (givingUpAfter)
|
|
77
|
+
script += ` giving up after ${givingUpAfter}`;
|
|
78
|
+
const result = await executeAppleScript(script);
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
throw new Error(`Dialog failed: ${result.stderr}`);
|
|
81
|
+
}
|
|
82
|
+
const buttonMatch = result.stdout.match(/button returned:(\w+)/);
|
|
83
|
+
const gaveUpMatch = result.stdout.match(/gave up:(\w+)/);
|
|
84
|
+
return {
|
|
85
|
+
buttonReturned: buttonMatch ? buttonMatch[1] : buttons[0],
|
|
86
|
+
gaveUp: gaveUpMatch ? gaveUpMatch[1] === "true" : false
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function getVolume() {
|
|
90
|
+
const script = `
|
|
91
|
+
output volume of (get volume settings) as integer
|
|
92
|
+
`;
|
|
93
|
+
const volumeResult = await executeAppleScript(script);
|
|
94
|
+
const mutedScript = `
|
|
95
|
+
output muted of (get volume settings) as boolean
|
|
96
|
+
`;
|
|
97
|
+
const mutedResult = await executeAppleScript(mutedScript);
|
|
98
|
+
return {
|
|
99
|
+
volume: parseInt(volumeResult.stdout) || 0,
|
|
100
|
+
muted: mutedResult.stdout.toLowerCase() === "true"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function setVolume(volume, muted) {
|
|
104
|
+
const safeVolume = Math.max(0, Math.min(100, Math.round(volume)));
|
|
105
|
+
let script = `set volume output volume ${safeVolume}`;
|
|
106
|
+
if (muted !== undefined) {
|
|
107
|
+
script += muted ? " with output muted" : " without output muted";
|
|
108
|
+
}
|
|
109
|
+
return executeAppleScript(script);
|
|
110
|
+
}
|
|
111
|
+
async function getClipboard() {
|
|
112
|
+
const result = await executeAppleScript("the clipboard");
|
|
113
|
+
if (!result.success) {
|
|
114
|
+
throw new Error(`Failed to get clipboard: ${result.stderr}`);
|
|
115
|
+
}
|
|
116
|
+
return result.stdout;
|
|
117
|
+
}
|
|
118
|
+
async function setClipboard(text) {
|
|
119
|
+
const script = `set the clipboard to "${escapeString(text)}"`;
|
|
120
|
+
return executeAppleScript(script);
|
|
121
|
+
}
|
|
122
|
+
async function clearClipboard() {
|
|
123
|
+
return executeAppleScript('set the clipboard to ""');
|
|
124
|
+
}
|
|
125
|
+
async function listRunningApplications() {
|
|
126
|
+
const script = `
|
|
127
|
+
tell application "System Events"
|
|
128
|
+
set appList to name of every process whose background only is false
|
|
129
|
+
set output to ""
|
|
130
|
+
repeat with appName in appList
|
|
131
|
+
set output to output & appName & linefeed
|
|
132
|
+
end repeat
|
|
133
|
+
return output
|
|
134
|
+
end tell
|
|
135
|
+
`;
|
|
136
|
+
const result = await executeAppleScript(script);
|
|
137
|
+
if (!result.success) {
|
|
138
|
+
throw new Error(`Failed to list applications: ${result.stderr}`);
|
|
139
|
+
}
|
|
140
|
+
const apps = result.stdout.split(`
|
|
141
|
+
`).filter((name) => name.trim()).map((name) => ({
|
|
142
|
+
name: name.trim(),
|
|
143
|
+
running: true
|
|
144
|
+
}));
|
|
145
|
+
return apps;
|
|
146
|
+
}
|
|
147
|
+
async function getApplicationInfo(appName) {
|
|
148
|
+
const script = `
|
|
149
|
+
tell application "System Events"
|
|
150
|
+
if exists process "${escapeString(appName)}" then
|
|
151
|
+
set theProcess to process "${escapeString(appName)}"
|
|
152
|
+
set theName to name of theProcess
|
|
153
|
+
set thePath to POSIX path of (application file of theProcess as alias)
|
|
154
|
+
set thePID to unix id of theProcess
|
|
155
|
+
set windowCount to count of windows of theProcess
|
|
156
|
+
return theName & "|" & thePath & "|" & thePID & "|" & windowCount
|
|
157
|
+
else
|
|
158
|
+
return "NOT_RUNNING"
|
|
159
|
+
end if
|
|
160
|
+
end tell
|
|
161
|
+
`;
|
|
162
|
+
const result = await executeAppleScript(script);
|
|
163
|
+
if (!result.success || result.stdout === "NOT_RUNNING") {
|
|
164
|
+
return {
|
|
165
|
+
name: appName,
|
|
166
|
+
running: false
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const [name, path, pid, windowCount] = result.stdout.split("|");
|
|
170
|
+
return {
|
|
171
|
+
name,
|
|
172
|
+
path,
|
|
173
|
+
processId: parseInt(pid),
|
|
174
|
+
running: true,
|
|
175
|
+
windowCount: parseInt(windowCount)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function activateApplication(appName) {
|
|
179
|
+
const script = `
|
|
180
|
+
tell application "${escapeString(appName)}"
|
|
181
|
+
activate
|
|
182
|
+
end tell
|
|
183
|
+
`;
|
|
184
|
+
return executeAppleScript(script);
|
|
185
|
+
}
|
|
186
|
+
async function quitApplication(appName, force = false) {
|
|
187
|
+
let script;
|
|
188
|
+
if (force) {
|
|
189
|
+
script = `
|
|
190
|
+
tell application "System Events"
|
|
191
|
+
set theProcess to process "${escapeString(appName)}"
|
|
192
|
+
set thePID to unix id of theProcess
|
|
193
|
+
do shell script "kill -9 " & thePID
|
|
194
|
+
end tell
|
|
195
|
+
`;
|
|
196
|
+
} else {
|
|
197
|
+
script = `
|
|
198
|
+
tell application "${escapeString(appName)}"
|
|
199
|
+
quit
|
|
200
|
+
end tell
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
return executeAppleScript(script);
|
|
204
|
+
}
|
|
205
|
+
async function launchApplication(appName) {
|
|
206
|
+
const script = `
|
|
207
|
+
tell application "${escapeString(appName)}"
|
|
208
|
+
launch
|
|
209
|
+
end tell
|
|
210
|
+
`;
|
|
211
|
+
return executeAppleScript(script);
|
|
212
|
+
}
|
|
213
|
+
async function getFrontmostApplication() {
|
|
214
|
+
const script = `
|
|
215
|
+
tell application "System Events"
|
|
216
|
+
name of first process whose frontmost is true
|
|
217
|
+
end tell
|
|
218
|
+
`;
|
|
219
|
+
const result = await executeAppleScript(script);
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
throw new Error(`Failed to get frontmost app: ${result.stderr}`);
|
|
222
|
+
}
|
|
223
|
+
return result.stdout;
|
|
224
|
+
}
|
|
225
|
+
async function getApplicationWindows(appName) {
|
|
226
|
+
const script = `
|
|
227
|
+
tell application "System Events"
|
|
228
|
+
tell process "${escapeString(appName)}"
|
|
229
|
+
set windowList to {}
|
|
230
|
+
repeat with theWindow in windows
|
|
231
|
+
set windowName to name of theWindow
|
|
232
|
+
set windowId to id of theWindow
|
|
233
|
+
set windowIndex to index of theWindow
|
|
234
|
+
set end of windowList to windowName & "|" & windowId & "|" & windowIndex
|
|
235
|
+
end repeat
|
|
236
|
+
return windowList as text
|
|
237
|
+
end tell
|
|
238
|
+
end tell
|
|
239
|
+
`;
|
|
240
|
+
const result = await executeAppleScript(script);
|
|
241
|
+
if (!result.success) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
if (!result.stdout.trim()) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const windows = result.stdout.split(", ").filter((w) => w.trim()).map((w) => {
|
|
248
|
+
const [name, id, index] = w.split("|");
|
|
249
|
+
return {
|
|
250
|
+
name: name || null,
|
|
251
|
+
id: parseInt(id) || 0,
|
|
252
|
+
index: parseInt(index) || 0
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
return windows;
|
|
256
|
+
}
|
|
257
|
+
async function setWindowBounds(appName, windowIndex, bounds) {
|
|
258
|
+
const script = `
|
|
259
|
+
tell application "System Events"
|
|
260
|
+
tell process "${escapeString(appName)}"
|
|
261
|
+
set position of window ${windowIndex} to {${bounds.x}, ${bounds.y}}
|
|
262
|
+
set size of window ${windowIndex} to {${bounds.width}, ${bounds.height}}
|
|
263
|
+
end tell
|
|
264
|
+
end tell
|
|
265
|
+
`;
|
|
266
|
+
return executeAppleScript(script);
|
|
267
|
+
}
|
|
268
|
+
async function sendKeystroke(options) {
|
|
269
|
+
const { key, modifiers = [], delay = 0 } = options;
|
|
270
|
+
const modifierStr = modifiers.map((m) => m + " down").join(", ");
|
|
271
|
+
const usingStr = modifierStr ? ` using {${modifierStr}}` : "";
|
|
272
|
+
const script = `
|
|
273
|
+
tell application "System Events"
|
|
274
|
+
keystroke "${escapeString(key)}"${usingStr}
|
|
275
|
+
${delay > 0 ? `delay ${delay}` : ""}
|
|
276
|
+
end tell
|
|
277
|
+
`;
|
|
278
|
+
return executeAppleScript(script);
|
|
279
|
+
}
|
|
280
|
+
async function sendKeyCode(options) {
|
|
281
|
+
const { keyCode, modifiers = [] } = options;
|
|
282
|
+
const modifierStr = modifiers.map((m) => m + " down").join(", ");
|
|
283
|
+
const usingStr = modifierStr ? ` using {${modifierStr}}` : "";
|
|
284
|
+
const script = `
|
|
285
|
+
tell application "System Events"
|
|
286
|
+
key code ${keyCode}${usingStr}
|
|
287
|
+
end tell
|
|
288
|
+
`;
|
|
289
|
+
return executeAppleScript(script);
|
|
290
|
+
}
|
|
291
|
+
async function typeText(text, delayBetweenChars = 0.01) {
|
|
292
|
+
const script = `
|
|
293
|
+
tell application "System Events"
|
|
294
|
+
keystroke "${escapeString(text)}"
|
|
295
|
+
${delayBetweenChars > 0 ? `delay ${delayBetweenChars}` : ""}
|
|
296
|
+
end tell
|
|
297
|
+
`;
|
|
298
|
+
return executeAppleScript(script);
|
|
299
|
+
}
|
|
300
|
+
async function getSafariURL() {
|
|
301
|
+
const script = `
|
|
302
|
+
tell application "Safari"
|
|
303
|
+
return URL of current tab of front window
|
|
304
|
+
end tell
|
|
305
|
+
`;
|
|
306
|
+
const result = await executeAppleScript(script);
|
|
307
|
+
if (!result.success) {
|
|
308
|
+
throw new Error(`Failed to get Safari URL: ${result.stderr}`);
|
|
309
|
+
}
|
|
310
|
+
return result.stdout;
|
|
311
|
+
}
|
|
312
|
+
async function setSafariURL(url) {
|
|
313
|
+
const script = `
|
|
314
|
+
tell application "Safari"
|
|
315
|
+
set URL of current tab of front window to "${escapeString(url)}"
|
|
316
|
+
end tell
|
|
317
|
+
`;
|
|
318
|
+
return executeAppleScript(script);
|
|
319
|
+
}
|
|
320
|
+
async function getSafariTabTitles() {
|
|
321
|
+
const script = `
|
|
322
|
+
tell application "Safari"
|
|
323
|
+
set tabTitles to name of every tab of front window
|
|
324
|
+
return tabTitles as text
|
|
325
|
+
end tell
|
|
326
|
+
`;
|
|
327
|
+
const result = await executeAppleScript(script);
|
|
328
|
+
if (!result.success) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
return result.stdout.split(", ").filter((t) => t.trim());
|
|
332
|
+
}
|
|
333
|
+
async function executeSafariJS(jsCode) {
|
|
334
|
+
const script = `
|
|
335
|
+
tell application "Safari"
|
|
336
|
+
do JavaScript "${escapeString(jsCode)}" in current tab of front window
|
|
337
|
+
end tell
|
|
338
|
+
`;
|
|
339
|
+
const result = await executeAppleScript(script);
|
|
340
|
+
if (!result.success) {
|
|
341
|
+
throw new Error(`Failed to execute Safari JS: ${result.stderr}`);
|
|
342
|
+
}
|
|
343
|
+
return result.stdout;
|
|
344
|
+
}
|
|
345
|
+
async function getFinderSelection() {
|
|
346
|
+
const script = `
|
|
347
|
+
tell application "Finder"
|
|
348
|
+
set selectedItems to selection
|
|
349
|
+
set output to ""
|
|
350
|
+
repeat with theItem in selectedItems
|
|
351
|
+
set itemName to name of theItem
|
|
352
|
+
set itemPath to POSIX path of (theItem as alias)
|
|
353
|
+
set itemKind to kind of theItem
|
|
354
|
+
set output to output & itemName & "|" & itemPath & "|" & itemKind & linefeed
|
|
355
|
+
end repeat
|
|
356
|
+
return output
|
|
357
|
+
end tell
|
|
358
|
+
`;
|
|
359
|
+
const result = await executeAppleScript(script);
|
|
360
|
+
if (!result.success || !result.stdout.trim()) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
return result.stdout.split(`
|
|
364
|
+
`).filter((line) => line.trim()).map((line) => {
|
|
365
|
+
const [name, path, kind] = line.split("|");
|
|
366
|
+
const type = kind?.toLowerCase().includes("folder") ? "folder" : kind?.toLowerCase().includes("application") ? "application" : "file";
|
|
367
|
+
return {
|
|
368
|
+
name,
|
|
369
|
+
path,
|
|
370
|
+
type,
|
|
371
|
+
exists: true
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async function openFinderWindow(path) {
|
|
376
|
+
const script = `
|
|
377
|
+
tell application "Finder"
|
|
378
|
+
activate
|
|
379
|
+
open POSIX file "${escapeString(path)}"
|
|
380
|
+
end tell
|
|
381
|
+
`;
|
|
382
|
+
return executeAppleScript(script);
|
|
383
|
+
}
|
|
384
|
+
async function createFolder(parentPath, folderName) {
|
|
385
|
+
const script = `
|
|
386
|
+
tell application "Finder"
|
|
387
|
+
make new folder at POSIX file "${escapeString(parentPath)}" with properties {name:"${escapeString(folderName)}"}
|
|
388
|
+
end tell
|
|
389
|
+
`;
|
|
390
|
+
return executeAppleScript(script);
|
|
391
|
+
}
|
|
392
|
+
async function moveToTrash(path) {
|
|
393
|
+
const script = `
|
|
394
|
+
tell application "Finder"
|
|
395
|
+
delete POSIX file "${escapeString(path)}"
|
|
396
|
+
end tell
|
|
397
|
+
`;
|
|
398
|
+
return executeAppleScript(script);
|
|
399
|
+
}
|
|
400
|
+
async function getDisplayInfo() {
|
|
401
|
+
const script = `
|
|
402
|
+
ObjC.import('Cocoa');
|
|
403
|
+
var screens = $.NSScreen.screens;
|
|
404
|
+
var count = screens.count;
|
|
405
|
+
var result = [];
|
|
406
|
+
for (var i = 0; i < count; i++) {
|
|
407
|
+
var screen = screens.objectAtIndex(i);
|
|
408
|
+
var frame = screen.frame;
|
|
409
|
+
result.push({
|
|
410
|
+
id: i,
|
|
411
|
+
width: frame.size.width,
|
|
412
|
+
height: frame.size.height,
|
|
413
|
+
main: i === 0
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
JSON.stringify(result);
|
|
417
|
+
`;
|
|
418
|
+
const result = await executeJXA(script);
|
|
419
|
+
if (!result.success) {
|
|
420
|
+
const fallbackScript = `
|
|
421
|
+
tell application "System Events"
|
|
422
|
+
set screenWidth to word 3 of (do shell script "system_profiler SPDisplaysDataType | grep Resolution")
|
|
423
|
+
set screenHeight to word 5 of (do shell script "system_profiler SPDisplaysDataType | grep Resolution")
|
|
424
|
+
return screenWidth & "x" & screenHeight
|
|
425
|
+
end tell
|
|
426
|
+
`;
|
|
427
|
+
const fallback = await executeAppleScript(fallbackScript);
|
|
428
|
+
const [width, height] = fallback.stdout.split("x").map((n) => parseInt(n));
|
|
429
|
+
return [
|
|
430
|
+
{
|
|
431
|
+
id: 0,
|
|
432
|
+
width: width || 1920,
|
|
433
|
+
height: height || 1080,
|
|
434
|
+
main: true
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
return JSON.parse(result.stdout);
|
|
440
|
+
} catch {
|
|
441
|
+
return [
|
|
442
|
+
{
|
|
443
|
+
id: 0,
|
|
444
|
+
width: 1920,
|
|
445
|
+
height: 1080,
|
|
446
|
+
main: true
|
|
447
|
+
}
|
|
448
|
+
];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function getScreenResolution() {
|
|
452
|
+
const displays = await getDisplayInfo();
|
|
453
|
+
const mainDisplay = displays.find((d) => d.main) || displays[0];
|
|
454
|
+
return {
|
|
455
|
+
width: mainDisplay?.width || 1920,
|
|
456
|
+
height: mainDisplay?.height || 1080
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function isMacOS() {
|
|
460
|
+
return process.platform === "darwin";
|
|
461
|
+
}
|
|
462
|
+
function escapeString(str) {
|
|
463
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
464
|
+
}
|
|
465
|
+
async function sayText(text, voice, rate) {
|
|
466
|
+
let script = `say "${escapeString(text)}"`;
|
|
467
|
+
if (voice)
|
|
468
|
+
script += ` using "${voice}"`;
|
|
469
|
+
if (rate)
|
|
470
|
+
script += ` speaking rate ${rate}`;
|
|
471
|
+
return executeAppleScript(script);
|
|
472
|
+
}
|
|
473
|
+
async function beep(count = 1) {
|
|
474
|
+
return executeAppleScript(`beep ${count}`);
|
|
475
|
+
}
|
|
476
|
+
async function getSystemDateTime() {
|
|
477
|
+
const script = `
|
|
478
|
+
get current date
|
|
479
|
+
`;
|
|
480
|
+
const result = await executeAppleScript(script);
|
|
481
|
+
if (!result.success) {
|
|
482
|
+
throw new Error(`Failed to get system date: ${result.stderr}`);
|
|
483
|
+
}
|
|
484
|
+
return result.stdout;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/mcp/events.ts
|
|
488
|
+
import { spawn as spawn2 } from "child_process";
|
|
489
|
+
import { EventEmitter } from "events";
|
|
490
|
+
|
|
491
|
+
class ActionEventBus extends EventEmitter {
|
|
492
|
+
listeners = new Map;
|
|
493
|
+
eventHistory = [];
|
|
494
|
+
maxHistorySize = 1000;
|
|
495
|
+
monitors = new Map;
|
|
496
|
+
emit(event) {
|
|
497
|
+
this.eventHistory.push(event);
|
|
498
|
+
if (this.eventHistory.length > this.maxHistorySize) {
|
|
499
|
+
this.eventHistory.shift();
|
|
500
|
+
}
|
|
501
|
+
for (const [id, listener] of this.listeners) {
|
|
502
|
+
if (!listener.active)
|
|
503
|
+
continue;
|
|
504
|
+
if (listener.eventType !== "*" && listener.eventType !== event.type)
|
|
505
|
+
continue;
|
|
506
|
+
if (listener.filter && !listener.filter(event))
|
|
507
|
+
continue;
|
|
508
|
+
if (listener.callback) {
|
|
509
|
+
Promise.resolve(listener.callback(event)).catch(console.error);
|
|
510
|
+
}
|
|
511
|
+
if (listener.webhookUrl) {
|
|
512
|
+
this.sendWebhook(listener.webhookUrl, event).catch(console.error);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return super.emit(event.type, event);
|
|
516
|
+
}
|
|
517
|
+
addListener(listener) {
|
|
518
|
+
this.listeners.set(listener.id, listener);
|
|
519
|
+
}
|
|
520
|
+
removeListener(id) {
|
|
521
|
+
return this.listeners.delete(id);
|
|
522
|
+
}
|
|
523
|
+
getListeners() {
|
|
524
|
+
return Array.from(this.listeners.values());
|
|
525
|
+
}
|
|
526
|
+
getHistory(since, type) {
|
|
527
|
+
let events = this.eventHistory;
|
|
528
|
+
if (since) {
|
|
529
|
+
events = events.filter((e) => e.timestamp >= since);
|
|
530
|
+
}
|
|
531
|
+
if (type) {
|
|
532
|
+
events = events.filter((e) => e.type === type);
|
|
533
|
+
}
|
|
534
|
+
return events;
|
|
535
|
+
}
|
|
536
|
+
clearHistory() {
|
|
537
|
+
this.eventHistory = [];
|
|
538
|
+
}
|
|
539
|
+
async sendWebhook(url, event, config) {
|
|
540
|
+
const retries = config?.retries || 3;
|
|
541
|
+
const timeout = config?.timeout || 5000;
|
|
542
|
+
for (let attempt = 0;attempt < retries; attempt++) {
|
|
543
|
+
try {
|
|
544
|
+
const controller = new AbortController;
|
|
545
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
546
|
+
const response = await fetch(url, {
|
|
547
|
+
method: config?.method || "POST",
|
|
548
|
+
headers: {
|
|
549
|
+
"Content-Type": "application/json",
|
|
550
|
+
...config?.headers
|
|
551
|
+
},
|
|
552
|
+
body: JSON.stringify(event),
|
|
553
|
+
signal: controller.signal
|
|
554
|
+
});
|
|
555
|
+
clearTimeout(timeoutId);
|
|
556
|
+
if (response.ok) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
} catch (error) {
|
|
560
|
+
if (attempt === retries - 1) {
|
|
561
|
+
console.error(`Webhook failed after ${retries} attempts:`, error);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
addMonitor(id, process2) {
|
|
568
|
+
this.monitors.set(id, process2);
|
|
569
|
+
}
|
|
570
|
+
removeMonitor(id) {
|
|
571
|
+
const proc = this.monitors.get(id);
|
|
572
|
+
if (proc) {
|
|
573
|
+
proc.kill();
|
|
574
|
+
this.monitors.delete(id);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
stopAllMonitors() {
|
|
578
|
+
for (const [id, proc] of this.monitors) {
|
|
579
|
+
proc.kill();
|
|
580
|
+
}
|
|
581
|
+
this.monitors.clear();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
var eventBus = new ActionEventBus;
|
|
585
|
+
var eventCounter = 0;
|
|
586
|
+
function generateEventId() {
|
|
587
|
+
return `evt_${Date.now()}_${++eventCounter}`;
|
|
588
|
+
}
|
|
589
|
+
function createEvent(type, source, data, success = true, error) {
|
|
590
|
+
return {
|
|
591
|
+
id: generateEventId(),
|
|
592
|
+
type,
|
|
593
|
+
timestamp: Date.now(),
|
|
594
|
+
source,
|
|
595
|
+
data,
|
|
596
|
+
success,
|
|
597
|
+
error
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async function startAppMonitor() {
|
|
601
|
+
const monitorId = `app_${Date.now()}`;
|
|
602
|
+
const script = `
|
|
603
|
+
set lastApp to ""
|
|
604
|
+
repeat
|
|
605
|
+
tell application "System Events"
|
|
606
|
+
set currentApp to name of first process whose frontmost is true
|
|
607
|
+
end tell
|
|
608
|
+
if currentApp is not lastApp then
|
|
609
|
+
do shell script "echo " & quoted form of currentApp
|
|
610
|
+
set lastApp to currentApp
|
|
611
|
+
end if
|
|
612
|
+
delay 0.5
|
|
613
|
+
end repeat
|
|
614
|
+
`;
|
|
615
|
+
const proc = spawn2("osascript", ["-e", script]);
|
|
616
|
+
eventBus.addMonitor(monitorId, proc);
|
|
617
|
+
proc.stdout?.on("data", (data) => {
|
|
618
|
+
const appName = data.toString().trim();
|
|
619
|
+
eventBus.emit(createEvent("app_activate", "app_monitor", { appName }));
|
|
620
|
+
});
|
|
621
|
+
proc.stderr?.on("data", (data) => {
|
|
622
|
+
console.error(`App monitor error: ${data}`);
|
|
623
|
+
});
|
|
624
|
+
proc.on("close", () => {
|
|
625
|
+
eventBus.removeMonitor(monitorId);
|
|
626
|
+
});
|
|
627
|
+
return monitorId;
|
|
628
|
+
}
|
|
629
|
+
async function startClipboardMonitor() {
|
|
630
|
+
const monitorId = `clipboard_${Date.now()}`;
|
|
631
|
+
const script = `
|
|
632
|
+
set lastClipboard to the clipboard as text
|
|
633
|
+
repeat
|
|
634
|
+
delay 0.5
|
|
635
|
+
try
|
|
636
|
+
set currentClipboard to the clipboard as text
|
|
637
|
+
if currentClipboard is not lastClipboard then
|
|
638
|
+
do shell script "echo CLIPBOARD_CHANGED"
|
|
639
|
+
set lastClipboard to currentClipboard
|
|
640
|
+
end if
|
|
641
|
+
end try
|
|
642
|
+
end repeat
|
|
643
|
+
`;
|
|
644
|
+
const proc = spawn2("osascript", ["-e", script]);
|
|
645
|
+
eventBus.addMonitor(monitorId, proc);
|
|
646
|
+
proc.stdout?.on("data", (data) => {
|
|
647
|
+
if (data.toString().includes("CLIPBOARD_CHANGED")) {
|
|
648
|
+
eventBus.emit(createEvent("clipboard_change", "clipboard_monitor", {}));
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
proc.stderr?.on("data", (data) => {
|
|
652
|
+
console.error(`Clipboard monitor error: ${data}`);
|
|
653
|
+
});
|
|
654
|
+
proc.on("close", () => {
|
|
655
|
+
eventBus.removeMonitor(monitorId);
|
|
656
|
+
});
|
|
657
|
+
return monitorId;
|
|
658
|
+
}
|
|
659
|
+
function stopMonitor(monitorId) {
|
|
660
|
+
eventBus.removeMonitor(monitorId);
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
function stopAllMonitors() {
|
|
664
|
+
eventBus.stopAllMonitors();
|
|
665
|
+
}
|
|
666
|
+
function registerEventListener(eventType, options = {}) {
|
|
667
|
+
const listenerId = `listener_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
668
|
+
eventBus.addListener({
|
|
669
|
+
id: listenerId,
|
|
670
|
+
eventType,
|
|
671
|
+
active: true,
|
|
672
|
+
webhookUrl: options.webhookUrl,
|
|
673
|
+
callback: options.callback,
|
|
674
|
+
filter: options.filter
|
|
675
|
+
});
|
|
676
|
+
return listenerId;
|
|
677
|
+
}
|
|
678
|
+
function unregisterEventListener(listenerId) {
|
|
679
|
+
return eventBus.removeListener(listenerId);
|
|
680
|
+
}
|
|
681
|
+
function listEventListeners() {
|
|
682
|
+
return eventBus.getListeners();
|
|
683
|
+
}
|
|
684
|
+
function getEventHistory(since, type) {
|
|
685
|
+
return eventBus.getHistory(since, type);
|
|
686
|
+
}
|
|
687
|
+
function clearEventHistory() {
|
|
688
|
+
eventBus.clearHistory();
|
|
689
|
+
}
|
|
690
|
+
async function sendEventWebhook(url, event, config) {
|
|
691
|
+
const success = await eventBus.sendWebhook(url, event, config);
|
|
692
|
+
return { success, error: success ? undefined : "Webhook delivery failed" };
|
|
693
|
+
}
|
|
694
|
+
function getActiveMonitorCount() {
|
|
695
|
+
return eventBus["monitors"].size;
|
|
696
|
+
}
|
|
697
|
+
function getActiveListenerCount() {
|
|
698
|
+
return eventBus.getListeners().filter((l) => l.active).length;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/mcp/stdio.ts
|
|
702
|
+
async function osascriptExecute(script, language = "applescript") {
|
|
703
|
+
const result = language === "javascript" ? await executeJXA(script) : await executeAppleScript(script);
|
|
704
|
+
return JSON.stringify(result, null, 2);
|
|
705
|
+
}
|
|
706
|
+
async function osascriptNotification(message, title = "Notification", subtitle, soundName) {
|
|
707
|
+
const options = { message, title, subtitle, soundName };
|
|
708
|
+
const result = await displayNotification(options);
|
|
709
|
+
return result.success ? `Notification sent: ${title}` : `Error: ${result.stderr}`;
|
|
710
|
+
}
|
|
711
|
+
async function osascriptDialog(message, title, buttons, defaultButton, icon) {
|
|
712
|
+
const options = { message, title, buttons, defaultButton, icon };
|
|
713
|
+
try {
|
|
714
|
+
const result = await displayDialog(options);
|
|
715
|
+
return `Button returned: ${result.buttonReturned}${result.gaveUp ? " (timed out)" : ""}`;
|
|
716
|
+
} catch (error) {
|
|
717
|
+
return `Error: ${error}`;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async function osascriptGetVolume() {
|
|
721
|
+
const info = await getVolume();
|
|
722
|
+
return `Volume: ${info.volume}%${info.muted ? " (muted)" : ""}`;
|
|
723
|
+
}
|
|
724
|
+
async function osascriptSetVolume(volume, muted) {
|
|
725
|
+
const result = await setVolume(volume, muted);
|
|
726
|
+
return result.success ? `Volume set to ${volume}%` : `Error: ${result.stderr}`;
|
|
727
|
+
}
|
|
728
|
+
async function osascriptGetClipboard() {
|
|
729
|
+
try {
|
|
730
|
+
const content = await getClipboard();
|
|
731
|
+
return content || "(clipboard is empty)";
|
|
732
|
+
} catch (error) {
|
|
733
|
+
return `Error: ${error}`;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async function osascriptSetClipboard(text) {
|
|
737
|
+
const result = await setClipboard(text);
|
|
738
|
+
return result.success ? "Clipboard updated" : `Error: ${result.stderr}`;
|
|
739
|
+
}
|
|
740
|
+
async function osascriptClearClipboard() {
|
|
741
|
+
const result = await clearClipboard();
|
|
742
|
+
return result.success ? "Clipboard cleared" : `Error: ${result.stderr}`;
|
|
743
|
+
}
|
|
744
|
+
async function osascriptListApps() {
|
|
745
|
+
const apps = await listRunningApplications();
|
|
746
|
+
const lines = ["Running Applications:", "=".repeat(40), ""];
|
|
747
|
+
for (const app of apps) {
|
|
748
|
+
lines.push(`- ${app.name}`);
|
|
749
|
+
}
|
|
750
|
+
return lines.join(`
|
|
751
|
+
`);
|
|
752
|
+
}
|
|
753
|
+
async function osascriptGetAppInfo(appName) {
|
|
754
|
+
const info = await getApplicationInfo(appName);
|
|
755
|
+
if (!info.running) {
|
|
756
|
+
return `Application "${appName}" is not running`;
|
|
757
|
+
}
|
|
758
|
+
const lines = [
|
|
759
|
+
`Application: ${info.name}`,
|
|
760
|
+
"=".repeat(40),
|
|
761
|
+
`Running: ${info.running}`,
|
|
762
|
+
info.path ? `Path: ${info.path}` : "",
|
|
763
|
+
info.processId ? `PID: ${info.processId}` : "",
|
|
764
|
+
info.windowCount !== undefined ? `Windows: ${info.windowCount}` : ""
|
|
765
|
+
].filter(Boolean);
|
|
766
|
+
return lines.join(`
|
|
767
|
+
`);
|
|
768
|
+
}
|
|
769
|
+
async function osascriptActivateApp(appName) {
|
|
770
|
+
const result = await activateApplication(appName);
|
|
771
|
+
return result.success ? `Activated ${appName}` : `Error: ${result.stderr}`;
|
|
772
|
+
}
|
|
773
|
+
async function osascriptQuitApp(appName, force = false) {
|
|
774
|
+
const result = await quitApplication(appName, force);
|
|
775
|
+
return result.success ? `Quit ${appName}` : `Error: ${result.stderr}`;
|
|
776
|
+
}
|
|
777
|
+
async function osascriptLaunchApp(appName) {
|
|
778
|
+
const result = await launchApplication(appName);
|
|
779
|
+
return result.success ? `Launched ${appName}` : `Error: ${result.stderr}`;
|
|
780
|
+
}
|
|
781
|
+
async function osascriptGetFrontmostApp() {
|
|
782
|
+
const appName = await getFrontmostApplication();
|
|
783
|
+
return `Frontmost application: ${appName}`;
|
|
784
|
+
}
|
|
785
|
+
async function osascriptGetWindows(appName) {
|
|
786
|
+
const windows = await getApplicationWindows(appName);
|
|
787
|
+
if (windows.length === 0) {
|
|
788
|
+
return `No windows for ${appName}`;
|
|
789
|
+
}
|
|
790
|
+
const lines = [`Windows for ${appName}:`, "=".repeat(40), ""];
|
|
791
|
+
for (const win of windows) {
|
|
792
|
+
lines.push(`${win.index}. ${win.name || "(unnamed)"} (ID: ${win.id})`);
|
|
793
|
+
}
|
|
794
|
+
return lines.join(`
|
|
795
|
+
`);
|
|
796
|
+
}
|
|
797
|
+
async function osascriptSetWindowBounds(appName, windowIndex, x, y, width, height) {
|
|
798
|
+
const result = await setWindowBounds(appName, windowIndex, { x, y, width, height });
|
|
799
|
+
return result.success ? `Window ${windowIndex} bounds set to ${width}x${height} at (${x}, ${y})` : `Error: ${result.stderr}`;
|
|
800
|
+
}
|
|
801
|
+
async function osascriptKeystroke(key, modifiers = []) {
|
|
802
|
+
const options = { key, modifiers };
|
|
803
|
+
const result = await sendKeystroke(options);
|
|
804
|
+
const modStr = modifiers.length > 0 ? ` with ${modifiers.join("+")}` : "";
|
|
805
|
+
return result.success ? `Sent keystroke: ${key}${modStr}` : `Error: ${result.stderr}`;
|
|
806
|
+
}
|
|
807
|
+
async function osascriptKeyCode(keyCode, modifiers = []) {
|
|
808
|
+
const options = { keyCode, modifiers };
|
|
809
|
+
const result = await sendKeyCode(options);
|
|
810
|
+
const modStr = modifiers.length > 0 ? ` with ${modifiers.join("+")}` : "";
|
|
811
|
+
return result.success ? `Sent key code: ${keyCode}${modStr}` : `Error: ${result.stderr}`;
|
|
812
|
+
}
|
|
813
|
+
async function osascriptTypeText(text) {
|
|
814
|
+
const result = await typeText(text);
|
|
815
|
+
return result.success ? `Typed: ${text}` : `Error: ${result.stderr}`;
|
|
816
|
+
}
|
|
817
|
+
async function osascriptSafariGetURL() {
|
|
818
|
+
try {
|
|
819
|
+
const url = await getSafariURL();
|
|
820
|
+
return `Current Safari URL: ${url}`;
|
|
821
|
+
} catch (error) {
|
|
822
|
+
return `Error: ${error}`;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
async function osascriptSafariSetURL(url) {
|
|
826
|
+
const result = await setSafariURL(url);
|
|
827
|
+
return result.success ? `Navigated to: ${url}` : `Error: ${result.stderr}`;
|
|
828
|
+
}
|
|
829
|
+
async function osascriptSafariGetTabs() {
|
|
830
|
+
const titles = await getSafariTabTitles();
|
|
831
|
+
if (titles.length === 0) {
|
|
832
|
+
return "No Safari tabs";
|
|
833
|
+
}
|
|
834
|
+
const lines = ["Safari Tabs:", "=".repeat(40), ""];
|
|
835
|
+
titles.forEach((title, i) => lines.push(`${i + 1}. ${title}`));
|
|
836
|
+
return lines.join(`
|
|
837
|
+
`);
|
|
838
|
+
}
|
|
839
|
+
async function osascriptSafariExecJS(jsCode) {
|
|
840
|
+
try {
|
|
841
|
+
const result = await executeSafariJS(jsCode);
|
|
842
|
+
return result || "(no output)";
|
|
843
|
+
} catch (error) {
|
|
844
|
+
return `Error: ${error}`;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
async function osascriptFinderGetSelection() {
|
|
848
|
+
const items = await getFinderSelection();
|
|
849
|
+
if (items.length === 0) {
|
|
850
|
+
return "No Finder selection";
|
|
851
|
+
}
|
|
852
|
+
const lines = ["Finder Selection:", "=".repeat(40), ""];
|
|
853
|
+
for (const item of items) {
|
|
854
|
+
lines.push(`- ${item.name} (${item.type})`);
|
|
855
|
+
lines.push(` ${item.path}`);
|
|
856
|
+
}
|
|
857
|
+
return lines.join(`
|
|
858
|
+
`);
|
|
859
|
+
}
|
|
860
|
+
async function osascriptFinderOpen(path) {
|
|
861
|
+
const result = await openFinderWindow(path);
|
|
862
|
+
return result.success ? `Opened Finder at: ${path}` : `Error: ${result.stderr}`;
|
|
863
|
+
}
|
|
864
|
+
async function osascriptFinderCreateFolder(parentPath, folderName) {
|
|
865
|
+
const result = await createFolder(parentPath, folderName);
|
|
866
|
+
return result.success ? `Created folder: ${folderName}` : `Error: ${result.stderr}`;
|
|
867
|
+
}
|
|
868
|
+
async function osascriptFinderTrash(path) {
|
|
869
|
+
const result = await moveToTrash(path);
|
|
870
|
+
return result.success ? `Moved to trash: ${path}` : `Error: ${result.stderr}`;
|
|
871
|
+
}
|
|
872
|
+
async function osascriptGetDisplays() {
|
|
873
|
+
const displays = await getDisplayInfo();
|
|
874
|
+
const lines = ["Display Information:", "=".repeat(40), ""];
|
|
875
|
+
for (const display of displays) {
|
|
876
|
+
lines.push(`Display ${display.id}: ${display.width}x${display.height}${display.main ? " (main)" : ""}`);
|
|
877
|
+
}
|
|
878
|
+
return lines.join(`
|
|
879
|
+
`);
|
|
880
|
+
}
|
|
881
|
+
async function osascriptGetResolution() {
|
|
882
|
+
const { width, height } = await getScreenResolution();
|
|
883
|
+
return `Screen resolution: ${width}x${height}`;
|
|
884
|
+
}
|
|
885
|
+
async function osascriptSay(text, voice, rate) {
|
|
886
|
+
const result = await sayText(text, voice, rate);
|
|
887
|
+
return result.success ? `Said: "${text}"` : `Error: ${result.stderr}`;
|
|
888
|
+
}
|
|
889
|
+
async function osascriptBeep(count = 1) {
|
|
890
|
+
const result = await beep(count);
|
|
891
|
+
return result.success ? `Beeped ${count} time(s)` : `Error: ${result.stderr}`;
|
|
892
|
+
}
|
|
893
|
+
async function osascriptGetDateTime() {
|
|
894
|
+
const dateTime = await getSystemDateTime();
|
|
895
|
+
return `System date/time: ${dateTime}`;
|
|
896
|
+
}
|
|
897
|
+
async function osascriptSpawnClaudeSession(sessionId, workingDir, dopplerProject = "seed", dopplerConfig = "prd") {
|
|
898
|
+
const cdCmd = workingDir ? `cd ${workingDir} && ` : "";
|
|
899
|
+
const script = `tell application "Terminal" to do script "${cdCmd}unset CLAUDECODE && NO_COLOR=1 TERM=dumb doppler run --project ${dopplerProject} --config ${dopplerConfig} --command \\"claude -r ${sessionId}\\""`;
|
|
900
|
+
const result = await executeAppleScript(script);
|
|
901
|
+
if (result.success) {
|
|
902
|
+
return `Spawned Claude Code session ${sessionId} in ${result.stdout}`;
|
|
903
|
+
}
|
|
904
|
+
return `Error: ${result.stderr}`;
|
|
905
|
+
}
|
|
906
|
+
async function osascriptSpawnClaudeSessionsBatch(sessionIdsStr, workingDir, dopplerProject = "seed", dopplerConfig = "prd") {
|
|
907
|
+
const sessionIds = sessionIdsStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
908
|
+
const cdCmd = workingDir ? `cd ${workingDir} && ` : "";
|
|
909
|
+
const results = [];
|
|
910
|
+
for (const sessionId of sessionIds) {
|
|
911
|
+
const script = `tell application "Terminal" to do script "${cdCmd}unset CLAUDECODE && NO_COLOR=1 TERM=dumb doppler run --project ${dopplerProject} --config ${dopplerConfig} --command \\"claude -r ${sessionId}\\""`;
|
|
912
|
+
const result = await executeAppleScript(script);
|
|
913
|
+
results.push(`${sessionId.slice(0, 8)}: ${result.success ? result.stdout : result.stderr}`);
|
|
914
|
+
}
|
|
915
|
+
return `Spawned ${sessionIds.length} sessions:
|
|
916
|
+
${results.join(`
|
|
917
|
+
`)}`;
|
|
918
|
+
}
|
|
919
|
+
var TOOLS = [
|
|
920
|
+
{
|
|
921
|
+
name: "osascript_execute",
|
|
922
|
+
description: "Execute raw AppleScript code",
|
|
923
|
+
inputSchema: {
|
|
924
|
+
type: "object",
|
|
925
|
+
properties: {
|
|
926
|
+
script: { type: "string", description: "AppleScript code to execute" },
|
|
927
|
+
language: {
|
|
928
|
+
type: "string",
|
|
929
|
+
description: "Script language",
|
|
930
|
+
enum: ["applescript", "javascript"],
|
|
931
|
+
default: "applescript"
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
required: ["script"]
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
name: "osascript_display_notification",
|
|
939
|
+
description: "Display a macOS notification",
|
|
940
|
+
inputSchema: {
|
|
941
|
+
type: "object",
|
|
942
|
+
properties: {
|
|
943
|
+
message: { type: "string", description: "Notification body text" },
|
|
944
|
+
title: { type: "string", description: "Notification title", default: "Notification" },
|
|
945
|
+
subtitle: { type: "string", description: "Notification subtitle" },
|
|
946
|
+
sound_name: { type: "string", description: "Sound name (e.g., 'Ping', 'Glass')" }
|
|
947
|
+
},
|
|
948
|
+
required: ["message"]
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
name: "osascript_display_dialog",
|
|
953
|
+
description: "Display a dialog with buttons",
|
|
954
|
+
inputSchema: {
|
|
955
|
+
type: "object",
|
|
956
|
+
properties: {
|
|
957
|
+
message: { type: "string", description: "Dialog message" },
|
|
958
|
+
title: { type: "string", description: "Dialog title" },
|
|
959
|
+
buttons: {
|
|
960
|
+
type: "string",
|
|
961
|
+
description: "Comma-separated button labels (e.g., 'OK,Cancel')"
|
|
962
|
+
},
|
|
963
|
+
default_button: { type: "number", description: "Default button index (1-based)" },
|
|
964
|
+
icon: {
|
|
965
|
+
type: "string",
|
|
966
|
+
description: "Dialog icon",
|
|
967
|
+
enum: ["note", "caution", "stop"]
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
required: ["message"]
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "osascript_get_volume",
|
|
975
|
+
description: "Get system volume level",
|
|
976
|
+
inputSchema: {
|
|
977
|
+
type: "object",
|
|
978
|
+
properties: {},
|
|
979
|
+
required: []
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
name: "osascript_set_volume",
|
|
984
|
+
description: "Set system volume level (0-100)",
|
|
985
|
+
inputSchema: {
|
|
986
|
+
type: "object",
|
|
987
|
+
properties: {
|
|
988
|
+
volume: { type: "number", description: "Volume level 0-100" },
|
|
989
|
+
muted: { type: "boolean", description: "Whether to mute" }
|
|
990
|
+
},
|
|
991
|
+
required: ["volume"]
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
name: "osascript_get_clipboard",
|
|
996
|
+
description: "Get clipboard contents",
|
|
997
|
+
inputSchema: {
|
|
998
|
+
type: "object",
|
|
999
|
+
properties: {},
|
|
1000
|
+
required: []
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
name: "osascript_set_clipboard",
|
|
1005
|
+
description: "Set clipboard contents",
|
|
1006
|
+
inputSchema: {
|
|
1007
|
+
type: "object",
|
|
1008
|
+
properties: {
|
|
1009
|
+
text: { type: "string", description: "Text to copy to clipboard" }
|
|
1010
|
+
},
|
|
1011
|
+
required: ["text"]
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
name: "osascript_clear_clipboard",
|
|
1016
|
+
description: "Clear clipboard contents",
|
|
1017
|
+
inputSchema: {
|
|
1018
|
+
type: "object",
|
|
1019
|
+
properties: {},
|
|
1020
|
+
required: []
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
name: "osascript_list_apps",
|
|
1025
|
+
description: "List all running applications",
|
|
1026
|
+
inputSchema: {
|
|
1027
|
+
type: "object",
|
|
1028
|
+
properties: {},
|
|
1029
|
+
required: []
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
name: "osascript_get_app_info",
|
|
1034
|
+
description: "Get detailed information about an application",
|
|
1035
|
+
inputSchema: {
|
|
1036
|
+
type: "object",
|
|
1037
|
+
properties: {
|
|
1038
|
+
app_name: { type: "string", description: "Application name" }
|
|
1039
|
+
},
|
|
1040
|
+
required: ["app_name"]
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
name: "osascript_activate_app",
|
|
1045
|
+
description: "Activate (bring to front) an application",
|
|
1046
|
+
inputSchema: {
|
|
1047
|
+
type: "object",
|
|
1048
|
+
properties: {
|
|
1049
|
+
app_name: { type: "string", description: "Application name to activate" }
|
|
1050
|
+
},
|
|
1051
|
+
required: ["app_name"]
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
name: "osascript_quit_app",
|
|
1056
|
+
description: "Quit an application",
|
|
1057
|
+
inputSchema: {
|
|
1058
|
+
type: "object",
|
|
1059
|
+
properties: {
|
|
1060
|
+
app_name: { type: "string", description: "Application name to quit" },
|
|
1061
|
+
force: { type: "boolean", description: "Force quit (kill -9)" }
|
|
1062
|
+
},
|
|
1063
|
+
required: ["app_name"]
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
name: "osascript_launch_app",
|
|
1068
|
+
description: "Launch an application",
|
|
1069
|
+
inputSchema: {
|
|
1070
|
+
type: "object",
|
|
1071
|
+
properties: {
|
|
1072
|
+
app_name: { type: "string", description: "Application name to launch" }
|
|
1073
|
+
},
|
|
1074
|
+
required: ["app_name"]
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
name: "osascript_get_frontmost_app",
|
|
1079
|
+
description: "Get the name of the frontmost (active) application",
|
|
1080
|
+
inputSchema: {
|
|
1081
|
+
type: "object",
|
|
1082
|
+
properties: {},
|
|
1083
|
+
required: []
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
name: "osascript_get_windows",
|
|
1088
|
+
description: "Get window list for an application",
|
|
1089
|
+
inputSchema: {
|
|
1090
|
+
type: "object",
|
|
1091
|
+
properties: {
|
|
1092
|
+
app_name: { type: "string", description: "Application name" }
|
|
1093
|
+
},
|
|
1094
|
+
required: ["app_name"]
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
name: "osascript_set_window_bounds",
|
|
1099
|
+
description: "Set window position and size",
|
|
1100
|
+
inputSchema: {
|
|
1101
|
+
type: "object",
|
|
1102
|
+
properties: {
|
|
1103
|
+
app_name: { type: "string", description: "Application name" },
|
|
1104
|
+
window_index: { type: "number", description: "Window index (1-based)" },
|
|
1105
|
+
x: { type: "number", description: "X position" },
|
|
1106
|
+
y: { type: "number", description: "Y position" },
|
|
1107
|
+
width: { type: "number", description: "Window width" },
|
|
1108
|
+
height: { type: "number", description: "Window height" }
|
|
1109
|
+
},
|
|
1110
|
+
required: ["app_name", "window_index", "x", "y", "width", "height"]
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
name: "osascript_keystroke",
|
|
1115
|
+
description: "Send keystroke with optional modifiers",
|
|
1116
|
+
inputSchema: {
|
|
1117
|
+
type: "object",
|
|
1118
|
+
properties: {
|
|
1119
|
+
key: { type: "string", description: "Key to press" },
|
|
1120
|
+
modifiers: {
|
|
1121
|
+
type: "string",
|
|
1122
|
+
description: "Comma-separated modifiers: command, shift, option, control"
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
required: ["key"]
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
name: "osascript_key_code",
|
|
1130
|
+
description: "Send key code (for special keys)",
|
|
1131
|
+
inputSchema: {
|
|
1132
|
+
type: "object",
|
|
1133
|
+
properties: {
|
|
1134
|
+
key_code: { type: "number", description: "Key code number (e.g., 36=Return, 48=Tab)" },
|
|
1135
|
+
modifiers: {
|
|
1136
|
+
type: "string",
|
|
1137
|
+
description: "Comma-separated modifiers: command, shift, option, control"
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
required: ["key_code"]
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
name: "osascript_type_text",
|
|
1145
|
+
description: "Type text (simulate keyboard input)",
|
|
1146
|
+
inputSchema: {
|
|
1147
|
+
type: "object",
|
|
1148
|
+
properties: {
|
|
1149
|
+
text: { type: "string", description: "Text to type" }
|
|
1150
|
+
},
|
|
1151
|
+
required: ["text"]
|
|
1152
|
+
}
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
name: "osascript_safari_get_url",
|
|
1156
|
+
description: "Get current Safari URL",
|
|
1157
|
+
inputSchema: {
|
|
1158
|
+
type: "object",
|
|
1159
|
+
properties: {},
|
|
1160
|
+
required: []
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
name: "osascript_safari_set_url",
|
|
1165
|
+
description: "Navigate Safari to URL",
|
|
1166
|
+
inputSchema: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {
|
|
1169
|
+
url: { type: "string", description: "URL to navigate to" }
|
|
1170
|
+
},
|
|
1171
|
+
required: ["url"]
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
name: "osascript_safari_get_tabs",
|
|
1176
|
+
description: "Get Safari tab titles",
|
|
1177
|
+
inputSchema: {
|
|
1178
|
+
type: "object",
|
|
1179
|
+
properties: {},
|
|
1180
|
+
required: []
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
name: "osascript_safari_exec_js",
|
|
1185
|
+
description: "Execute JavaScript in Safari",
|
|
1186
|
+
inputSchema: {
|
|
1187
|
+
type: "object",
|
|
1188
|
+
properties: {
|
|
1189
|
+
js_code: { type: "string", description: "JavaScript code to execute" }
|
|
1190
|
+
},
|
|
1191
|
+
required: ["js_code"]
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
name: "osascript_finder_get_selection",
|
|
1196
|
+
description: "Get currently selected files in Finder",
|
|
1197
|
+
inputSchema: {
|
|
1198
|
+
type: "object",
|
|
1199
|
+
properties: {},
|
|
1200
|
+
required: []
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
name: "osascript_finder_open",
|
|
1205
|
+
description: "Open Finder window at path",
|
|
1206
|
+
inputSchema: {
|
|
1207
|
+
type: "object",
|
|
1208
|
+
properties: {
|
|
1209
|
+
path: { type: "string", description: "Path to open" }
|
|
1210
|
+
},
|
|
1211
|
+
required: ["path"]
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
name: "osascript_finder_create_folder",
|
|
1216
|
+
description: "Create new folder in Finder",
|
|
1217
|
+
inputSchema: {
|
|
1218
|
+
type: "object",
|
|
1219
|
+
properties: {
|
|
1220
|
+
parent_path: { type: "string", description: "Parent directory path" },
|
|
1221
|
+
folder_name: { type: "string", description: "New folder name" }
|
|
1222
|
+
},
|
|
1223
|
+
required: ["parent_path", "folder_name"]
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
name: "osascript_finder_trash",
|
|
1228
|
+
description: "Move file/folder to trash",
|
|
1229
|
+
inputSchema: {
|
|
1230
|
+
type: "object",
|
|
1231
|
+
properties: {
|
|
1232
|
+
path: { type: "string", description: "Path to move to trash" }
|
|
1233
|
+
},
|
|
1234
|
+
required: ["path"]
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
name: "osascript_get_displays",
|
|
1239
|
+
description: "Get display information",
|
|
1240
|
+
inputSchema: {
|
|
1241
|
+
type: "object",
|
|
1242
|
+
properties: {},
|
|
1243
|
+
required: []
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
name: "osascript_get_resolution",
|
|
1248
|
+
description: "Get main screen resolution",
|
|
1249
|
+
inputSchema: {
|
|
1250
|
+
type: "object",
|
|
1251
|
+
properties: {},
|
|
1252
|
+
required: []
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
name: "osascript_say",
|
|
1257
|
+
description: "Say text using system voice",
|
|
1258
|
+
inputSchema: {
|
|
1259
|
+
type: "object",
|
|
1260
|
+
properties: {
|
|
1261
|
+
text: { type: "string", description: "Text to speak" },
|
|
1262
|
+
voice: { type: "string", description: "Voice name (e.g., 'Alex', 'Samantha')" },
|
|
1263
|
+
rate: { type: "number", description: "Speaking rate" }
|
|
1264
|
+
},
|
|
1265
|
+
required: ["text"]
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
name: "osascript_beep",
|
|
1270
|
+
description: "Play system beep",
|
|
1271
|
+
inputSchema: {
|
|
1272
|
+
type: "object",
|
|
1273
|
+
properties: {
|
|
1274
|
+
count: { type: "number", description: "Number of beeps" }
|
|
1275
|
+
},
|
|
1276
|
+
required: []
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
name: "osascript_get_datetime",
|
|
1281
|
+
description: "Get current system date/time",
|
|
1282
|
+
inputSchema: {
|
|
1283
|
+
type: "object",
|
|
1284
|
+
properties: {},
|
|
1285
|
+
required: []
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
name: "osascript_spawn_claude_session",
|
|
1290
|
+
description: "Open new Terminal tab with Claude Code resume session (auto-unsets CLAUDECODE to bypass nested session check)",
|
|
1291
|
+
inputSchema: {
|
|
1292
|
+
type: "object",
|
|
1293
|
+
properties: {
|
|
1294
|
+
session_id: { type: "string", description: "Claude Code session ID to resume" },
|
|
1295
|
+
working_dir: { type: "string", description: "Working directory (default: current directory)" },
|
|
1296
|
+
doppler_project: { type: "string", description: "Doppler project name (default: seed)" },
|
|
1297
|
+
doppler_config: { type: "string", description: "Doppler config (default: prd)" }
|
|
1298
|
+
},
|
|
1299
|
+
required: ["session_id"]
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
name: "osascript_spawn_claude_sessions_batch",
|
|
1304
|
+
description: "Open multiple Terminal tabs with Claude Code resume sessions in parallel",
|
|
1305
|
+
inputSchema: {
|
|
1306
|
+
type: "object",
|
|
1307
|
+
properties: {
|
|
1308
|
+
session_ids: { type: "string", description: "Comma-separated list of Claude Code session IDs" },
|
|
1309
|
+
working_dir: { type: "string", description: "Working directory (default: current directory)" },
|
|
1310
|
+
doppler_project: { type: "string", description: "Doppler project name (default: seed)" },
|
|
1311
|
+
doppler_config: { type: "string", description: "Doppler config (default: prd)" }
|
|
1312
|
+
},
|
|
1313
|
+
required: ["session_ids"]
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
name: "osascript_event_start_app_monitor",
|
|
1318
|
+
description: "Start monitoring application switches (fires event when frontmost app changes)",
|
|
1319
|
+
inputSchema: {
|
|
1320
|
+
type: "object",
|
|
1321
|
+
properties: {},
|
|
1322
|
+
required: []
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "osascript_event_start_clipboard_monitor",
|
|
1327
|
+
description: "Start monitoring clipboard changes (fires event when clipboard content changes)",
|
|
1328
|
+
inputSchema: {
|
|
1329
|
+
type: "object",
|
|
1330
|
+
properties: {},
|
|
1331
|
+
required: []
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
name: "osascript_event_stop_monitor",
|
|
1336
|
+
description: "Stop a specific monitor by ID",
|
|
1337
|
+
inputSchema: {
|
|
1338
|
+
type: "object",
|
|
1339
|
+
properties: {
|
|
1340
|
+
monitor_id: { type: "string", description: "Monitor ID to stop" }
|
|
1341
|
+
},
|
|
1342
|
+
required: ["monitor_id"]
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
name: "osascript_event_stop_all_monitors",
|
|
1347
|
+
description: "Stop all active monitors",
|
|
1348
|
+
inputSchema: {
|
|
1349
|
+
type: "object",
|
|
1350
|
+
properties: {},
|
|
1351
|
+
required: []
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
name: "osascript_event_register_listener",
|
|
1356
|
+
description: "Register an event listener with optional webhook callback",
|
|
1357
|
+
inputSchema: {
|
|
1358
|
+
type: "object",
|
|
1359
|
+
properties: {
|
|
1360
|
+
event_type: {
|
|
1361
|
+
type: "string",
|
|
1362
|
+
description: "Event type to listen for (e.g., 'keystroke', 'app_activate', '*' for all)"
|
|
1363
|
+
},
|
|
1364
|
+
webhook_url: { type: "string", description: "Webhook URL to POST events to" }
|
|
1365
|
+
},
|
|
1366
|
+
required: ["event_type"]
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
name: "osascript_event_unregister_listener",
|
|
1371
|
+
description: "Unregister an event listener",
|
|
1372
|
+
inputSchema: {
|
|
1373
|
+
type: "object",
|
|
1374
|
+
properties: {
|
|
1375
|
+
listener_id: { type: "string", description: "Listener ID to remove" }
|
|
1376
|
+
},
|
|
1377
|
+
required: ["listener_id"]
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
name: "osascript_event_list_listeners",
|
|
1382
|
+
description: "List all registered event listeners",
|
|
1383
|
+
inputSchema: {
|
|
1384
|
+
type: "object",
|
|
1385
|
+
properties: {},
|
|
1386
|
+
required: []
|
|
1387
|
+
}
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
name: "osascript_event_get_history",
|
|
1391
|
+
description: "Get event history, optionally filtered by type or time",
|
|
1392
|
+
inputSchema: {
|
|
1393
|
+
type: "object",
|
|
1394
|
+
properties: {
|
|
1395
|
+
since: { type: "number", description: "Unix timestamp to get events since" },
|
|
1396
|
+
event_type: { type: "string", description: "Filter by event type" }
|
|
1397
|
+
},
|
|
1398
|
+
required: []
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
name: "osascript_event_clear_history",
|
|
1403
|
+
description: "Clear all event history",
|
|
1404
|
+
inputSchema: {
|
|
1405
|
+
type: "object",
|
|
1406
|
+
properties: {},
|
|
1407
|
+
required: []
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
name: "osascript_event_send_webhook",
|
|
1412
|
+
description: "Manually send an event to a webhook URL",
|
|
1413
|
+
inputSchema: {
|
|
1414
|
+
type: "object",
|
|
1415
|
+
properties: {
|
|
1416
|
+
url: { type: "string", description: "Webhook URL" },
|
|
1417
|
+
event_type: { type: "string", description: "Event type" },
|
|
1418
|
+
data: { type: "string", description: "JSON string of event data" }
|
|
1419
|
+
},
|
|
1420
|
+
required: ["url", "event_type"]
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
name: "osascript_event_emit",
|
|
1425
|
+
description: "Emit a custom event to the event bus",
|
|
1426
|
+
inputSchema: {
|
|
1427
|
+
type: "object",
|
|
1428
|
+
properties: {
|
|
1429
|
+
event_type: { type: "string", description: "Event type to emit" },
|
|
1430
|
+
source: { type: "string", description: "Source identifier" },
|
|
1431
|
+
data: { type: "string", description: "JSON string of event data" }
|
|
1432
|
+
},
|
|
1433
|
+
required: ["event_type", "source"]
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
name: "osascript_event_status",
|
|
1438
|
+
description: "Get status of event system (active monitors, listeners, event count)",
|
|
1439
|
+
inputSchema: {
|
|
1440
|
+
type: "object",
|
|
1441
|
+
properties: {},
|
|
1442
|
+
required: []
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
];
|
|
1446
|
+
function parseModifiers(modifiersStr) {
|
|
1447
|
+
if (!modifiersStr)
|
|
1448
|
+
return [];
|
|
1449
|
+
return modifiersStr.split(",").map((m) => m.trim().toLowerCase()).filter((m) => ["command", "shift", "option", "control"].includes(m));
|
|
1450
|
+
}
|
|
1451
|
+
async function handleToolCall(name, args) {
|
|
1452
|
+
if (!isMacOS()) {
|
|
1453
|
+
throw new Error("osascript MCP only works on macOS");
|
|
1454
|
+
}
|
|
1455
|
+
switch (name) {
|
|
1456
|
+
case "osascript_execute":
|
|
1457
|
+
return await osascriptExecute(args.script, args.language || "applescript");
|
|
1458
|
+
case "osascript_display_notification":
|
|
1459
|
+
return await osascriptNotification(args.message, args.title, args.subtitle, args.sound_name);
|
|
1460
|
+
case "osascript_display_dialog":
|
|
1461
|
+
return await osascriptDialog(args.message, args.title, args.buttons?.split(","), args.default_button, args.icon);
|
|
1462
|
+
case "osascript_get_volume":
|
|
1463
|
+
return await osascriptGetVolume();
|
|
1464
|
+
case "osascript_set_volume":
|
|
1465
|
+
return await osascriptSetVolume(args.volume, args.muted);
|
|
1466
|
+
case "osascript_get_clipboard":
|
|
1467
|
+
return await osascriptGetClipboard();
|
|
1468
|
+
case "osascript_set_clipboard":
|
|
1469
|
+
return await osascriptSetClipboard(args.text);
|
|
1470
|
+
case "osascript_clear_clipboard":
|
|
1471
|
+
return await osascriptClearClipboard();
|
|
1472
|
+
case "osascript_list_apps":
|
|
1473
|
+
return await osascriptListApps();
|
|
1474
|
+
case "osascript_get_app_info":
|
|
1475
|
+
return await osascriptGetAppInfo(args.app_name);
|
|
1476
|
+
case "osascript_activate_app":
|
|
1477
|
+
return await osascriptActivateApp(args.app_name);
|
|
1478
|
+
case "osascript_quit_app":
|
|
1479
|
+
return await osascriptQuitApp(args.app_name, args.force);
|
|
1480
|
+
case "osascript_launch_app":
|
|
1481
|
+
return await osascriptLaunchApp(args.app_name);
|
|
1482
|
+
case "osascript_get_frontmost_app":
|
|
1483
|
+
return await osascriptGetFrontmostApp();
|
|
1484
|
+
case "osascript_get_windows":
|
|
1485
|
+
return await osascriptGetWindows(args.app_name);
|
|
1486
|
+
case "osascript_set_window_bounds":
|
|
1487
|
+
return await osascriptSetWindowBounds(args.app_name, args.window_index, args.x, args.y, args.width, args.height);
|
|
1488
|
+
case "osascript_keystroke":
|
|
1489
|
+
return await osascriptKeystroke(args.key, parseModifiers(args.modifiers));
|
|
1490
|
+
case "osascript_key_code":
|
|
1491
|
+
return await osascriptKeyCode(args.key_code, parseModifiers(args.modifiers));
|
|
1492
|
+
case "osascript_type_text":
|
|
1493
|
+
return await osascriptTypeText(args.text);
|
|
1494
|
+
case "osascript_safari_get_url":
|
|
1495
|
+
return await osascriptSafariGetURL();
|
|
1496
|
+
case "osascript_safari_set_url":
|
|
1497
|
+
return await osascriptSafariSetURL(args.url);
|
|
1498
|
+
case "osascript_safari_get_tabs":
|
|
1499
|
+
return await osascriptSafariGetTabs();
|
|
1500
|
+
case "osascript_safari_exec_js":
|
|
1501
|
+
return await osascriptSafariExecJS(args.js_code);
|
|
1502
|
+
case "osascript_finder_get_selection":
|
|
1503
|
+
return await osascriptFinderGetSelection();
|
|
1504
|
+
case "osascript_finder_open":
|
|
1505
|
+
return await osascriptFinderOpen(args.path);
|
|
1506
|
+
case "osascript_finder_create_folder":
|
|
1507
|
+
return await osascriptFinderCreateFolder(args.parent_path, args.folder_name);
|
|
1508
|
+
case "osascript_finder_trash":
|
|
1509
|
+
return await osascriptFinderTrash(args.path);
|
|
1510
|
+
case "osascript_get_displays":
|
|
1511
|
+
return await osascriptGetDisplays();
|
|
1512
|
+
case "osascript_get_resolution":
|
|
1513
|
+
return await osascriptGetResolution();
|
|
1514
|
+
case "osascript_say":
|
|
1515
|
+
return await osascriptSay(args.text, args.voice, args.rate);
|
|
1516
|
+
case "osascript_beep":
|
|
1517
|
+
return await osascriptBeep(args.count || 1);
|
|
1518
|
+
case "osascript_get_datetime":
|
|
1519
|
+
return await osascriptGetDateTime();
|
|
1520
|
+
case "osascript_spawn_claude_session":
|
|
1521
|
+
return await osascriptSpawnClaudeSession(args.session_id, args.working_dir, args.doppler_project, args.doppler_config);
|
|
1522
|
+
case "osascript_spawn_claude_sessions_batch":
|
|
1523
|
+
return await osascriptSpawnClaudeSessionsBatch(args.session_ids, args.working_dir, args.doppler_project, args.doppler_config);
|
|
1524
|
+
case "osascript_event_start_app_monitor": {
|
|
1525
|
+
const monitorId = await startAppMonitor();
|
|
1526
|
+
return `Started app monitor: ${monitorId}`;
|
|
1527
|
+
}
|
|
1528
|
+
case "osascript_event_start_clipboard_monitor": {
|
|
1529
|
+
const monitorId = await startClipboardMonitor();
|
|
1530
|
+
return `Started clipboard monitor: ${monitorId}`;
|
|
1531
|
+
}
|
|
1532
|
+
case "osascript_event_stop_monitor": {
|
|
1533
|
+
const stopped = stopMonitor(args.monitor_id);
|
|
1534
|
+
return stopped ? `Stopped monitor: ${args.monitor_id}` : `Monitor not found: ${args.monitor_id}`;
|
|
1535
|
+
}
|
|
1536
|
+
case "osascript_event_stop_all_monitors": {
|
|
1537
|
+
stopAllMonitors();
|
|
1538
|
+
return "Stopped all monitors";
|
|
1539
|
+
}
|
|
1540
|
+
case "osascript_event_register_listener": {
|
|
1541
|
+
const listenerId = registerEventListener(args.event_type, {
|
|
1542
|
+
webhookUrl: args.webhook_url
|
|
1543
|
+
});
|
|
1544
|
+
return `Registered listener: ${listenerId}`;
|
|
1545
|
+
}
|
|
1546
|
+
case "osascript_event_unregister_listener": {
|
|
1547
|
+
const removed = unregisterEventListener(args.listener_id);
|
|
1548
|
+
return removed ? `Unregistered listener: ${args.listener_id}` : `Listener not found: ${args.listener_id}`;
|
|
1549
|
+
}
|
|
1550
|
+
case "osascript_event_list_listeners": {
|
|
1551
|
+
const listeners = listEventListeners();
|
|
1552
|
+
if (listeners.length === 0)
|
|
1553
|
+
return "No listeners registered";
|
|
1554
|
+
const lines = ["Event Listeners:", "=".repeat(40), ""];
|
|
1555
|
+
for (const l of listeners) {
|
|
1556
|
+
lines.push(`${l.id}: ${l.eventType} (${l.active ? "active" : "inactive"})${l.webhookUrl ? ` -> ${l.webhookUrl}` : ""}`);
|
|
1557
|
+
}
|
|
1558
|
+
return lines.join(`
|
|
1559
|
+
`);
|
|
1560
|
+
}
|
|
1561
|
+
case "osascript_event_get_history": {
|
|
1562
|
+
const events = getEventHistory(args.since, args.event_type);
|
|
1563
|
+
if (events.length === 0)
|
|
1564
|
+
return "No events in history";
|
|
1565
|
+
return JSON.stringify(events, null, 2);
|
|
1566
|
+
}
|
|
1567
|
+
case "osascript_event_clear_history": {
|
|
1568
|
+
clearEventHistory();
|
|
1569
|
+
return "Event history cleared";
|
|
1570
|
+
}
|
|
1571
|
+
case "osascript_event_send_webhook": {
|
|
1572
|
+
const data = args.data ? JSON.parse(args.data) : {};
|
|
1573
|
+
const event = createEvent(args.event_type, "manual", data);
|
|
1574
|
+
const result = await sendEventWebhook(args.url, event);
|
|
1575
|
+
return result.success ? `Webhook sent: ${event.id}` : `Webhook failed: ${result.error}`;
|
|
1576
|
+
}
|
|
1577
|
+
case "osascript_event_emit": {
|
|
1578
|
+
const data = args.data ? JSON.parse(args.data) : {};
|
|
1579
|
+
const event = createEvent(args.event_type, args.source, data);
|
|
1580
|
+
eventBus.emit(event);
|
|
1581
|
+
return `Emitted event: ${event.id}`;
|
|
1582
|
+
}
|
|
1583
|
+
case "osascript_event_status": {
|
|
1584
|
+
const status = {
|
|
1585
|
+
activeMonitors: getActiveMonitorCount(),
|
|
1586
|
+
activeListeners: getActiveListenerCount(),
|
|
1587
|
+
historySize: getEventHistory().length
|
|
1588
|
+
};
|
|
1589
|
+
return `Event System Status:
|
|
1590
|
+
${JSON.stringify(status, null, 2)}`;
|
|
1591
|
+
}
|
|
1592
|
+
default:
|
|
1593
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function sendMessage(message) {
|
|
1597
|
+
console.log(JSON.stringify(message));
|
|
1598
|
+
}
|
|
1599
|
+
async function processMessage(message) {
|
|
1600
|
+
const { id, method, params } = message;
|
|
1601
|
+
try {
|
|
1602
|
+
switch (method) {
|
|
1603
|
+
case "initialize":
|
|
1604
|
+
sendMessage({
|
|
1605
|
+
jsonrpc: "2.0",
|
|
1606
|
+
id,
|
|
1607
|
+
result: {
|
|
1608
|
+
protocolVersion: "2024-11-05",
|
|
1609
|
+
capabilities: {
|
|
1610
|
+
tools: {}
|
|
1611
|
+
},
|
|
1612
|
+
serverInfo: {
|
|
1613
|
+
name: "osascript-mcp",
|
|
1614
|
+
version: "1.0.0",
|
|
1615
|
+
description: "macOS automation via osascript (AppleScript/JXA)"
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
break;
|
|
1620
|
+
case "tools/list":
|
|
1621
|
+
sendMessage({
|
|
1622
|
+
jsonrpc: "2.0",
|
|
1623
|
+
id,
|
|
1624
|
+
result: {
|
|
1625
|
+
tools: TOOLS
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
break;
|
|
1629
|
+
case "tools/call":
|
|
1630
|
+
const { name, arguments: args } = params;
|
|
1631
|
+
const result = await handleToolCall(name, args || {});
|
|
1632
|
+
sendMessage({
|
|
1633
|
+
jsonrpc: "2.0",
|
|
1634
|
+
id,
|
|
1635
|
+
result: {
|
|
1636
|
+
content: [
|
|
1637
|
+
{
|
|
1638
|
+
type: "text",
|
|
1639
|
+
text: result
|
|
1640
|
+
}
|
|
1641
|
+
]
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
break;
|
|
1645
|
+
case "shutdown":
|
|
1646
|
+
sendMessage({
|
|
1647
|
+
jsonrpc: "2.0",
|
|
1648
|
+
id,
|
|
1649
|
+
result: {}
|
|
1650
|
+
});
|
|
1651
|
+
process.exit(0);
|
|
1652
|
+
break;
|
|
1653
|
+
default:
|
|
1654
|
+
sendMessage({
|
|
1655
|
+
jsonrpc: "2.0",
|
|
1656
|
+
id,
|
|
1657
|
+
error: {
|
|
1658
|
+
code: -32601,
|
|
1659
|
+
message: `Method not found: ${method}`
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
sendMessage({
|
|
1665
|
+
jsonrpc: "2.0",
|
|
1666
|
+
id,
|
|
1667
|
+
error: {
|
|
1668
|
+
code: -32603,
|
|
1669
|
+
message: `Internal error: ${error}`
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
async function main() {
|
|
1675
|
+
if (!isMacOS()) {
|
|
1676
|
+
console.error("ERROR: osascript MCP only works on macOS");
|
|
1677
|
+
process.exit(1);
|
|
1678
|
+
}
|
|
1679
|
+
const decoder = new TextDecoder;
|
|
1680
|
+
let buffer = "";
|
|
1681
|
+
for await (const chunk of process.stdin) {
|
|
1682
|
+
buffer += decoder.decode(chunk);
|
|
1683
|
+
const lines = buffer.split(`
|
|
1684
|
+
`);
|
|
1685
|
+
buffer = lines.pop() || "";
|
|
1686
|
+
for (const line of lines) {
|
|
1687
|
+
if (!line.trim())
|
|
1688
|
+
continue;
|
|
1689
|
+
try {
|
|
1690
|
+
const message = JSON.parse(line);
|
|
1691
|
+
await processMessage(message);
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
sendMessage({
|
|
1694
|
+
jsonrpc: "2.0",
|
|
1695
|
+
error: {
|
|
1696
|
+
code: -32700,
|
|
1697
|
+
message: `Parse error: ${error}`
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
main().catch(console.error);
|