@l22-io/orchard-mcp 0.6.0 → 0.6.3

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/build/bridge.d.ts CHANGED
@@ -3,13 +3,24 @@ export interface BridgeResponse {
3
3
  data?: unknown;
4
4
  error?: string;
5
5
  }
6
+ export interface BridgeOptions {
7
+ /**
8
+ * Per-call timeout in milliseconds. Defaults to 30_000.
9
+ * Long-running operations (e.g. mail content searches, large file reads)
10
+ * should pass a larger value. When the timeout fires the entire child
11
+ * process group is killed via SIGTERM (then SIGKILL) — this is required
12
+ * for tools that spawn osascript grandchildren, which would otherwise
13
+ * be orphaned and keep Mail.app / Notes.app wedged on Apple Events.
14
+ */
15
+ timeoutMs?: number;
16
+ }
6
17
  /**
7
18
  * Execute an apple-bridge subcommand and return parsed JSON.
8
19
  * Tries direct execution first; falls back to .app bundle mode
9
20
  * (via `open`) when direct execution returns a permission error.
10
21
  */
11
- export declare function callBridge(args: string[]): Promise<BridgeResponse>;
22
+ export declare function callBridge(args: string[], opts?: BridgeOptions): Promise<BridgeResponse>;
12
23
  /**
13
24
  * Convenience: call bridge, check status, return data or throw.
14
25
  */
15
- export declare function bridgeData(args: string[]): Promise<unknown>;
26
+ export declare function bridgeData(args: string[], opts?: BridgeOptions): Promise<unknown>;
package/build/bridge.js CHANGED
@@ -1,11 +1,9 @@
1
- import { execFile, spawn } from "node:child_process";
2
- import { promisify } from "node:util";
1
+ import { spawn } from "node:child_process";
3
2
  import { resolve, dirname } from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
4
  import { readFile, unlink, access } from "node:fs/promises";
6
5
  import { randomUUID } from "node:crypto";
7
6
  import { tmpdir } from "node:os";
8
- const execFileAsync = promisify(execFile);
9
7
  // Reason: Resolve the Swift binary path relative to this file's location.
10
8
  // In development: swift/.build/release/apple-bridge
11
9
  // In npm package: swift/.build/release/apple-bridge (shipped alongside)
@@ -42,42 +40,160 @@ function getAppBundlePath() {
42
40
  }
43
41
  return resolve(__dirname, "..", "swift", ".build", "AppleBridge.app");
44
42
  }
43
+ const DEFAULT_TIMEOUT_MS = 30_000;
44
+ const SIGKILL_GRACE_MS = 2_000;
45
45
  /**
46
46
  * Execute an apple-bridge subcommand and return parsed JSON.
47
47
  * Tries direct execution first; falls back to .app bundle mode
48
48
  * (via `open`) when direct execution returns a permission error.
49
49
  */
