apple-tools-mcp 3.0.3 → 3.0.4
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/lib/mailWrite.js +29 -8
- package/lib/shell.js +23 -1
- package/package.json +1 -1
package/lib/mailWrite.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
isHardTccDenial
|
|
32
32
|
} from "./appleScript.js";
|
|
33
33
|
import { isIndexerMode } from "./processMode.js";
|
|
34
|
-
import {
|
|
34
|
+
import { safeSpawnDetached } from "./shell.js";
|
|
35
35
|
import {
|
|
36
36
|
planWrite,
|
|
37
37
|
plannedWriteResult,
|
|
@@ -161,16 +161,25 @@ export const BODY_PASTE_MISDIRECTED_SENTINEL = "BODY_PASTE_MISDIRECTED";
|
|
|
161
161
|
export const BODY_FOCUS_FAILED_SENTINEL = "BODY_FOCUS_FAILED";
|
|
162
162
|
/** Focus request ran, but the re-read AXFocusedUIElement was not the chosen body. */
|
|
163
163
|
export const BODY_FOCUS_UNPROVEN_SENTINEL = "BODY_FOCUS_UNPROVEN";
|
|
164
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* System Events sees 0 Mail windows while Mail AppleScript still lists the compose:
|
|
166
|
+
* the screen saver is up (loginwindow owns the screen) or the display is asleep.
|
|
167
|
+
*/
|
|
165
168
|
export const DISPLAY_ASLEEP_GUI_UNAVAILABLE_SENTINEL = "DISPLAY_ASLEEP_GUI_UNAVAILABLE";
|
|
166
|
-
|
|
169
|
+
/** Outlasts the whole compose osascript (MAIL_COMPOSE_TIMEOUT_MS) so the assertion is live while it runs. */
|
|
170
|
+
export const DISPLAY_WAKE_SECONDS = 30;
|
|
167
171
|
export const DISPLAY_WAKE_POLL_ATTEMPTS = 20;
|
|
168
172
|
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Declare user activity so a sleeping display wakes before GUI scripting. caffeinate runs
|
|
175
|
+
* detached for its own -t: a blocking spawn would either SIGKILL it early (3.0.3's 5s timeout
|
|
176
|
+
* cut the 15s assertion at ~4s) or release it before compose starts. This does not dismiss a
|
|
177
|
+
* running screen saver; the compose script stops that itself. Best-effort; never throws.
|
|
178
|
+
*/
|
|
179
|
+
export function wakeDisplay({ spawnDetached = safeSpawnDetached, platform = process.platform } = {}) {
|
|
180
|
+
if (platform !== "darwin" || (process.env.VITEST && spawnDetached === safeSpawnDetached)) return false;
|
|
172
181
|
try {
|
|
173
|
-
return
|
|
182
|
+
return spawnDetached("/usr/bin/caffeinate", ["-u", "-t", String(DISPLAY_WAKE_SECONDS)]) != null;
|
|
174
183
|
} catch {
|
|
175
184
|
return false;
|
|
176
185
|
}
|
|
@@ -634,6 +643,16 @@ on atmAxValueIsRecipientToken(atmVal)
|
|
|
634
643
|
return false
|
|
635
644
|
end atmAxValueIsRecipientToken
|
|
636
645
|
|
|
646
|
+
-- A running screen saver keeps loginwindow in front and hides every Mail window from
|
|
647
|
+
-- System Events; caffeinate's user-activity assertion does not dismiss it.
|
|
648
|
+
on atmStopScreenSaver()
|
|
649
|
+
try
|
|
650
|
+
tell application "System Events"
|
|
651
|
+
if running of screen saver preferences then stop current screen saver
|
|
652
|
+
end tell
|
|
653
|
+
end try
|
|
654
|
+
end atmStopScreenSaver
|
|
655
|
+
|
|
637
656
|
on atmFillMailBody(bodyText, needle, titleMarker, msg)
|
|
638
657
|
try
|
|
639
658
|
tell application "Mail" to activate
|
|
@@ -644,10 +663,12 @@ on atmFillMailBody(bodyText, needle, titleMarker, msg)
|
|
|
644
663
|
set atmSaveBh to -1
|
|
645
664
|
with timeout of ${seSecs} seconds
|
|
646
665
|
tell application "System Events"
|
|
666
|
+
my atmStopScreenSaver()
|
|
647
667
|
tell process "Mail"
|
|
648
668
|
set frontmost to true
|
|
649
669
|
repeat ${DISPLAY_WAKE_POLL_ATTEMPTS} times
|
|
650
670
|
if (count of (every window)) > 0 then exit repeat
|
|
671
|
+
my atmStopScreenSaver()
|
|
651
672
|
delay 0.5
|
|
652
673
|
end repeat
|
|
653
674
|
set atmSeWindowCount to count of (every window)
|
|
@@ -1316,7 +1337,7 @@ function failure(action, summary, result, secrets) {
|
|
|
1316
1337
|
return `${action} failed — attempted to ${summary}. ${MAIL_GUI_SCRIPTING_GUIDANCE}`;
|
|
1317
1338
|
}
|
|
1318
1339
|
if (err.includes("display_asleep_gui_unavailable")) {
|
|
1319
|
-
return `${action} failed — attempted to ${summary}. The display is asleep, so System Events cannot see Mail's compose window (GUI unavailable even after
|
|
1340
|
+
return `${action} failed — attempted to ${summary}. The display is asleep or the screen is locked, so System Events cannot see Mail's compose window (GUI unavailable even after waking the display and stopping the screen saver); nothing was sent. Wake or unlock the Mac and retry.`;
|
|
1320
1341
|
}
|
|
1321
1342
|
if (err.includes("body_focus_unproven")) {
|
|
1322
1343
|
return `${action} failed — attempted to ${summary}. Keyboard focus was not on the message body (focus proof failed); the draft was discarded and nothing was sent.`;
|
package/lib/shell.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Validating inputs before execution
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { spawnSync } from "child_process";
|
|
11
|
+
import { spawn, spawnSync } from "child_process";
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { escapeSQL } from "./validators.js";
|
|
14
14
|
|
|
@@ -340,6 +340,28 @@ export function safeOpenApp(appName, options = {}) {
|
|
|
340
340
|
return String(result.stdout || "").trim();
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Start a command in the background and return immediately (no shell, no
|
|
345
|
+
* timeout, stdio ignored). The child is detached and unref'd, so it is never
|
|
346
|
+
* killed mid-run by a spawnSync timeout and never holds the caller open — for
|
|
347
|
+
* self-terminating helpers like `caffeinate -t N`. Returns the pid, or null if
|
|
348
|
+
* the spawn failed.
|
|
349
|
+
*/
|
|
350
|
+
export function safeSpawnDetached(command, args = []) {
|
|
351
|
+
if (!command || typeof command !== 'string') {
|
|
352
|
+
throw new Error('Command is required');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!Array.isArray(args)) {
|
|
356
|
+
throw new Error('Args must be an array');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const child = spawn(command, args, { detached: true, stdio: 'ignore', shell: false });
|
|
360
|
+
child.on('error', () => {});
|
|
361
|
+
child.unref();
|
|
362
|
+
return child.pid ?? null;
|
|
363
|
+
}
|
|
364
|
+
|
|
343
365
|
export function safeSpawn(command, args = [], options = {}) {
|
|
344
366
|
const {
|
|
345
367
|
timeout = 30000,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-tools-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "MCP server for semantic search and write actions across Apple Mail, Messages, Calendar, and Contacts. Speaks stdio (local) or bearer-token-authenticated Streamable HTTP (LAN / Tailscale).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|