@l22-io/orchard-mcp 0.6.1 → 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.js +30 -13
- package/build/bridge.js.map +1 -1
- package/package.json +1 -1
- package/swift/.build/AppleBridge.app/Contents/MacOS/apple-bridge +0 -0
- package/swift/.build/AppleBridge.app.sha256 +1 -1
- package/swift/Sources/AppleBridge/AppleBridge.swift +6 -1
- package/swift/Sources/AppleBridge/Doctor.swift +43 -77
- package/swift/Sources/AppleBridge/Keynote.swift +1 -35
- package/swift/Sources/AppleBridge/Mail.swift +14 -79
- package/swift/Sources/AppleBridge/Notes.swift +1 -35
- package/swift/Sources/AppleBridge/Numbers.swift +2 -63
- package/swift/Sources/AppleBridge/OsascriptRunner.swift +171 -0
- package/swift/Sources/AppleBridge/Pages.swift +1 -35
package/build/bridge.js
CHANGED
|
@@ -106,14 +106,8 @@ function runBridgeProcess(bin, args, timeoutMs) {
|
|
|
106
106
|
const MAX_BYTES = 10 * 1024 * 1024;
|
|
107
107
|
let settled = false;
|
|
108
108
|
let timedOut = false;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return;
|
|
112
|
-
settled = true;
|
|
113
|
-
clearTimeout(timer);
|
|
114
|
-
clearTimeout(sigkillTimer);
|
|
115
|
-
resolvePromise(result);
|
|
116
|
-
};
|
|
109
|
+
let killEscalated = false;
|
|
110
|
+
let sigkillTimer = null;
|
|
117
111
|
const killGroup = (signal) => {
|
|
118
112
|
try {
|
|
119
113
|
process.kill(-pid, signal);
|
|
@@ -122,17 +116,40 @@ function runBridgeProcess(bin, args, timeoutMs) {
|
|
|
122
116
|
// Group may already be gone; nothing to do.
|
|
123
117
|
}
|
|
124
118
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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;
|
|
129
130
|
killGroup("SIGTERM");
|
|
130
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();
|
|
131
148
|
}, timeoutMs);
|
|
132
149
|
child.stdout?.on("data", (d) => {
|
|
133
150
|
totalBytes += d.length;
|
|
134
151
|
if (totalBytes > MAX_BYTES) {
|
|
135
|
-
|
|
152
|
+
escalateKill();
|
|
136
153
|
settle({
|
|
137
154
|
status: "error",
|
|
138
155
|
spawnError: `apple-bridge output exceeded ${MAX_BYTES} bytes`,
|
package/build/bridge.js.map
CHANGED
|
@@ -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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,0EAA0E;AAC1E,oDAAoD;AACpD,wEAAwE;AACxE,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,2EAA2E;AAC3E,gFAAgF;AAChF,IAAI,cAAc,GAAG,KAAK,CAAC;AAC3B,SAAS,gBAAgB;IACvB,IAAI,cAAc;QAAE,OAAO;IAC3B,cAAc,GAAG,IAAI,CAAC;IACtB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CACX,6DAA6D,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM;YAC/F,gFAAgF,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CACX,6DAA6D,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM;YAC/F,qFAAqF,CACtF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,aAAa;IACpB,+DAA+D;IAC/D,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,gBAAgB,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACtC,CAAC;IACD,4EAA4E;IAC5E,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;AAC7G,CAAC;AAED,SAAS,gBAAgB;IACvB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,gBAAgB,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;AACxE,CAAC;AAoBD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAc,EACd,OAAsB,EAAE;IAExB,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAE5D,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;QACvD,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,OAAO;gBACL,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,gCAAgC,SAAS,IAAI;aACrD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,oEAAoE;QACpE,IACE,MAAM,CAAC,MAAM,KAAK,OAAO;YACzB,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,EACtC,CAAC;YACD,OAAO,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,IAAI,iCAAiC,EAAE,CAAC;AAC5F,CAAC;AASD;;;;;;GAMG;AACH,SAAS,gBAAgB,CACvB,GAAW,EACX,IAAc,EACd,SAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE;QACpC,IAAI,KAAK,CAAC;QACV,IAAI,CAAC;YACH,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;gBACvB,QAAQ,EAAE,IAAI,EAAE,+CAA+C;gBAC/D,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;YAChF,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;YACrD,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACtB,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,oCAAoC,EAAE,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;QACnC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,aAAa,GAAG,KAAK,CAAC;QAC1B,IAAI,YAAY,GAA0B,IAAI,CAAC;QAE/C,MAAM,SAAS,GAAG,CAAC,MAAsB,EAAE,EAAE;YAC3C,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,4CAA4C;YAC9C,CAAC;QACH,CAAC,CAAC;QAEF,4EAA4E;QAC5E,wEAAwE;QACxE,qEAAqE;QACrE,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,yCAAyC;QACzC,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,IAAI,aAAa;gBAAE,OAAO;YAC1B,aAAa,GAAG,IAAI,CAAC;YACrB,SAAS,CAAC,SAAS,CAAC,CAAC;YACrB,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;YACxE,YAAY,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC,CAAC;QAEF,MAAM,MAAM,GAAG,CAAC,MAAoB,EAAE,EAAE;YACtC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,aAAa,IAAI,YAAY;gBAAE,YAAY,CAAC,YAAY,CAAC,CAAC;YAC/D,oEAAoE;YACpE,oEAAoE;YACpE,cAAc,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC,CAAC;QAEF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,QAAQ,GAAG,IAAI,CAAC;YAChB,YAAY,EAAE,CAAC;QACjB,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACrC,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC;YACvB,IAAI,UAAU,GAAG,SAAS,EAAE,CAAC;gBAC3B,YAAY,EAAE,CAAC;gBACf,MAAM,CAAC;oBACL,MAAM,EAAE,OAAO;oBACf,UAAU,EAAE,gCAAgC,SAAS,QAAQ;iBAC9D,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACrC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAChE,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAC;YACnD,CAAC;YACD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5C,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACtD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAmB,CAAC;gBACpD,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YACnC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;gBACrE,MAAM,CAAC;oBACL,MAAM,EAAE,OAAO;oBACf,UAAU,EAAE,uCAAuC,GAAG,EAAE;iBACzD,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAC7B,IAAc,EACd,SAAiB;IAEjB,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IACnC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,gBAAgB,UAAU,EAAE,OAAO,CAAC,CAAC;IAE1E,IAAI,CAAC;QACH,4BAA4B;QAC5B,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,gCAAgC,OAAO,mEAAmE;SAClH,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE;YAC1B,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO;YACzB,QAAQ,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,UAAU;SAC1C,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,KAAK,CAAC,IAAI,EAAE,CAAC;YACb,cAAc,CAAC;gBACb,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,4CAA4C,SAAS,IAAI;aACjE,CAAC,CAAC;QACL,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YAC3B,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBACjD,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;gBAClD,cAAc,CAAC,MAAM,CAAC,CAAC;YACzB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACzC,MAAM,GAAG,GACP,GAAG,YAAY,KAAK;oBAClB,CAAC,CAAC,GAAG,CAAC,OAAO;oBACb,CAAC,CAAC,mCAAmC,CAAC;gBAC1C,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,cAAc,CAAC;gBACb,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,iCAAiC,GAAG,CAAC,OAAO,EAAE;aACtD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAc,EACd,IAAoB;IAEpB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,gCAAgC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
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.
|
|
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.
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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": "
|
|
192
|
+
"note": "Failed to spawn osascript to probe \(appName)."
|
|
210
193
|
]
|
|
211
|
-
}
|
|
194
|
+
}
|
|
195
|
+
if result.timedOut {
|
|
212
196
|
return [
|
|
213
197
|
"accessible": false,
|
|
214
|
-
"note": "
|
|
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":
|
|
248
|
-
"
|
|
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
|
-
|
|
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 {
|
|
@@ -637,85 +637,20 @@ enum MailBridge {
|
|
|
637
637
|
|
|
638
638
|
// MARK: - AppleScript Execution
|
|
639
639
|
|
|
640
|
-
///
|
|
641
|
-
///
|
|
642
|
-
///
|
|
643
|
-
///
|
|
644
|
-
///
|
|
645
|
-
private static let
|
|
646
|
-
|
|
647
|
-
private static func runAppleScript(_ script: String
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
task.standardOutput = outPipe
|
|
655
|
-
task.standardError = errPipe
|
|
656
|
-
|
|
657
|
-
do {
|
|
658
|
-
try task.run()
|
|
659
|
-
|
|
660
|
-
// Watchdog: terminate osascript if it exceeds the timeout. SIGTERM
|
|
661
|
-
// first so the script gets a chance to clean up; SIGKILL after a
|
|
662
|
-
// short grace period if it ignores SIGTERM (Apple Events held by
|
|
663
|
-
// Mail.app can keep an osascript subprocess unresponsive to TERM).
|
|
664
|
-
let pid = task.processIdentifier
|
|
665
|
-
let didTimeOut = TimeoutFlag()
|
|
666
|
-
let watchdog = DispatchWorkItem {
|
|
667
|
-
guard task.isRunning else { return }
|
|
668
|
-
didTimeOut.set()
|
|
669
|
-
task.terminate()
|
|
670
|
-
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
|
|
671
|
-
if task.isRunning { kill(pid, SIGKILL) }
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
DispatchQueue.global().asyncAfter(deadline: .now() + timeoutSeconds, execute: watchdog)
|
|
675
|
-
|
|
676
|
-
task.waitUntilExit()
|
|
677
|
-
watchdog.cancel()
|
|
678
|
-
|
|
679
|
-
if didTimeOut.value {
|
|
680
|
-
JSONOutput.error(
|
|
681
|
-
"Mail AppleScript exceeded \(Int(timeoutSeconds))s timeout — killed to free Mail.app. " +
|
|
682
|
-
"Narrow the search scope (specific --account or --mailbox) or use --search-in subject."
|
|
683
|
-
)
|
|
684
|
-
return nil
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
if task.terminationStatus != 0 {
|
|
688
|
-
let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
|
|
689
|
-
let errStr = String(data: errData, encoding: .utf8)?
|
|
690
|
-
.trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown error"
|
|
691
|
-
|
|
692
|
-
if errStr.contains("-1743") || errStr.contains("not allowed") {
|
|
693
|
-
JSONOutput.error("Mail automation permission denied. Grant access in System Settings > Privacy & Security > Automation > apple-bridge > Mail.")
|
|
694
|
-
} else if errStr.contains("-600") || errStr.contains("not running") {
|
|
695
|
-
JSONOutput.error("Mail.app is not running. Open Mail.app and try again.")
|
|
696
|
-
} else {
|
|
697
|
-
JSONOutput.error("AppleScript error: \(errStr)")
|
|
698
|
-
}
|
|
699
|
-
return nil
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
let data = outPipe.fileHandleForReading.readDataToEndOfFile()
|
|
703
|
-
return String(data: data, encoding: .utf8)?
|
|
704
|
-
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
705
|
-
} catch {
|
|
706
|
-
JSONOutput.error("Failed to run osascript: \(error.localizedDescription)")
|
|
707
|
-
return nil
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/// Tiny actor-like flag so the watchdog closure can signal the main
|
|
712
|
-
/// thread that it fired, without racing with `task.terminationReason`
|
|
713
|
-
/// (which is only set after the kernel reaps the child).
|
|
714
|
-
private final class TimeoutFlag {
|
|
715
|
-
private let lock = NSLock()
|
|
716
|
-
private var fired = false
|
|
717
|
-
func set() { lock.lock(); fired = true; lock.unlock() }
|
|
718
|
-
var value: Bool { lock.lock(); defer { lock.unlock() }; return fired }
|
|
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
|
|
646
|
+
|
|
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
|
+
)
|
|
719
654
|
}
|
|
720
655
|
|
|
721
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|