50
- export async function callBridge(args) {
50
+ export async function callBridge(args, opts = {}) {
51
51
  const bin = getBridgePath();
52
- try {
53
- const { stdout, stderr } = await execFileAsync(bin, args, {
54
- timeout: 30_000,
55
- maxBuffer: 10 * 1024 * 1024,
56
- });
57
- if (stderr) {
58
- // Reason: stderr is used for Swift warnings/diagnostics, log but don't fail.
59
- console.error(`[apple-bridge stderr] ${stderr.trim()}`);
52
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
53
+ const direct = await runBridgeProcess(bin, args, timeoutMs);
54
+ if (direct.status === "error" || direct.parsed == null) {
55
+ if (direct.spawnError) {
56
+ return { status: "error", error: direct.spawnError };
60
57
  }
61
- const parsed = JSON.parse(stdout);
58
+ if (direct.timedOut) {
59
+ return {
60
+ status: "error",
61
+ error: `apple-bridge timed out after ${timeoutMs}ms`,
62
+ };
63
+ }
64
+ }
65
+ if (direct.parsed) {
66
+ const parsed = direct.parsed;
62
67
  // If the command returned a permission error, retry via .app bundle
63
68
  if (parsed.status === "error" &&
64
69
  typeof parsed.error === "string" &&
65
70
  parsed.error.includes("access denied")) {
66
- return callBridgeViaApp(args);
71
+ return callBridgeViaApp(args, timeoutMs);
67
72
  }
68
73
  return parsed;
69
74
  }
70
- catch (err) {
71
- const msg = err instanceof Error ? err.message : "Unknown error calling apple-bridge";
72
- return { status: "error", error: msg };
73
- }
75
+ return { status: "error", error: direct.spawnError ?? "apple-bridge returned no output" };
76
+ }
77
+ /**
78
+ * Spawn apple-bridge in its own process group so we can SIGTERM the entire
79
+ * group on timeout. Required because Swift's Foundation.Process does not
80
+ * cascade signals to its child osascript processes — without this, a
81
+ * cancelled/timed-out mail search leaves osascript orphaned and Mail.app
82
+ * locked on Apple Events for as long as the script keeps iterating.
83
+ */
84
+ function runBridgeProcess(bin, args, timeoutMs) {
85
+ return new Promise((resolvePromise) => {
86
+ let child;
87
+ try {
88
+ child = spawn(bin, args, {
89
+ detached: true, // own process group; -pid kills the whole tree
90
+ stdio: ["ignore", "pipe", "pipe"],
91
+ });
92
+ }
93
+ catch (err) {
94
+ const msg = err instanceof Error ? err.message : "Failed to spawn apple-bridge";
95
+ resolvePromise({ status: "error", spawnError: msg });
96
+ return;
97
+ }
98
+ const pid = child.pid;
99
+ if (!pid) {
100
+ resolvePromise({ status: "error", spawnError: "apple-bridge spawn returned no pid" });
101
+ return;
102
+ }
103
+ const chunks = [];
104
+ const errChunks = [];
105
+ let totalBytes = 0;
106
+ const MAX_BYTES = 10 * 1024 * 1024;
107
+ let settled = false;
108
+ let timedOut = false;
109
+ let killEscalated = false;
110
+ let sigkillTimer = null;
111
+ const killGroup = (signal) => {
112
+ try {
113
+ process.kill(-pid, signal);
114
+ }
115
+ catch {
116
+ // Group may already be gone; nothing to do.
117
+ }
118
+ };
119
+ // Reason: Swift's apple-bridge has no SIGTERM handler and dies on the first
120
+ // signal, which causes Node's "close" event to fire almost immediately.
121
+ // If we cleared sigkillTimer at that point, any osascript grandchild
122
+ // wedged in an Apple Event RPC to Mail.app / Notes.app would be orphaned
123
+ // (PPID=1) and continue to hold Mail.app's event queue hostage. So once
124
+ // we have committed to escalating, the SIGKILL must fire regardless of
125
+ // when the bridge process itself closes.
126
+ const escalateKill = () => {
127
+ if (killEscalated)
128
+ return;
129
+ killEscalated = true;
130
+ killGroup("SIGTERM");
131
+ sigkillTimer = setTimeout(() => killGroup("SIGKILL"), SIGKILL_GRACE_MS);
132
+ sigkillTimer.unref();
133
+ };
134
+ const settle = (result) => {
135
+ if (settled)
136
+ return;
137
+ settled = true;
138
+ clearTimeout(timer);
139
+ if (!killEscalated && sigkillTimer)
140
+ clearTimeout(sigkillTimer);
141
+ // When escalation is in flight, leave the SIGKILL timer alone so it
142
+ // can reap any grandchildren the bridge spawned (see escalateKill).
143
+ resolvePromise(result);
144
+ };
145
+ const timer = setTimeout(() => {
146
+ timedOut = true;
147
+ escalateKill();
148
+ }, timeoutMs);
149
+ child.stdout?.on("data", (d) => {
150
+ totalBytes += d.length;
151
+ if (totalBytes > MAX_BYTES) {
152
+ escalateKill();
153
+ settle({
154
+ status: "error",
155
+ spawnError: `apple-bridge output exceeded ${MAX_BYTES} bytes`,
156
+ });
157
+ return;
158
+ }
159
+ chunks.push(d);
160
+ });
161
+ child.stderr?.on("data", (d) => {
162
+ errChunks.push(d);
163
+ });
164
+ child.on("error", (err) => {
165
+ settle({ status: "error", spawnError: err.message });
166
+ });
167
+ child.on("close", () => {
168
+ const stderr = Buffer.concat(errChunks).toString("utf8").trim();
169
+ if (stderr) {
170
+ console.error(`[apple-bridge stderr] ${stderr}`);
171
+ }
172
+ if (timedOut) {
173
+ settle({ status: "error", timedOut: true });
174
+ return;
175
+ }
176
+ const stdout = Buffer.concat(chunks).toString("utf8");
177
+ try {
178
+ const parsed = JSON.parse(stdout);
179
+ settle({ status: "ok", parsed });
180
+ }
181
+ catch (err) {
182
+ const msg = err instanceof Error ? err.message : "JSON parse failed";
183
+ settle({
184
+ status: "error",
185
+ spawnError: `apple-bridge returned invalid JSON: ${msg}`,
186
+ });
187
+ }
188
+ });
189
+ });
74
190
  }
75
191
  /**
76
192
  * Launch apple-bridge via .app bundle using `open`, with output written to
77
193
  * a temp file. Required on macOS Sequoia where CLI tools cannot obtain
78
194
  * TCC permissions (e.g. Reminders) without an .app bundle context.
79
195
  */
80
- async function callBridgeViaApp(args) {
196
+ async function callBridgeViaApp(args, timeoutMs) {
81
197
  const appPath = getAppBundlePath();
82
198
  const outputFile = resolve(tmpdir(), `apple-bridge-${randomUUID()}.json`);
83
199
  try {
@@ -99,9 +215,9 @@ async function callBridgeViaApp(args) {
99
215
  child.kill();
100
216
  resolvePromise({
101
217
  status: "error",
102
- error: "apple-bridge .app bundle timed out after 30s",
218
+ error: `apple-bridge .app bundle timed out after ${timeoutMs}ms`,
103
219
  });
104
- }, 30_000);
220
+ }, timeoutMs);
105
221
  child.on("close", async () => {
106
222
  clearTimeout(timeout);
107
223
  try {
@@ -130,8 +246,8 @@ async function callBridgeViaApp(args) {
130
246
  /**
131
247
  * Convenience: call bridge, check status, return data or throw.
132
248
  */
133
- export async function bridgeData(args) {
134
- const result = await callBridge(args);
249
+ export async function bridgeData(args, opts) {
250
+ const result = await callBridge(args, opts);
135
251
  if (result.status === "error") {
136
252
  throw new Error(result.error ?? "apple-bridge returned an error");
137
253
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { bridgeData } from "../bridge.js";
3
+ // Reason: Mail tools that iterate mailboxes via AppleScript can legitimately
4
+ // run for a minute or more on large accounts. The Swift bridge has a 90s
5
+ // osascript watchdog (Mail.swift `defaultAppleScriptTimeout`); the TS timeout
6
+ // must outlive that so the Swift watchdog reports a clean error instead of
7
+ // being cut off mid-execution by the TS-side process-group kill.
8
+ const MAIL_SCAN_TIMEOUT_MS = 120_000;
3
9
  export function registerMailTools(server) {
4
10
  server.tool("mail.list_accounts", "List all Apple Mail accounts with their mailboxes and unread counts. Requires Mail.app to be running.", {}, async () => {
5
11
  const data = await bridgeData(["mail-accounts"]);
@@ -20,12 +26,15 @@ export function registerMailTools(server) {
20
26
  if (limit) {
21
27
  args.push("--limit", String(limit));
22
28
  }
23
- const data = await bridgeData(args);
29
+ const data = await bridgeData(args, { timeoutMs: MAIL_SCAN_TIMEOUT_MS });
24
30
  return {
25
31
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
26
32
  };
27
33
  });
28
- server.tool("mail.search", "Search email messages by subject, sender, body, or all fields (default: all). Returns headers only (no body). Body search across all mailboxes may be slow on large accounts.", {
34
+ server.tool("mail.search", "Search email messages by subject, sender, body, or all fields (default: all). Returns headers only (no body). " +
35
+ "SCOPE RULE: body or all-fields search across mailbox='all' AND no specific account is REFUSED — it can lock " +
36
+ "Mail.app for minutes. Narrow the scope: pick a specific account, a specific mailbox, or set searchIn to " +
37
+ "'subject' or 'sender' before using mailbox='all'.", {
29
38
  query: z
30
39
  .string()
31
40
  .describe("Search term to match against message fields (controlled by searchIn)"),
@@ -36,11 +45,11 @@ export function registerMailTools(server) {
36
45
  mailbox: z
37
46
  .string()
38
47
  .optional()
39
- .describe("Mailbox to search in (default: inbox). Use 'all' to search all mailboxes."),
48
+ .describe("Mailbox to search in (default: inbox). Use 'all' to search all mailboxes (requires a specific account when searchIn includes body)."),
40
49
  searchIn: z
41
50
  .enum(["subject", "sender", "body", "all"])
42
51
  .optional()
43
- .describe("Fields to search: subject, sender, body, or all (default: all)"),
52
+ .describe("Fields to search: subject, sender, body, or all (default: all). Use 'subject' or 'sender' when searching across mailbox='all' without an account filter."),
44
53
  limit: z
45
54
  .number()
46
55
  .int()
@@ -71,7 +80,7 @@ export function registerMailTools(server) {
71
80
  if (offset !== undefined) {
72
81
  args.push("--offset", String(offset));
73
82
  }
74
- const data = await bridgeData(args);
83
+ const data = await bridgeData(args, { timeoutMs: MAIL_SCAN_TIMEOUT_MS });
75
84
  return {
76
85
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
77
86
  };
@@ -161,7 +170,7 @@ export function registerMailTools(server) {
161
170
  if (offset !== undefined) {
162
171
  args.push("--offset", String(offset));
163
172
  }
164
- const data = await bridgeData(args);
173
+ const data = await bridgeData(args, { timeoutMs: MAIL_SCAN_TIMEOUT_MS });
165
174
  return {
166
175
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
167
176
  };
@@ -1 +1 @@
1
- {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/tools/mail.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/tools/mail.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l22-io/orchard-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
4
4
  "description": "MCP server for Apple Calendar, Mail, Reminders, and Files on macOS using native EventKit",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "npm run build:swift && npm run build:ts",
22
22
  "build:ts": "tsc && chmod 755 build/index.js",
23
- "build:swift": "cd swift && swift build -c release -Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker Sources/AppleBridge/Info.plist",
23
+ "build:swift": "cd swift && swift build -c release -Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker Sources/AppleBridge/Info.plist && mkdir -p .build/AppleBridge.app/Contents/MacOS && cp .build/release/apple-bridge .build/AppleBridge.app/Contents/MacOS/apple-bridge && cp Sources/AppleBridge/Info.plist .build/AppleBridge.app/Contents/Info.plist && codesign --force --sign - .build/AppleBridge.app",
24
24
  "postinstall": "bash scripts/postinstall.sh",
25
25
  "prepublishOnly": "npm run build:ts && bash scripts/prepublish.sh",
26
26
  "dev": "tsc --watch",
@@ -1 +1 @@
1
- 20ea806633df5f1242e3c40daee70a03b1389ef6a03a6356a1751b1680b04433 apple-bridge
1
+ 8362b4b7635f775e1847b883917e1839bb8e0f3522b2b497ce471150d0ddaba4 apple-bridge
@@ -7,6 +7,11 @@ struct AppleBridge: AsyncParsableCommand {
7
7
  // This allows any subcommand to write to a file instead of stdout,
8
8
  // needed for .app bundle mode on macOS Sequoia where stdout is not capturable.
9
9
  static func main() async {
10
+ // Install before parsing so a SIGTERM during arg parsing (rare, but
11
+ // possible if node tears us down before we finish startup) still
12
+ // reaps any osascript spawned by a partial subcommand.
13
+ OsascriptRunner.installSignalHandlers()
14
+
10
15
  var args = Array(CommandLine.arguments.dropFirst())
11
16
  if let idx = args.firstIndex(of: "--output"), idx + 1 < args.count {
12
17
  JSONOutput.outputPath = args[idx + 1]
@@ -28,7 +33,7 @@ struct AppleBridge: AsyncParsableCommand {
28
33
  static let configuration = CommandConfiguration(
29
34
  commandName: "apple-bridge",
30
35
  abstract: "Native macOS bridge for Apple Calendar, Mail, Reminders, Numbers, Pages, and Keynote.",
31
- version: "0.5.0",
36
+ version: "0.6.3",
32
37
  subcommands: [
33
38
  Calendars.self,
34
39
  Events.self,
@@ -9,7 +9,7 @@ import Foundation
9
9
  enum DoctorBridge {
10
10
  static func run() async {
11
11
  var report: [String: Any] = [
12
- "version": "0.5.0",
12
+ "version": "0.6.3",
13
13
  "platform": "macOS",
14
14
  "systemVersion": ProcessInfo.processInfo.operatingSystemVersionString
15
15
  ]
@@ -147,30 +147,20 @@ enum DoctorBridge {
147
147
  }
148
148
 
149
149
  private static func checkIWorkApp(_ appName: String) -> [String: Any] {
150
- let task = Process()
151
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
152
- task.arguments = ["-e", "tell application \"\(appName)\" to return name"]
153
- let outPipe = Pipe()
154
- let errPipe = Pipe()
155
- task.standardOutput = outPipe
156
- task.standardError = errPipe
157
-
158
- do {
159
- try task.run()
160
- task.waitUntilExit()
161
- if task.terminationStatus == 0 {
162
- return ["installed": true, "accessible": true]
163
- } else {
164
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
165
- let errStr = String(data: errData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
166
- if errStr.contains("-600") || errStr.contains("not running") {
167
- return ["installed": true, "accessible": false, "note": "\(appName) is not running."]
168
- }
169
- return ["installed": false, "accessible": false, "note": "\(appName) may not be installed."]
170
- }
171
- } catch {
172
- return ["installed": false, "accessible": false, "note": "Could not check \(appName): \(error.localizedDescription)"]
150
+ let script = "tell application \"\(appName)\" to return name"
151
+ guard let result = OsascriptRunner.runRaw(script: script, timeout: doctorAppleScriptTimeout) else {
152
+ return ["installed": false, "accessible": false, "note": "Could not spawn osascript to check \(appName)."]
173
153
  }
154
+ if result.timedOut {
155
+ return ["installed": true, "accessible": false, "note": "\(appName) did not respond within \(Int(doctorAppleScriptTimeout))s."]
156
+ }
157
+ if result.status == 0 {
158
+ return ["installed": true, "accessible": true]
159
+ }
160
+ if result.stderr.contains("-600") || result.stderr.contains("not running") {
161
+ return ["installed": true, "accessible": false, "note": "\(appName) is not running."]
162
+ }
163
+ return ["installed": false, "accessible": false, "note": "\(appName) may not be installed."]
174
164
  }
175
165
 
176
166
  private static func contactsAuthName(_ status: CNAuthorizationStatus) -> String {
@@ -185,68 +175,44 @@ enum DoctorBridge {
185
175
  }
186
176
 
187
177
  private static func checkNotesAccess() -> [String: Any] {
188
- let task = Process()
189
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
190
- task.arguments = ["-e", "tell application \"Notes\" to count of accounts"]
191
- let pipe = Pipe()
192
- task.standardOutput = pipe
193
- task.standardError = Pipe()
194
-
195
- do {
196
- try task.run()
197
- task.waitUntilExit()
198
- if task.terminationStatus == 0 {
199
- let data = pipe.fileHandleForReading.readDataToEndOfFile()
200
- let output = String(data: data, encoding: .utf8)?
201
- .trimmingCharacters(in: .whitespacesAndNewlines)
202
- return [
203
- "accessible": true,
204
- "accountCount": Int(output ?? "0") ?? 0
205
- ]
206
- }
178
+ return checkAppAccess(appName: "Notes")
179
+ }
180
+
181
+ private static func checkMailAccess() -> [String: Any] {
182
+ // Reason: Try a minimal AppleScript to see if Mail.app is accessible.
183
+ // This doesn't send the permission prompt -- it just checks if we can talk to Mail.
184
+ return checkAppAccess(appName: "Mail")
185
+ }
186
+
187
+ private static func checkAppAccess(appName: String) -> [String: Any] {
188
+ let script = "tell application \"\(appName)\" to count of accounts"
189
+ guard let result = OsascriptRunner.runRaw(script: script, timeout: doctorAppleScriptTimeout) else {
207
190
  return [
208
191
  "accessible": false,
209
- "note": "Notes automation permission not yet granted or Notes.app not running."
192
+ "note": "Failed to spawn osascript to probe \(appName)."
210
193
  ]
211
- } catch {
194
+ }
195
+ if result.timedOut {
212
196
  return [
213
197
  "accessible": false,
214
- "note": "Could not run osascript: \(error.localizedDescription)"
198
+ "note": "\(appName).app did not respond within \(Int(doctorAppleScriptTimeout))s. It may be busy or unresponsive; system_doctor refuses to wait longer to avoid orphaning osascript."
215
199
  ]
216
200
  }
217
- }
218
-
219
- private static func checkMailAccess() -> [String: Any] {
220
- // Reason: Try a minimal AppleScript to see if Mail.app is accessible.
221
- // This doesn't send the permission prompt -- it just checks if we can talk to Mail.
222
- let task = Process()
223
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
224
- task.arguments = ["-e", "tell application \"Mail\" to count of accounts"]
225
- let pipe = Pipe()
226
- task.standardOutput = pipe
227
- task.standardError = Pipe()
228
-
229
- do {
230
- try task.run()
231
- task.waitUntilExit()
232
- if task.terminationStatus == 0 {
233
- let data = pipe.fileHandleForReading.readDataToEndOfFile()
234
- let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
235
- return [
236
- "accessible": true,
237
- "accountCount": Int(output ?? "0") ?? 0
238
- ]
239
- } else {
240
- return [
241
- "accessible": false,
242
- "note": "Mail automation permission not yet granted or Mail.app not running."
243
- ]
244
- }
245
- } catch {
201
+ if result.status == 0 {
246
202
  return [
247
- "accessible": false,
248
- "note": "Could not run osascript: \(error.localizedDescription)"
203
+ "accessible": true,
204
+ "accountCount": Int(result.stdout) ?? 0
249
205
  ]
250
206
  }
207
+ return [
208
+ "accessible": false,
209
+ "note": "\(appName) automation permission not yet granted or \(appName).app not running."
210
+ ]
251
211
  }
212
+
213
+ /// Hard timeout for any AppleScript invocation issued by the doctor. The
214
+ /// doctor's job is to report state quickly; if Mail.app or Notes.app
215
+ /// cannot answer "count of accounts" in this window they are by definition
216
+ /// not accessible.
217
+ private static let doctorAppleScriptTimeout: TimeInterval = 5
252
218
  }
@@ -530,41 +530,7 @@ enum KeynoteBridge {
530
530
  // MARK: - AppleScript Execution
531
531
 
532
532
  private static func runAppleScript(_ script: String) -> String? {
533
- let task = Process()
534
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
535
- task.arguments = ["-e", script]
536
-
537
- let outPipe = Pipe()
538
- let errPipe = Pipe()
539
- task.standardOutput = outPipe
540
- task.standardError = errPipe
541
-
542
- do {
543
- try task.run()
544
- task.waitUntilExit()
545
-
546
- if task.terminationStatus != 0 {
547
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
548
- let errStr = String(data: errData, encoding: .utf8)?
549
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
550
-
551
- if errStr.contains("-1743") || errStr.contains("not allowed") {
552
- JSONOutput.error("Keynote automation permission denied. Grant access in System Settings > Privacy & Security > Automation.")
553
- } else if errStr.contains("-600") || errStr.contains("not running") {
554
- JSONOutput.error("Keynote is not running. It will be launched automatically on next attempt.")
555
- } else {
556
- JSONOutput.error("AppleScript error: \(errStr)")
557
- }
558
- return nil
559
- }
560
-
561
- let data = outPipe.fileHandleForReading.readDataToEndOfFile()
562
- return String(data: data, encoding: .utf8)?
563
- .trimmingCharacters(in: .whitespacesAndNewlines)
564
- } catch {
565
- JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
566
- return nil
567
- }
533
+ return OsascriptRunner.run(script: script, appName: "Keynote")
568
534
  }
569
535
 
570
536
  private static func escapeForAppleScript(_ str: String) -> String {
@@ -116,6 +116,23 @@ enum MailBridge {
116
116
 
117
117
  /// Search messages by subject, sender, body, or all fields across accounts.
118
118
  static func search(query: String, account: String?, mailbox: String?, limit: Int, searchIn: String, offset: Int?) {
119
+ // Refuse the catastrophic combination: body-content search across every
120
+ // mailbox of every account. AppleScript's `content contains` predicate
121
+ // forces Mail to load message bodies; iterating that across all
122
+ // mailboxes can lock Mail.app on Apple Event processing for many
123
+ // minutes (it cannot service quit / UI events while the script runs).
124
+ // See: orphaned osascript incident (PID 85905, ~11min before kill).
125
+ let searchesContent = (searchIn == "body" || searchIn == "all")
126
+ let allAccounts = (account == nil || account == "all")
127
+ let allMailboxes = (mailbox == "all")
128
+ if searchesContent && allAccounts && allMailboxes {
129
+ JSONOutput.error(
130
+ "Refusing body/all-fields search across every mailbox of every account — this can lock Mail.app for minutes. " +
131
+ "Narrow the scope: pass --account <name>, or pass --mailbox <name>, or pass --search-in subject (or --search-in sender)."
132
+ )
133
+ return
134
+ }
135
+
119
136
  let searchQuery = escapeForAppleScript(query)
120
137
  let effectiveOffset = offset ?? 0
121
138
  let whereClause: String
@@ -620,42 +637,20 @@ enum MailBridge {
620
637
 
621
638
  // MARK: - AppleScript Execution
622
639
 
623
- private static func runAppleScript(_ script: String) -> String? {
624
- let task = Process()
625
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
626
- task.arguments = ["-e", script]
627
-
628
- let outPipe = Pipe()
629
- let errPipe = Pipe()
630
- task.standardOutput = outPipe
631
- task.standardError = errPipe
632
-
633
- do {
634
- try task.run()
635
- task.waitUntilExit()
636
-
637
- if task.terminationStatus != 0 {
638
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
639
- let errStr = String(data: errData, encoding: .utf8)?
640
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
641
-
642
- if errStr.contains("-1743") || errStr.contains("not allowed") {
643
- JSONOutput.error("Mail automation permission denied. Grant access in System Settings > Privacy & Security > Automation > apple-bridge > Mail.")
644
- } else if errStr.contains("-600") || errStr.contains("not running") {
645
- JSONOutput.error("Mail.app is not running. Open Mail.app and try again.")
646
- } else {
647
- JSONOutput.error("AppleScript error: \(errStr)")
648
- }
649
- return nil
650
- }
640
+ /// Mail.app's per-account/per-mailbox fallback can iterate large folders;
641
+ /// 90s is long enough for legitimate searches on big accounts but short
642
+ /// enough that node's default 30s timeout (which fires first) plus our
643
+ /// signal handler still reap osascript before it wedges Mail.app for
644
+ /// other concurrent apple-bridge instances.
645
+ private static let mailAppleScriptTimeout: TimeInterval = 90
651
646
 
652
- let data = outPipe.fileHandleForReading.readDataToEndOfFile()
653
- return String(data: data, encoding: .utf8)?
654
- .trimmingCharacters(in: .whitespacesAndNewlines)
655
- } catch {
656
- JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
657
- return nil
658
- }
647
+ private static func runAppleScript(_ script: String) -> String? {
648
+ return OsascriptRunner.run(
649
+ script: script,
650
+ timeout: mailAppleScriptTimeout,
651
+ appName: "Mail",
652
+ timeoutHint: "Narrow the search scope (specific --account or --mailbox) or use --search-in subject."
653
+ )
659
654
  }
660
655
 
661
656
  private static func escapeForAppleScript(_ str: String) -> String {
@@ -184,41 +184,7 @@ enum NotesBridge {
184
184
  // MARK: - AppleScript plumbing
185
185
 
186
186
  private static func runAppleScript(_ script: String) -> String? {
187
- let task = Process()
188
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
189
- task.arguments = ["-e", script]
190
-
191
- let outPipe = Pipe()
192
- let errPipe = Pipe()
193
- task.standardOutput = outPipe
194
- task.standardError = errPipe
195
-
196
- do {
197
- try task.run()
198
- task.waitUntilExit()
199
-
200
- if task.terminationStatus != 0 {
201
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
202
- let errStr = String(data: errData, encoding: .utf8)?
203
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
204
-
205
- if errStr.contains("-1743") || errStr.contains("not allowed") {
206
- JSONOutput.error("Notes automation permission denied. Grant access in System Settings > Privacy & Security > Automation > apple-bridge > Notes.")
207
- } else if errStr.contains("-600") || errStr.contains("not running") {
208
- JSONOutput.error("Notes.app is not running. Open Notes.app and try again.")
209
- } else {
210
- JSONOutput.error("AppleScript error: \(errStr)")
211
- }
212
- return nil
213
- }
214
-
215
- let data = outPipe.fileHandleForReading.readDataToEndOfFile()
216
- return String(data: data, encoding: .utf8)?
217
- .trimmingCharacters(in: .whitespacesAndNewlines)
218
- } catch {
219
- JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
220
- return nil
221
- }
187
+ return OsascriptRunner.run(script: script, appName: "Notes")
222
188
  }
223
189
 
224
190
  private static func escapeForAppleScript(_ str: String) -> String {
@@ -444,74 +444,13 @@ enum NumbersBridge {
444
444
  // MARK: - AppleScript Execution
445
445
 
446
446
  private static func runAppleScript(_ script: String) -> String? {
447
- let task = Process()
448
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
449
- task.arguments = ["-e", script]
450
-
451
- let outPipe = Pipe()
452
- let errPipe = Pipe()
453
- task.standardOutput = outPipe
454
- task.standardError = errPipe
455
-
456
- do {
457
- try task.run()
458
- task.waitUntilExit()
459
-
460
- if task.terminationStatus != 0 {
461
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
462
- let errStr = String(data: errData, encoding: .utf8)?
463
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
464
-
465
- if errStr.contains("-1743") || errStr.contains("not allowed") {
466
- JSONOutput.error("Numbers automation permission denied. Grant access in System Settings > Privacy & Security > Automation.")
467
- } else if errStr.contains("-600") || errStr.contains("not running") {
468
- JSONOutput.error("Numbers is not running. It will be launched automatically on next attempt.")
469
- } else {
470
- JSONOutput.error("AppleScript error: \(errStr)")
471
- }
472
- return nil
473
- }
474
-
475
- let data = outPipe.fileHandleForReading.readDataToEndOfFile()
476
- return String(data: data, encoding: .utf8)?
477
- .trimmingCharacters(in: .whitespacesAndNewlines)
478
- } catch {
479
- JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
480
- return nil
481
- }
447
+ return OsascriptRunner.run(script: script, appName: "Numbers")
482
448
  }
483
449
 
484
450
  // MARK: - JXA Execution
485
451
 
486
452
  private static func runJXA(_ script: String) -> String? {
487
- let task = Process()
488
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
489
- task.arguments = ["-l", "JavaScript", "-e", script]
490
-
491
- let outPipe = Pipe()
492
- let errPipe = Pipe()
493
- task.standardOutput = outPipe
494
- task.standardError = errPipe
495
-
496
- do {
497
- try task.run()
498
- task.waitUntilExit()
499
-
500
- if task.terminationStatus != 0 {
501
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
502
- let errStr = String(data: errData, encoding: .utf8)?
503
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
504
- JSONOutput.error("JXA error: \(errStr)")
505
- return nil
506
- }
507
-
508
- let data = outPipe.fileHandleForReading.readDataToEndOfFile()
509
- return String(data: data, encoding: .utf8)?
510
- .trimmingCharacters(in: .whitespacesAndNewlines)
511
- } catch {
512
- JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
513
- return nil
514
- }
453
+ return OsascriptRunner.run(script: script, language: .javaScript, appName: "Numbers")
515
454
  }
516
455
 
517
456
  private static func escapeForAppleScript(_ str: String) -> String {
@@ -0,0 +1,171 @@
1
+ import Foundation
2
+ import Darwin
3
+
4
+ // Reason: Single PID slot for the currently-running osascript child. apple-bridge
5
+ // runs one subcommand per invocation and each runOsascript call is synchronous,
6
+ // so at most one osascript is alive at any moment. Stored as sig_atomic_t so
7
+ // the C-convention signal handler can read it without locks (async-signal-safe).
8
+ // File-scope is required: @convention(c) closures cannot capture Swift state, so
9
+ // the handler reaches it as a C global.
10
+ private var currentChildPid: sig_atomic_t = 0
11
+
12
+ /// Language flavour passed to `osascript`. AppleScript is the default; JXA is
13
+ /// used by Numbers for native JSON output via JavaScript for Automation.
14
+ enum OsascriptLanguage {
15
+ case appleScript
16
+ case javaScript
17
+ }
18
+
19
+ /// Raw result of an osascript invocation. Used by callers that need to inspect
20
+ /// status without emitting a JSON error envelope (e.g. Doctor's probes).
21
+ struct OsascriptResult {
22
+ let status: Int32
23
+ let stdout: String
24
+ let stderr: String
25
+ let timedOut: Bool
26
+ }
27
+
28
+ enum OsascriptRunner {
29
+
30
+ /// Default watchdog window. Long enough for typical iWork/Notes/Mail
31
+ /// operations under load; short enough that the Swift watchdog fires
32
+ /// before node's per-call timeout under default conditions.
33
+ static let defaultTimeout: TimeInterval = 120
34
+
35
+ /// SIGKILL grace period after the initial SIGTERM. Apple Events held by
36
+ /// host apps (Mail, Notes) can keep osascript unresponsive to SIGTERM for
37
+ /// a moment; SIGKILL is uncatchable so the second signal always wins.
38
+ private static let sigkillGrace: TimeInterval = 2
39
+
40
+ /// Install signal handlers that kill the currently-running osascript child
41
+ /// before apple-bridge dies from SIGTERM/SIGINT/SIGHUP. Required because
42
+ /// Foundation.Process on macOS spawns its child into a new process group,
43
+ /// so the node-side group-kill in src/bridge.ts only hits apple-bridge --
44
+ /// the osascript grandchild gets orphaned (PPID=1) and keeps holding
45
+ /// Mail.app's Apple Event queue hostage. The handler is async-signal-safe:
46
+ /// it only reads currentChildPid (sig_atomic_t) and calls kill/signal/raise,
47
+ /// all of which are listed as signal-safe by POSIX.
48
+ static func installSignalHandlers() {
49
+ let handler: @convention(c) (Int32) -> Void = { signo in
50
+ let pid = pid_t(currentChildPid)
51
+ if pid > 0 {
52
+ _ = kill(pid, SIGKILL)
53
+ }
54
+ // Restore default disposition and re-raise so we exit with the
55
+ // standard signal status (and any system-level cleanup runs).
56
+ signal(signo, SIG_DFL)
57
+ raise(signo)
58
+ }
59
+ signal(SIGTERM, handler)
60
+ signal(SIGINT, handler)
61
+ signal(SIGHUP, handler)
62
+ }
63
+
64
+ /// Convenience entry point that emits a `JSONOutput.error` and returns nil
65
+ /// on any failure (timeout, non-zero exit, spawn failure). On success
66
+ /// returns the trimmed stdout. `appName` is used to build the standard
67
+ /// permission-denied / not-running messages that every iWork module emits;
68
+ /// `timeoutHint` is an optional sentence appended to the timeout message
69
+ /// (used by Mail to suggest narrowing the search scope).
70
+ static func run(
71
+ script: String,
72
+ language: OsascriptLanguage = .appleScript,
73
+ timeout: TimeInterval = defaultTimeout,
74
+ appName: String,
75
+ timeoutHint: String? = nil
76
+ ) -> String? {
77
+ guard let result = runRaw(script: script, language: language, timeout: timeout) else {
78
+ JSONOutput.error("Failed to spawn osascript")
79
+ return nil
80
+ }
81
+ if result.timedOut {
82
+ var msg = "\(appName) AppleScript exceeded \(Int(timeout))s timeout - killed to free \(appName)."
83
+ if let hint = timeoutHint {
84
+ msg += " \(hint)"
85
+ }
86
+ JSONOutput.error(msg)
87
+ return nil
88
+ }
89
+ if result.status != 0 {
90
+ let errStr = result.stderr.isEmpty ? "Unknown error" : result.stderr
91
+ if errStr.contains("-1743") || errStr.contains("not allowed") {
92
+ JSONOutput.error("\(appName) automation permission denied. Grant access in System Settings > Privacy & Security > Automation > apple-bridge > \(appName).")
93
+ } else if errStr.contains("-600") || errStr.contains("not running") {
94
+ JSONOutput.error("\(appName) is not running. Open \(appName) and try again.")
95
+ } else {
96
+ JSONOutput.error("AppleScript error: \(errStr)")
97
+ }
98
+ return nil
99
+ }
100
+ return result.stdout
101
+ }
102
+
103
+ /// Raw runner that returns status + stdout + stderr without side effects on
104
+ /// the JSON envelope. Returns nil only on spawn failure. Used by callers
105
+ /// that need to inspect status (e.g. Doctor probing "is Notes accessible").
106
+ static func runRaw(
107
+ script: String,
108
+ language: OsascriptLanguage = .appleScript,
109
+ timeout: TimeInterval = defaultTimeout
110
+ ) -> OsascriptResult? {
111
+ let task = Process()
112
+ task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
113
+ switch language {
114
+ case .appleScript:
115
+ task.arguments = ["-e", script]
116
+ case .javaScript:
117
+ task.arguments = ["-l", "JavaScript", "-e", script]
118
+ }
119
+
120
+ let outPipe = Pipe()
121
+ let errPipe = Pipe()
122
+ task.standardOutput = outPipe
123
+ task.standardError = errPipe
124
+
125
+ do {
126
+ try task.run()
127
+ } catch {
128
+ return nil
129
+ }
130
+
131
+ let pid = task.processIdentifier
132
+ currentChildPid = sig_atomic_t(pid)
133
+ defer { currentChildPid = 0 }
134
+
135
+ let timeoutLock = NSLock()
136
+ var didTimeOut = false
137
+
138
+ let watchdog = DispatchWorkItem {
139
+ guard task.isRunning else { return }
140
+ timeoutLock.lock()
141
+ didTimeOut = true
142
+ timeoutLock.unlock()
143
+ task.terminate()
144
+ DispatchQueue.global().asyncAfter(deadline: .now() + sigkillGrace) {
145
+ if task.isRunning { kill(pid, SIGKILL) }
146
+ }
147
+ }
148
+ DispatchQueue.global().asyncAfter(deadline: .now() + timeout, execute: watchdog)
149
+
150
+ task.waitUntilExit()
151
+ watchdog.cancel()
152
+
153
+ let outData = outPipe.fileHandleForReading.readDataToEndOfFile()
154
+ let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
155
+ let stdout = String(data: outData, encoding: .utf8)?
156
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
157
+ let stderr = String(data: errData, encoding: .utf8)?
158
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
159
+
160
+ timeoutLock.lock()
161
+ let timedOut = didTimeOut
162
+ timeoutLock.unlock()
163
+
164
+ return OsascriptResult(
165
+ status: task.terminationStatus,
166
+ stdout: stdout,
167
+ stderr: stderr,
168
+ timedOut: timedOut
169
+ )
170
+ }
171
+ }
@@ -403,41 +403,7 @@ enum PagesBridge {
403
403
  // MARK: - AppleScript Execution
404
404
 
405
405
  private static func runAppleScript(_ script: String) -> String? {
406
- let task = Process()
407
- task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
408
- task.arguments = ["-e", script]
409
-
410
- let outPipe = Pipe()
411
- let errPipe = Pipe()
412
- task.standardOutput = outPipe
413
- task.standardError = errPipe
414
-
415
- do {
416
- try task.run()
417
- task.waitUntilExit()
418
-
419
- if task.terminationStatus != 0 {
420
- let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
421
- let errStr = String(data: errData, encoding: .utf8)?
422
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
423
-
424
- if errStr.contains("-1743") || errStr.contains("not allowed") {
425
- JSONOutput.error("Pages automation permission denied. Grant access in System Settings > Privacy & Security > Automation.")
426
- } else if errStr.contains("-600") || errStr.contains("not running") {
427
- JSONOutput.error("Pages is not running. It will be launched automatically on next attempt.")
428
- } else {
429
- JSONOutput.error("AppleScript error: \(errStr)")
430
- }
431
- return nil
432
- }
433
-
434
- let data = outPipe.fileHandleForReading.readDataToEndOfFile()
435
- return String(data: data, encoding: .utf8)?
436
- .trimmingCharacters(in: .whitespacesAndNewlines)
437
- } catch {
438
- JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
439
- return nil
440
- }
406
+ return OsascriptRunner.run(script: script, appName: "Pages")
441
407
  }
442
408
 
443
409
  private static func escapeForAppleScript(_ str: String) -> String {