apple-tools-mcp 2.0.0 → 2.0.2
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 +78 -20
- package/index.js +30 -7
- package/indexer.js +4 -1
- package/lib/appleScript.js +209 -21
- package/lib/calendarWrite.js +835 -23
- package/lib/contactsWrite.js +192 -5
- package/lib/eventKitSession.js +369 -0
- package/lib/mailWrite.js +332 -26
- package/lib/messagesWrite.js +39 -3
- package/lib/permissions.js +360 -0
- package/lib/processMode.js +47 -0
- package/lib/shell.js +50 -5
- package/lib/writeGuards.js +8 -0
- package/lib/writeRouting.js +76 -6
- package/lib/writeTools.js +26 -8
- package/package.json +4 -1
- package/scripts/postinstall.js +32 -0
- package/scripts/smoke-writes.js +188 -17
package/lib/mailWrite.js
CHANGED
|
@@ -6,12 +6,25 @@
|
|
|
6
6
|
* property). `file_path` from mail_search / mail_recent is also accepted and
|
|
7
7
|
* is resolved to a Message-ID by reading the .emlx headers, so callers never
|
|
8
8
|
* have to invent an identifier.
|
|
9
|
+
*
|
|
10
|
+
* After a send/reply/forward hang, Sent (and Outbox) is checked before the
|
|
11
|
+
* tool reports failure. A message already in Sent is success — never a TCC
|
|
12
|
+
* fail. ETIMEDOUT / -1712 is a timeout (find/reply/open before send too);
|
|
13
|
+
* -1743 / -10004 is a hard deny.
|
|
9
14
|
*/
|
|
10
15
|
|
|
11
16
|
import fs from "fs";
|
|
12
17
|
import path from "path";
|
|
13
18
|
import { validateEmailPath, unfoldRfc822Headers, safeMatch, stripHtmlTags } from "./validators.js";
|
|
14
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
runAppleScript,
|
|
21
|
+
asString,
|
|
22
|
+
MAIL_TCC_GUIDANCE,
|
|
23
|
+
MAIL_SEND_TIMEOUT_GUIDANCE,
|
|
24
|
+
MAIL_APP_NOT_RUNNING_GUIDANCE,
|
|
25
|
+
ATTRIBUTION_GUIDANCE,
|
|
26
|
+
isHardTccDenial
|
|
27
|
+
} from "./appleScript.js";
|
|
15
28
|
import {
|
|
16
29
|
planWrite,
|
|
17
30
|
validateEmailList,
|
|
@@ -98,6 +111,43 @@ function recipientLines(addresses, kind) {
|
|
|
98
111
|
.join("\n");
|
|
99
112
|
}
|
|
100
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Ship-gate / first-run probe: the compose verb that hangs when node → Mail
|
|
116
|
+
* Automation is denied. `tell Mail to get name` is not enough — Mini
|
|
117
|
+
* diagnosis showed that returns while `make new outgoing message` blocks.
|
|
118
|
+
* Nothing is sent; the outgoing message is deleted immediately.
|
|
119
|
+
*/
|
|
120
|
+
export const MAIL_AUTOMATION_PROBE_SUBJECT = "ATM Mail Automation probe";
|
|
121
|
+
|
|
122
|
+
export function buildMailAutomationProbeScript() {
|
|
123
|
+
return `tell application "Mail"
|
|
124
|
+
set probe to make new outgoing message with properties {subject:${asString(MAIL_AUTOMATION_PROBE_SUBJECT)}, content:"", visible:false}
|
|
125
|
+
delete probe
|
|
126
|
+
end tell
|
|
127
|
+
return "OK"`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Live Mail Apple Events check. Not a dry_run: that path never talks to Mail.
|
|
132
|
+
* @returns {{ ok: boolean, message: string }}
|
|
133
|
+
*/
|
|
134
|
+
export function probeMailAutomation() {
|
|
135
|
+
const action = "mail_automation_probe";
|
|
136
|
+
const summary = "compose a temporary outgoing message in Mail (Automation / Apple Events check; nothing is sent)";
|
|
137
|
+
const result = runAppleScript(buildMailAutomationProbeScript(), { timeout: 30000, appName: "Mail" });
|
|
138
|
+
if (!result.ok) {
|
|
139
|
+
return { ok: false, message: failure(action, summary, result, []), kind: result.kind };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
kind: null,
|
|
144
|
+
message: writeSuccessMessage(
|
|
145
|
+
action,
|
|
146
|
+
"Mail Automation allowed (composed and discarded a temporary outgoing message; nothing was sent)"
|
|
147
|
+
)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
101
151
|
/**
|
|
102
152
|
* Build the outgoing-message script shared by send and draft.
|
|
103
153
|
*
|
|
@@ -135,13 +185,219 @@ function summarizeRecipients(to, cc, bcc) {
|
|
|
135
185
|
return parts.join("; ");
|
|
136
186
|
}
|
|
137
187
|
|
|
188
|
+
export const SENT_VERIFY_FOUND = "FOUND";
|
|
189
|
+
export const SENT_VERIFY_TIMEOUT_MS = 15000;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Hard Mail Automation deny: -1743 / -10004 / "not authorized…", or
|
|
193
|
+
* classify kind `tcc`. A spawnSync hang is `timeout`, not this.
|
|
194
|
+
*/
|
|
195
|
+
export function isMailHardTcc(result) {
|
|
196
|
+
if (!result) return false;
|
|
197
|
+
if (result.kind === "tcc") return true;
|
|
198
|
+
return isHardTccDenial(result.error);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Find/reply/send hang (ETIMEDOUT / -1712). Not a hard TCC deny.
|
|
203
|
+
*/
|
|
204
|
+
export function isMailSendTimeout(result) {
|
|
205
|
+
if (!result) return false;
|
|
206
|
+
if (result.kind !== "timeout") return false;
|
|
207
|
+
return !isHardTccDenial(result.error);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function mailBoxesPreamble() {
|
|
211
|
+
return ` set cutoff to (current date) - (10 * minutes)
|
|
212
|
+
set boxes to {}
|
|
213
|
+
try
|
|
214
|
+
set end of boxes to sent mailbox
|
|
215
|
+
end try
|
|
216
|
+
try
|
|
217
|
+
set end of boxes to outgoing mailbox
|
|
218
|
+
end try
|
|
219
|
+
repeat with acct in accounts
|
|
220
|
+
try
|
|
221
|
+
set end of boxes to sent mailbox of acct
|
|
222
|
+
end try
|
|
223
|
+
end repeat`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function appleScriptToList(addresses = []) {
|
|
227
|
+
return `{${addresses.map((address) => asString(address)).join(", ")}}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Look in Sent / Outbox for a reply of `messageId`.
|
|
232
|
+
* Proper Mail replies set In-Reply-To. The fallback `make new outgoing
|
|
233
|
+
* message` path does not, so also match exact "Re: " & original subject.
|
|
234
|
+
* Never treat the original itself (or a Re: that merely contains the
|
|
235
|
+
* original subject) as this send.
|
|
236
|
+
*/
|
|
237
|
+
export function buildFindSentByInReplyToScript(messageId) {
|
|
238
|
+
const idLit = asString(messageId);
|
|
239
|
+
const needleBare = asString(messageId);
|
|
240
|
+
const needleAngle = asString(`<${messageId}>`);
|
|
241
|
+
return `${findMessageHandler()}
|
|
242
|
+
|
|
243
|
+
set origSubject to ""
|
|
244
|
+
try
|
|
245
|
+
set origMsg to atmFindMessage(${idLit})
|
|
246
|
+
tell application "Mail" to set origSubject to subject of origMsg
|
|
247
|
+
end try
|
|
248
|
+
tell application "Mail"
|
|
249
|
+
${mailBoxesPreamble()}
|
|
250
|
+
repeat with boxRef in boxes
|
|
251
|
+
try
|
|
252
|
+
set recentMsgs to (messages of boxRef whose date sent > cutoff)
|
|
253
|
+
repeat with msg in recentMsgs
|
|
254
|
+
try
|
|
255
|
+
set candId to message id of msg
|
|
256
|
+
if candId is ${needleBare} then
|
|
257
|
+
-- The original, not the reply we just sent.
|
|
258
|
+
else
|
|
259
|
+
try
|
|
260
|
+
set src to source of msg
|
|
261
|
+
if src contains ("In-Reply-To: " & ${needleAngle}) then return "${SENT_VERIFY_FOUND}"
|
|
262
|
+
if src contains ("In-Reply-To: " & ${needleBare}) then return "${SENT_VERIFY_FOUND}"
|
|
263
|
+
end try
|
|
264
|
+
if origSubject is not "" then
|
|
265
|
+
set subj to subject of msg
|
|
266
|
+
if subj is ("Re: " & origSubject) then return "${SENT_VERIFY_FOUND}"
|
|
267
|
+
end if
|
|
268
|
+
end if
|
|
269
|
+
end try
|
|
270
|
+
end repeat
|
|
271
|
+
end try
|
|
272
|
+
end repeat
|
|
273
|
+
end tell
|
|
274
|
+
return "NOT_FOUND"`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Look in Sent / Outbox for a forward of `messageId` to `toAddresses`.
|
|
279
|
+
* Mail.app forwards do not set In-Reply-To / References — those headers are
|
|
280
|
+
* replies. Require the intended recipient plus either an exact Fwd:/Fw:
|
|
281
|
+
* subject or the original Message-ID in a forwarded body. Skip the original
|
|
282
|
+
* itself, In-Reply-To hits, and unrelated Fwd: mail that only shares a To.
|
|
283
|
+
*/
|
|
284
|
+
export function buildFindSentForwardScript(messageId, toAddresses = []) {
|
|
285
|
+
const idLit = asString(messageId);
|
|
286
|
+
const needleBare = asString(messageId);
|
|
287
|
+
const needleAngle = asString(`<${messageId}>`);
|
|
288
|
+
const toList = appleScriptToList(toAddresses);
|
|
289
|
+
return `${findMessageHandler()}
|
|
290
|
+
|
|
291
|
+
set origSubject to ""
|
|
292
|
+
try
|
|
293
|
+
set origMsg to atmFindMessage(${idLit})
|
|
294
|
+
tell application "Mail" to set origSubject to subject of origMsg
|
|
295
|
+
end try
|
|
296
|
+
tell application "Mail"
|
|
297
|
+
${mailBoxesPreamble()}
|
|
298
|
+
set wantedTos to ${toList}
|
|
299
|
+
repeat with boxRef in boxes
|
|
300
|
+
try
|
|
301
|
+
set recentMsgs to (messages of boxRef whose date sent > cutoff)
|
|
302
|
+
repeat with msg in recentMsgs
|
|
303
|
+
try
|
|
304
|
+
set candId to message id of msg
|
|
305
|
+
if candId is ${needleBare} then
|
|
306
|
+
-- The original, not the forward we just sent.
|
|
307
|
+
else
|
|
308
|
+
set src to source of msg
|
|
309
|
+
if src contains ("In-Reply-To: " & ${needleAngle}) or src contains ("In-Reply-To: " & ${needleBare}) then
|
|
310
|
+
-- A reply to the original is not this forward.
|
|
311
|
+
else
|
|
312
|
+
set hitTo to false
|
|
313
|
+
try
|
|
314
|
+
repeat with recip in (to recipients of msg)
|
|
315
|
+
set recipAddr to address of recip as string
|
|
316
|
+
repeat with wanted in wantedTos
|
|
317
|
+
if recipAddr is (wanted as string) then set hitTo to true
|
|
318
|
+
end repeat
|
|
319
|
+
end repeat
|
|
320
|
+
end try
|
|
321
|
+
if hitTo then
|
|
322
|
+
set subj to subject of msg
|
|
323
|
+
set exactFwd to false
|
|
324
|
+
if origSubject is not "" then
|
|
325
|
+
if subj is ("Fwd: " & origSubject) then set exactFwd to true
|
|
326
|
+
if subj is ("Fw: " & origSubject) then set exactFwd to true
|
|
327
|
+
if subj is ("FW: " & origSubject) then set exactFwd to true
|
|
328
|
+
if subj is ("Forward: " & origSubject) then set exactFwd to true
|
|
329
|
+
end if
|
|
330
|
+
set looksForward to exactFwd
|
|
331
|
+
if subj starts with "Fwd:" or subj starts with "Fw:" or subj starts with "FW:" or subj starts with "Forward:" then set looksForward to true
|
|
332
|
+
if src contains "Begin forwarded message" then set looksForward to true
|
|
333
|
+
set mentionsOrigId to false
|
|
334
|
+
if src contains ${needleAngle} then set mentionsOrigId to true
|
|
335
|
+
if src contains ${needleBare} then set mentionsOrigId to true
|
|
336
|
+
if exactFwd then return "${SENT_VERIFY_FOUND}"
|
|
337
|
+
if looksForward and mentionsOrigId then return "${SENT_VERIFY_FOUND}"
|
|
338
|
+
end if
|
|
339
|
+
end if
|
|
340
|
+
end if
|
|
341
|
+
end try
|
|
342
|
+
end repeat
|
|
343
|
+
end try
|
|
344
|
+
end repeat
|
|
345
|
+
end tell
|
|
346
|
+
return "NOT_FOUND"`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Look in Sent / Outbox for a compose whose subject matches exactly and
|
|
351
|
+
* was sent in the last 10 minutes.
|
|
352
|
+
*/
|
|
353
|
+
export function buildFindSentBySubjectScript(subject) {
|
|
354
|
+
const subj = asString(subject);
|
|
355
|
+
return `tell application "Mail"
|
|
356
|
+
${mailBoxesPreamble()}
|
|
357
|
+
repeat with boxRef in boxes
|
|
358
|
+
try
|
|
359
|
+
set hits to (messages of boxRef whose subject is ${subj} and date sent > cutoff)
|
|
360
|
+
if (count of hits) > 0 then return "${SENT_VERIFY_FOUND}"
|
|
361
|
+
end try
|
|
362
|
+
end repeat
|
|
363
|
+
end tell
|
|
364
|
+
return "NOT_FOUND"`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* After a send hang, ask Mail whether the message is already in Sent.
|
|
369
|
+
* Returns a hit object or null. Verify failure / timeout is not success.
|
|
370
|
+
*/
|
|
371
|
+
export function recoverIfInSent({ inReplyTo = null, subject = null, forwardTo = null } = {}) {
|
|
372
|
+
const script = forwardTo && inReplyTo
|
|
373
|
+
? buildFindSentForwardScript(inReplyTo, forwardTo)
|
|
374
|
+
: inReplyTo
|
|
375
|
+
? buildFindSentByInReplyToScript(inReplyTo)
|
|
376
|
+
: subject
|
|
377
|
+
? buildFindSentBySubjectScript(subject)
|
|
378
|
+
: null;
|
|
379
|
+
if (!script) return null;
|
|
380
|
+
const result = runAppleScript(script, { timeout: SENT_VERIFY_TIMEOUT_MS, appName: "Mail" });
|
|
381
|
+
if (!result.ok) return null;
|
|
382
|
+
const out = String(result.output || "").trim();
|
|
383
|
+
if (out === SENT_VERIFY_FOUND) return { found: true };
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
138
387
|
function failure(action, summary, result, secrets) {
|
|
139
|
-
if (result.kind === "tcc") {
|
|
140
|
-
return `${action} failed — attempted to ${summary}. ${
|
|
388
|
+
if (result.kind === "tcc" || isHardTccDenial(result && result.error)) {
|
|
389
|
+
return `${action} failed — attempted to ${summary}. ${MAIL_TCC_GUIDANCE}`;
|
|
390
|
+
}
|
|
391
|
+
if (result.kind === "timeout") {
|
|
392
|
+
// Allowed hang / ETIMEDOUT / -1712 is never TCC — including pre-send find/reply/open.
|
|
393
|
+
return `${action} failed — attempted to ${summary}. ${MAIL_SEND_TIMEOUT_GUIDANCE}`;
|
|
141
394
|
}
|
|
142
395
|
if (result.kind === "not_found") {
|
|
143
396
|
return `${action} failed — attempted to ${summary}. The message could not be found in Mail. Pass a message_id from a current mail_search result.`;
|
|
144
397
|
}
|
|
398
|
+
if (result.kind === "app_not_running") {
|
|
399
|
+
return `${action} failed — attempted to ${summary}. ${MAIL_APP_NOT_RUNNING_GUIDANCE}`;
|
|
400
|
+
}
|
|
145
401
|
if (result.kind === "attribution") {
|
|
146
402
|
return `${action} failed — attempted to ${summary}. ${ATTRIBUTION_GUIDANCE}`;
|
|
147
403
|
}
|
|
@@ -151,6 +407,45 @@ function failure(action, summary, result, secrets) {
|
|
|
151
407
|
return writeErrorMessage(action, summary, new Error(result.error || "unknown error"), secrets);
|
|
152
408
|
}
|
|
153
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Finish a compose/reply/forward. A send hang is not labeled TCC; if Mail
|
|
412
|
+
* already delivered, return success. Hard -1743/-10004 stays a deny.
|
|
413
|
+
*/
|
|
414
|
+
function finalizeMailWrite({
|
|
415
|
+
action,
|
|
416
|
+
summary,
|
|
417
|
+
result,
|
|
418
|
+
secrets,
|
|
419
|
+
sendNow,
|
|
420
|
+
inReplyTo = null,
|
|
421
|
+
subject = null,
|
|
422
|
+
forwardTo = null,
|
|
423
|
+
successSummary,
|
|
424
|
+
details
|
|
425
|
+
}) {
|
|
426
|
+
if (result.ok) {
|
|
427
|
+
return { ok: true, message: writeSuccessMessage(action, successSummary, details) };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (sendNow && isMailSendTimeout(result)) {
|
|
431
|
+
const recovered = recoverIfInSent({ inReplyTo, subject, forwardTo });
|
|
432
|
+
if (recovered) {
|
|
433
|
+
return {
|
|
434
|
+
ok: true,
|
|
435
|
+
recovered: true,
|
|
436
|
+
message: writeSuccessMessage(
|
|
437
|
+
action,
|
|
438
|
+
`${successSummary} (verified in Sent after AppleScript hang)`,
|
|
439
|
+
details
|
|
440
|
+
)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
return { ok: false, message: failure(action, summary, result, secrets) };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return { ok: false, message: failure(action, summary, result, secrets) };
|
|
447
|
+
}
|
|
448
|
+
|
|
154
449
|
/**
|
|
155
450
|
* Compose and send (or save as draft) a new email.
|
|
156
451
|
*/
|
|
@@ -204,19 +499,21 @@ export function mailCompose(args = {}, { draft = false } = {}) {
|
|
|
204
499
|
});
|
|
205
500
|
|
|
206
501
|
const result = runAppleScript(script, { timeout: 60000, appName: "Mail" });
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
502
|
+
return finalizeMailWrite({
|
|
503
|
+
action,
|
|
504
|
+
summary,
|
|
505
|
+
result,
|
|
506
|
+
secrets: [body.text, subject.text],
|
|
507
|
+
sendNow: !draft,
|
|
508
|
+
subject: draft ? null : subject.text,
|
|
509
|
+
successSummary: draft ? "draft saved to Drafts" : "sent",
|
|
510
|
+
details: {
|
|
214
511
|
to: to.addresses.join(", "),
|
|
215
512
|
cc: cc.addresses.join(", ") || undefined,
|
|
216
513
|
bcc: bcc.addresses.length ? `${bcc.addresses.length} recipient(s)` : undefined,
|
|
217
514
|
subject: truncate(subject.text, 150)
|
|
218
|
-
}
|
|
219
|
-
};
|
|
515
|
+
}
|
|
516
|
+
});
|
|
220
517
|
}
|
|
221
518
|
|
|
222
519
|
export function buildReplyScript({ messageId, body, replyAll, sendNow }) {
|
|
@@ -274,15 +571,19 @@ export function mailReply(args = {}) {
|
|
|
274
571
|
buildReplyScript({ messageId: resolved.messageId, body: body.text, replyAll, sendNow }),
|
|
275
572
|
{ timeout: 60000, appName: "Mail" }
|
|
276
573
|
);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
574
|
+
return finalizeMailWrite({
|
|
575
|
+
action,
|
|
576
|
+
summary,
|
|
577
|
+
result,
|
|
578
|
+
secrets: [body.text],
|
|
579
|
+
sendNow,
|
|
580
|
+
inReplyTo: sendNow ? resolved.messageId : null,
|
|
581
|
+
successSummary: sendNow ? "reply sent" : "reply saved to Drafts",
|
|
582
|
+
details: {
|
|
282
583
|
message_id: resolved.messageId,
|
|
283
584
|
reply_all: String(replyAll)
|
|
284
|
-
}
|
|
285
|
-
};
|
|
585
|
+
}
|
|
586
|
+
});
|
|
286
587
|
}
|
|
287
588
|
|
|
288
589
|
export function buildForwardScript({ messageId, to, body, sendNow }) {
|
|
@@ -333,15 +634,20 @@ export function mailForward(args = {}) {
|
|
|
333
634
|
buildForwardScript({ messageId: resolved.messageId, to: to.addresses, body: body.text, sendNow }),
|
|
334
635
|
{ timeout: 60000, appName: "Mail" }
|
|
335
636
|
);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
637
|
+
return finalizeMailWrite({
|
|
638
|
+
action,
|
|
639
|
+
summary,
|
|
640
|
+
result,
|
|
641
|
+
secrets: [body.text],
|
|
642
|
+
sendNow,
|
|
643
|
+
inReplyTo: sendNow ? resolved.messageId : null,
|
|
644
|
+
forwardTo: sendNow ? to.addresses : null,
|
|
645
|
+
successSummary: sendNow ? "forwarded" : "forward saved to Drafts",
|
|
646
|
+
details: {
|
|
341
647
|
message_id: resolved.messageId,
|
|
342
648
|
to: to.addresses.join(", ")
|
|
343
|
-
}
|
|
344
|
-
};
|
|
649
|
+
}
|
|
650
|
+
});
|
|
345
651
|
}
|
|
346
652
|
|
|
347
653
|
export function buildMarkScript({ messageId, read }) {
|
package/lib/messagesWrite.js
CHANGED
|
@@ -14,7 +14,7 @@ import fs from "fs";
|
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { safeSqlite3Json } from "./shell.js";
|
|
16
16
|
import { escapeSQL } from "./validators.js";
|
|
17
|
-
import { runAppleScript, asString,
|
|
17
|
+
import { runAppleScript, asString, MESSAGES_TCC_GUIDANCE, MESSAGES_APP_NOT_RUNNING_GUIDANCE, ATTRIBUTION_GUIDANCE } from "./appleScript.js";
|
|
18
18
|
import {
|
|
19
19
|
planWrite,
|
|
20
20
|
normalizeList,
|
|
@@ -95,6 +95,39 @@ export function validateAttachmentPath(value) {
|
|
|
95
95
|
return { filePath: resolved, error: null };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Ship-gate / first-run probe: the Messages Apple Events used before send
|
|
100
|
+
* (enumerate accounts / service type). `messages_send` dry_run never talks
|
|
101
|
+
* to Messages. Nothing is sent.
|
|
102
|
+
*/
|
|
103
|
+
export function buildMessagesAutomationProbeScript() {
|
|
104
|
+
return `tell application "Messages"
|
|
105
|
+
repeat with acc in accounts
|
|
106
|
+
try
|
|
107
|
+
get service type of acc
|
|
108
|
+
end try
|
|
109
|
+
end repeat
|
|
110
|
+
end tell
|
|
111
|
+
return "OK"`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function probeMessagesAutomation() {
|
|
115
|
+
const action = "messages_automation_probe";
|
|
116
|
+
const summary = "enumerate Messages accounts (Automation / Apple Events check; nothing is sent)";
|
|
117
|
+
const result = runAppleScript(buildMessagesAutomationProbeScript(), { timeout: 30000, appName: "Messages" });
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
return { ok: false, message: failure(action, summary, result, []), kind: result.kind };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
kind: null,
|
|
124
|
+
message: writeSuccessMessage(
|
|
125
|
+
action,
|
|
126
|
+
"Messages Automation allowed (enumerated accounts; nothing was sent)"
|
|
127
|
+
)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
98
131
|
function serviceHandler() {
|
|
99
132
|
return `on atmService(kind)
|
|
100
133
|
tell application "Messages"
|
|
@@ -159,8 +192,11 @@ return "OK"`;
|
|
|
159
192
|
}
|
|
160
193
|
|
|
161
194
|
function failure(action, summary, result, secrets) {
|
|
162
|
-
if (result.kind === "tcc") {
|
|
163
|
-
return `${action} failed — attempted to ${summary}. ${
|
|
195
|
+
if (result.kind === "tcc" || result.kind === "timeout") {
|
|
196
|
+
return `${action} failed — attempted to ${summary}. ${MESSAGES_TCC_GUIDANCE}`;
|
|
197
|
+
}
|
|
198
|
+
if (result.kind === "app_not_running") {
|
|
199
|
+
return `${action} failed — attempted to ${summary}. ${MESSAGES_APP_NOT_RUNNING_GUIDANCE}`;
|
|
164
200
|
}
|
|
165
201
|
if (result.kind === "attribution") {
|
|
166
202
|
return `${action} failed — attempted to ${summary}. ${ATTRIBUTION_GUIDANCE}`;
|