@dawnlit/outlook-bridge 1.1.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +152 -183
  2. package/dist/backend.d.ts +151 -0
  3. package/dist/backend.js +3 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bridge.d.ts +56 -0
  6. package/dist/bridge.js +507 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/errors.d.ts +79 -43
  9. package/dist/errors.js +93 -52
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.d.ts +10 -10
  12. package/dist/index.js +25 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/mac/accounts.d.ts +7 -2
  15. package/dist/mac/accounts.js +23 -6
  16. package/dist/mac/accounts.js.map +1 -1
  17. package/dist/mac/attachments.d.ts +5 -25
  18. package/dist/mac/attachments.js +40 -99
  19. package/dist/mac/attachments.js.map +1 -1
  20. package/dist/mac/bounces.d.ts +12 -27
  21. package/dist/mac/bounces.js +96 -117
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +44 -107
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +15 -34
  27. package/dist/mac/drafts.js +113 -186
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +12 -21
  30. package/dist/mac/folders.js +44 -59
  31. package/dist/mac/folders.js.map +1 -1
  32. package/dist/mac/index.d.ts +2 -22
  33. package/dist/mac/index.js +19 -81
  34. package/dist/mac/index.js.map +1 -1
  35. package/dist/mac/profile.d.ts +13 -0
  36. package/dist/mac/profile.js +149 -0
  37. package/dist/mac/profile.js.map +1 -0
  38. package/dist/mac/read.d.ts +21 -37
  39. package/dist/mac/read.js +198 -216
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +51 -38
  42. package/dist/mac/run.js +138 -103
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +90 -91
  45. package/dist/mac/scripts.js +167 -126
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +99 -89
  49. package/dist/mac/send.js.map +1 -1
  50. package/dist/mac/signatures.d.ts +2 -2
  51. package/dist/mac/signatures.js +9 -11
  52. package/dist/mac/signatures.js.map +1 -1
  53. package/dist/mac/templates.d.ts +14 -26
  54. package/dist/mac/templates.js +79 -120
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +80 -46
  57. package/dist/mail.js +179 -73
  58. package/dist/mail.js.map +1 -1
  59. package/dist/runtime.d.ts +76 -43
  60. package/dist/runtime.js +103 -48
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/shared/args.d.ts +48 -0
  63. package/dist/shared/args.js +216 -0
  64. package/dist/shared/args.js.map +1 -0
  65. package/dist/shared/attachmentMatch.d.ts +26 -20
  66. package/dist/shared/attachmentMatch.js +60 -23
  67. package/dist/shared/attachmentMatch.js.map +1 -1
  68. package/dist/shared/bounceReport.d.ts +11 -0
  69. package/dist/shared/bounceReport.js +45 -0
  70. package/dist/shared/bounceReport.js.map +1 -0
  71. package/dist/shared/bounceRules.d.ts +38 -10
  72. package/dist/shared/bounceRules.js +50 -32
  73. package/dist/shared/bounceRules.js.map +1 -1
  74. package/dist/shared/exec.d.ts +21 -0
  75. package/dist/shared/exec.js +47 -0
  76. package/dist/shared/exec.js.map +1 -0
  77. package/dist/shared/json.d.ts +17 -6
  78. package/dist/shared/json.js +35 -24
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +31 -0
  81. package/dist/shared/locate.js +128 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +31 -36
  84. package/dist/shared/replyBody.js +83 -72
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/shared/templateLimits.d.ts +14 -0
  87. package/dist/shared/templateLimits.js +21 -0
  88. package/dist/shared/templateLimits.js.map +1 -0
  89. package/dist/templateBody.d.ts +69 -0
  90. package/dist/{outlookTemplateSections.js → templateBody.js} +80 -73
  91. package/dist/templateBody.js.map +1 -0
  92. package/dist/types.d.ts +381 -168
  93. package/dist/types.js +8 -9
  94. package/dist/types.js.map +1 -1
  95. package/dist/windows/accounts.d.ts +7 -12
  96. package/dist/windows/accounts.js +19 -34
  97. package/dist/windows/accounts.js.map +1 -1
  98. package/dist/windows/attachments.d.ts +9 -24
  99. package/dist/windows/attachments.js +94 -119
  100. package/dist/windows/attachments.js.map +1 -1
  101. package/dist/windows/bounces.d.ts +13 -26
  102. package/dist/windows/bounces.js +168 -200
  103. package/dist/windows/bounces.js.map +1 -1
  104. package/dist/windows/cleanup.d.ts +23 -23
  105. package/dist/windows/cleanup.js +99 -113
  106. package/dist/windows/cleanup.js.map +1 -1
  107. package/dist/windows/drafts.d.ts +16 -35
  108. package/dist/windows/drafts.js +156 -253
  109. package/dist/windows/drafts.js.map +1 -1
  110. package/dist/windows/folders.d.ts +8 -13
  111. package/dist/windows/folders.js +44 -52
  112. package/dist/windows/folders.js.map +1 -1
  113. package/dist/windows/index.d.ts +2 -27
  114. package/dist/windows/index.js +11 -66
  115. package/dist/windows/index.js.map +1 -1
  116. package/dist/windows/read.d.ts +24 -60
  117. package/dist/windows/read.js +265 -306
  118. package/dist/windows/read.js.map +1 -1
  119. package/dist/windows/run.d.ts +38 -40
  120. package/dist/windows/run.js +151 -112
  121. package/dist/windows/run.js.map +1 -1
  122. package/dist/windows/scripts.d.ts +65 -55
  123. package/dist/windows/scripts.js +109 -85
  124. package/dist/windows/scripts.js.map +1 -1
  125. package/dist/windows/send.d.ts +9 -9
  126. package/dist/windows/send.js +76 -88
  127. package/dist/windows/send.js.map +1 -1
  128. package/dist/windows/signatures.d.ts +4 -10
  129. package/dist/windows/signatures.js +30 -37
  130. package/dist/windows/signatures.js.map +1 -1
  131. package/dist/windows/templates.d.ts +12 -18
  132. package/dist/windows/templates.js +117 -150
  133. package/dist/windows/templates.js.map +1 -1
  134. package/package.json +5 -4
  135. package/dist/OutlookService.d.ts +0 -43
  136. package/dist/OutlookService.js +0 -137
  137. package/dist/OutlookService.js.map +0 -1
  138. package/dist/outlookTemplateSections.d.ts +0 -62
  139. package/dist/outlookTemplateSections.js.map +0 -1
  140. package/dist/shared/capabilities.d.ts +0 -3
  141. package/dist/shared/capabilities.js +0 -9
  142. package/dist/shared/capabilities.js.map +0 -1
package/dist/mail.js CHANGED
@@ -1,103 +1,209 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PROTECTED_MAIL_REASON = exports.WELL_KNOWN_FOLDERS = void 0;
4
- exports.clamp = clamp;
3
+ exports.REPLY_PREFIX = exports.REPLY_PREFIX_SOURCE = exports.NON_INCOMING_ROOTS = exports.PROTECTED_MAIL_REASON = exports.WELL_KNOWN_FOLDERS = exports.FolderId = void 0;
4
+ exports.deleteStatus = deleteStatus;
5
+ exports.deleteMailResult = deleteMailResult;
5
6
  exports.isOutgoingRoot = isOutgoingRoot;
7
+ exports.hasReplyPrefix = hasReplyPrefix;
8
+ exports.threadSubject = threadSubject;
9
+ exports.subjectGlobSource = subjectGlobSource;
10
+ exports.folderLeafName = folderLeafName;
6
11
  exports.mailFolderRef = mailFolderRef;
12
+ exports.folderOption = folderOption;
7
13
  exports.splitQuotedOriginal = splitQuotedOriginal;
14
+ // Platform-neutral mail helpers: folder strings, reply prefixes, splitting a
15
+ // reply from the thread it quotes, and what deleting mail reports. Pure
16
+ // functions, usable without Outlook, and shared by both platforms so neither can
17
+ // come to read these differently.
18
+ const errors_1 = require("./errors");
8
19
  /**
9
- * Clamp a caller's count to the range an operation actually accepts.
10
- *
11
- * These bounds ARE the argument contract — how far back a scan reaches, how many
12
- * rows come back, how deep a folder walk goes — so they are stated here rather
13
- * than once per platform, which is how the two came to disagree about what a
14
- * `daysBack` of 0 means.
20
+ * Outlook's olDefaultFolders ids for the folders this package addresses by
21
+ * role rather than by name. The ids are Outlook's own, and a folder's NAME is
22
+ * localized, so a role is always resolved through its id.
23
+ */
24
+ exports.FolderId = Object.freeze({
25
+ DeletedItems: 3,
26
+ Outbox: 4,
27
+ SentMail: 5,
28
+ Inbox: 6,
29
+ Drafts: 16,
30
+ Junk: 23,
31
+ });
32
+ /**
33
+ * The folders addressable by name rather than by walking from the Inbox, with
34
+ * their olDefaultFolders id. A well-known name wins over a user folder of the
35
+ * same name; reach the latter by qualifying it (`Inbox\Drafts`).
36
+ */
37
+ exports.WELL_KNOWN_FOLDERS = Object.freeze({
38
+ 'inbox': exports.FolderId.Inbox,
39
+ 'sent items': exports.FolderId.SentMail,
40
+ 'sent': exports.FolderId.SentMail,
41
+ 'sent mail': exports.FolderId.SentMail,
42
+ 'drafts': exports.FolderId.Drafts,
43
+ 'deleted items': exports.FolderId.DeletedItems,
44
+ 'deleted': exports.FolderId.DeletedItems,
45
+ 'trash': exports.FolderId.DeletedItems,
46
+ 'junk email': exports.FolderId.Junk,
47
+ 'junk': exports.FolderId.Junk,
48
+ 'outbox': exports.FolderId.Outbox,
49
+ });
50
+ /** The Inbox root, as a folder reference. */
51
+ const INBOX_REF = Object.freeze({
52
+ rootId: exports.FolderId.Inbox,
53
+ rootLabel: 'Inbox',
54
+ segments: [],
55
+ });
56
+ /**
57
+ * Why `deleteOutlookEmails` refuses an item, worded once for both platforms —
58
+ * it reaches the caller verbatim as a per-item `reason`.
15
59
  */
16
- function clamp(value, min, max) {
17
- return Math.max(min, Math.min(max, Math.floor(value)));
60
+ exports.PROTECTED_MAIL_REASON = 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';
61
+ const DELETE_STATUSES = ['deleted', 'would-delete', 'refused', 'failed'];
62
+ /** The status a delete script reported for one item; anything it shouldn't have said is a failure. */
63
+ function deleteStatus(reported) {
64
+ return DELETE_STATUSES.find(status => status === reported) ?? 'failed';
65
+ }
66
+ /** A deleteOutlookEmails result: every outcome, and how many ended each way. */
67
+ function deleteMailResult(dryRun, items) {
68
+ const tally = (status) => items.filter(item => item.status === status).length;
69
+ return { dryRun, deleted: tally('deleted'), refused: tally('refused'), failed: tally('failed'), items };
18
70
  }
19
71
  /**
20
- * The Outlook folders addressable by name rather than by walking from the Inbox,
21
- * with their olDefaultFolders id. Sent Items is the one that earns this: "has this
22
- * already gone out?" is otherwise unanswerable, and no amount of Inbox scanning
23
- * substitutes for it.
72
+ * Roots whose items are outgoing mail, which carries a sent time where incoming
73
+ * mail carries a received one — so a date filter has to know which it's on.
74
+ */
75
+ function isOutgoingRoot(rootId) {
76
+ return rootId === exports.FolderId.SentMail || rootId === exports.FolderId.Outbox || rootId === exports.FolderId.Drafts;
77
+ }
78
+ /**
79
+ * Roots an Inbox walk must never descend into.
24
80
  *
25
- * A well-known name always wins over a user folder of the same name — reach the
26
- * latter by qualifying it (`Inbox\Drafts`).
81
+ * On an Exchange profile these sit beside the Inbox and a walk from it can't
82
+ * reach them anyway. On an IMAP profile Outlook nests them UNDER the Inbox, so
83
+ * the same walk would hand back mail already sent, drafted or thrown away as if
84
+ * it had just arrived. They are matched by id, never by name: the names are
85
+ * localized.
27
86
  */
28
- exports.WELL_KNOWN_FOLDERS = {
29
- inbox: 6,
30
- 'sent items': 5,
31
- sent: 5,
32
- 'sent mail': 5,
33
- drafts: 16,
34
- 'deleted items': 3,
35
- deleted: 3,
36
- trash: 3,
37
- 'junk email': 23,
38
- junk: 23,
39
- outbox: 4,
40
- };
87
+ exports.NON_INCOMING_ROOTS = Object.freeze([
88
+ exports.FolderId.DeletedItems,
89
+ exports.FolderId.Outbox,
90
+ exports.FolderId.SentMail,
91
+ exports.FolderId.Drafts,
92
+ exports.FolderId.Junk,
93
+ ]);
41
94
  /**
42
- * Why `deleteOutlookEmails` refuses a message, worded once for both platforms.
95
+ * Subject prefixes that mark a reply or a forward.
96
+ *
97
+ * Kept as a pattern SOURCE because it runs in two engines: macOS tests it as a
98
+ * JavaScript RegExp, Windows inside PowerShell's .NET one, and both read every
99
+ * construct used here the same way. Non-ASCII is written as \uXXXX so the
100
+ * generated PowerShell stays plain ASCII.
43
101
  *
44
- * It reaches the caller verbatim in a per-email `reason`, so the two generated
45
- * scripts must not word it independently — the same reasoning that put the
46
- * attachment "not found" sentence in `shared/attachmentMatch.ts`. It also names
47
- * the option a consumer of THIS package actually passes: the text used to say
48
- * `allow_protected`, which is the tool-layer spelling and not a name this
49
- * package exposes.
102
+ * Not only English: mail threaded in Chinese, Japanese or Korean replies under
103
+ * 回复:/返信:/답장: and forwards under 转发:/転送:/전달:, and the European
104
+ * clients use AW:, WG:, SV:, TR:, RV:, ENC: and friends. The colon may be the
105
+ * full-width one a CJK input method types, and Outlook's reply counter (RE[2]:)
106
+ * can sit before it.
50
107
  */
51
- exports.PROTECTED_MAIL_REASON = 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';
108
+ const REPLY_PREFIXES = [
109
+ 're', 'fwd?', 'aw', 'wg', 'sv', 'vs', 'vb', 'tr', 'res', 'rv', 'enc', 'odp',
110
+ '\\u56DE\\u590D', '\\u56DE\\u8986', '\\u7B54\\u590D', '\\u7B54\\u8986',
111
+ '\\u8F6C\\u53D1', '\\u8F49\\u767C', '\\u8FD4\\u4FE1', '\\u8EE2\\u9001',
112
+ '\\uB2F5\\uC7A5', '\\uC804\\uB2EC',
113
+ ];
114
+ /** The reply/forward prefixes as one case-insensitive pattern source. */
115
+ exports.REPLY_PREFIX_SOURCE = '^\\s*(?:' + REPLY_PREFIXES.join('|') + ')\\s*(?:\\[\\d+\\])?\\s*[:\\uFF1A]';
116
+ exports.REPLY_PREFIX = new RegExp(exports.REPLY_PREFIX_SOURCE, 'i');
52
117
  /**
53
- * Roots whose items are OUTGOING mail.
118
+ * Whether a subject opens on a reply or forward prefix.
54
119
  *
55
- * Outgoing mail carries a sent timestamp where received mail carries a received
56
- * one, and both platforms have to pick the same folders out on that basis —
57
- * which is why it lives beside `WELL_KNOWN_FOLDERS` rather than as raw
58
- * `olDefaultFolders` ids compared inline in each reader.
120
+ * This reads what the subject claims, not how the email was made: a forward
121
+ * carries a prefix too, and a client can leave one off a real reply.
59
122
  */
60
- function isOutgoingRoot(rootId) {
61
- return rootId === exports.WELL_KNOWN_FOLDERS['sent items']
62
- || rootId === exports.WELL_KNOWN_FOLDERS.outbox
63
- || rootId === exports.WELL_KNOWN_FOLDERS.drafts;
123
+ function hasReplyPrefix(subject) {
124
+ return exports.REPLY_PREFIX.test(subject);
64
125
  }
65
126
  /**
66
- * Resolve a caller's folder string to a well-known root plus the segments below it.
67
- *
68
- * Accepts every shape a user has to hand: a bare name ("Invoices"), a relative
69
- * path ("Invoices\\Paid"), a well-known folder ("Sent Items"), a path under one
70
- * ("Deleted Items\\2026"), and the full FolderPath listInboxFolders prints
71
- * ("\\\\team@x.com\\Inbox\\Invoices").
127
+ * The subject every message in a thread shares: every reply and forward prefix
128
+ * stripped, however many clients stacked them (`RE: FW: RE[2]: Rate request`
129
+ * is `Rate request`), and whitespace collapsed. Case is kept, so lower-case
130
+ * both sides to compare two threads.
131
+ */
132
+ function threadSubject(subject) {
133
+ let rest = subject;
134
+ // Each pass removes at least the prefix's colon, so this always ends.
135
+ while (exports.REPLY_PREFIX.test(rest))
136
+ rest = rest.replace(exports.REPLY_PREFIX, '');
137
+ return rest.replace(/\s+/g, ' ').trim();
138
+ }
139
+ /**
140
+ * A subject glob as an anchored regex source: `*` matches any run, `?` one
141
+ * character, everything else itself. Written with constructs JavaScript and
142
+ * .NET read identically, so both platforms apply exactly the same test.
143
+ */
144
+ function subjectGlobSource(glob) {
145
+ let source = '';
146
+ for (const ch of glob) {
147
+ if (ch === '*')
148
+ source += '.*';
149
+ else if (ch === '?')
150
+ source += '.';
151
+ else
152
+ source += ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
153
+ }
154
+ return `^${source}$`;
155
+ }
156
+ /** The leaf name of a folder path or bare name. */
157
+ function folderLeafName(entry) {
158
+ return entry.trim().split(/[\\/]/).filter(Boolean).pop() ?? '';
159
+ }
160
+ /** A full Outlook folder path starts with the mailbox: `\\mailbox@example.com\…`. */
161
+ const MAILBOX_PREFIX = /^\\\\[^\\/]+[\\/]/;
162
+ function folderSegments(folder) {
163
+ return folder.trim().replace(MAILBOX_PREFIX, '').split(/[\\/]+/).map(s => s.trim()).filter(Boolean);
164
+ }
165
+ /**
166
+ * Resolve a folder string to a well-known root plus the segments below it.
72
167
  *
73
- * An unrecognized first segment means Inbox-relative, which is what keeps a bare
74
- * subfolder name working unchanged.
168
+ * Accepts every shape a person has to hand: a bare name ('Invoices'), a
169
+ * relative path ('Invoices\Paid'), a well-known folder ('Sent Items'), a path
170
+ * under one ('Deleted Items\2026'), and the full path listInboxFolders prints
171
+ * ('\\team@example.com\Inbox\Invoices'). Anything not starting at a well-known
172
+ * folder is taken as relative to the Inbox.
75
173
  */
76
174
  function mailFolderRef(folder) {
77
- const rest = folder.trim().replace(/^\\\\[^\\/]+[\\/]/, '');
78
- const segments = rest.split(/[\\/]+/).map(s => s.trim()).filter(Boolean);
79
- if (segments.length > 0) {
80
- const head = segments[0].toLowerCase();
81
- if (Object.prototype.hasOwnProperty.call(exports.WELL_KNOWN_FOLDERS, head)) {
82
- return { rootId: exports.WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1) };
83
- }
175
+ const segments = folderSegments(folder);
176
+ const head = segments[0]?.toLowerCase();
177
+ if (head !== undefined && Object.prototype.hasOwnProperty.call(exports.WELL_KNOWN_FOLDERS, head)) {
178
+ return { rootId: exports.WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1) };
179
+ }
180
+ return { rootId: exports.FolderId.Inbox, rootLabel: 'Inbox', segments };
181
+ }
182
+ /**
183
+ * A caller's folder option as a reference: none given means the Inbox root,
184
+ * and a string that names no folder at all (only separators) is refused rather
185
+ * than silently read as the Inbox root.
186
+ */
187
+ function folderOption(folder) {
188
+ if (folder === undefined || folder.trim() === '')
189
+ return { ref: INBOX_REF, label: 'Inbox' };
190
+ if (folderSegments(folder).length === 0) {
191
+ throw new errors_1.InvalidRequestError(`Folder '${folder}' does not name a folder.`);
84
192
  }
85
- return { rootId: 6, rootLabel: 'Inbox', segments };
193
+ return { ref: mailFolderRef(folder), label: folder.trim() };
86
194
  }
87
195
  /**
88
196
  * Split a plain-text reply into the sender's own text and the quoted thread
89
197
  * below it.
90
198
  *
91
- * This matters more than it looks: the quoted original of a reply carries the
92
- * text of the message it answers. Handing a caller one blob invites a figure
93
- * from the outgoing message to be read back as the sender's answer, which is a
94
- * worse failure than the truncation this exists to fix.
199
+ * This matters more than it looks: the quoted original carries the text of the
200
+ * message being answered, and handing a reader one blob invites a figure from
201
+ * that message to be read back as the sender's own.
95
202
  *
96
- * Scans for the earliest of the separators Outlook and the common webmail
97
- * clients emit. If the split would leave no new text at all — a bottom-posted
98
- * reply, or a body that opens on a quote — it is abandoned and everything is
99
- * returned as `body`, since dropping the sender's actual words is the one
100
- * outcome worth avoiding.
203
+ * Splits at the earliest separator Outlook or the common webmail clients emit.
204
+ * If that would leave no new text at all — a bottom-posted reply, or a body
205
+ * that opens on a quote — the split is abandoned and everything is `body`,
206
+ * since losing the sender's actual words is the one outcome worth avoiding.
101
207
  */
102
208
  function splitQuotedOriginal(text) {
103
209
  const lines = text.split(/\r?\n/);
@@ -107,10 +213,10 @@ function splitQuotedOriginal(text) {
107
213
  let separator = '';
108
214
  if (/^-{2,}\s*original message\s*-{2,}$/i.test(line))
109
215
  separator = '-----Original Message-----';
110
- // Outlook's HTML thread rule, which sits directly above the From:/Sent: block.
216
+ // Outlook's HTML thread rule, directly above the From:/Sent: block.
111
217
  else if (/^_{10,}$/.test(line))
112
218
  separator = 'Outlook thread rule';
113
- // "On Tue, Jul 21, 2026 at 9:14 AM John Doe <j@x.com> wrote:"
219
+ // "On Tue, Jul 21, 2026 at 9:14 AM Jo Doe <jo@example.com> wrote:"
114
220
  else if (/^on\b.{5,300}\bwrote:$/i.test(line))
115
221
  separator = 'On … wrote:';
116
222
  else if (/^>/.test(lines[i]))
package/dist/mail.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"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","sourcesContent":["// Platform-neutral mail helpers: folder strings, reply prefixes, splitting a\n// reply from the thread it quotes, and what deleting mail reports. Pure\n// functions, usable without Outlook, and shared by both platforms so neither can\n// come to read these differently.\nimport { InvalidRequestError } from './errors';\nimport type { DeleteMailOutcome, DeleteMailResult, MailFolderRef } from './types';\n\n/**\n * Outlook's olDefaultFolders ids for the folders this package addresses by\n * role rather than by name. The ids are Outlook's own, and a folder's NAME is\n * localized, so a role is always resolved through its id.\n */\nexport const FolderId = Object.freeze({\n DeletedItems: 3,\n Outbox: 4,\n SentMail: 5,\n Inbox: 6,\n Drafts: 16,\n Junk: 23,\n});\n\n/**\n * The folders addressable by name rather than by walking from the Inbox, with\n * their olDefaultFolders id. A well-known name wins over a user folder of the\n * same name; reach the latter by qualifying it (`Inbox\\Drafts`).\n */\nexport const WELL_KNOWN_FOLDERS: Readonly<Record<string, number>> = Object.freeze({\n 'inbox': FolderId.Inbox,\n 'sent items': FolderId.SentMail,\n 'sent': FolderId.SentMail,\n 'sent mail': FolderId.SentMail,\n 'drafts': FolderId.Drafts,\n 'deleted items': FolderId.DeletedItems,\n 'deleted': FolderId.DeletedItems,\n 'trash': FolderId.DeletedItems,\n 'junk email': FolderId.Junk,\n 'junk': FolderId.Junk,\n 'outbox': FolderId.Outbox,\n});\n\n/** The Inbox root, as a folder reference. */\nconst INBOX_REF: Readonly<MailFolderRef> = Object.freeze({\n rootId: FolderId.Inbox,\n rootLabel: 'Inbox',\n segments: [],\n});\n\n/**\n * Why `deleteOutlookEmails` refuses an item, worded once for both platforms —\n * it reaches the caller verbatim as a per-item `reason`.\n */\nexport const PROTECTED_MAIL_REASON =\n 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';\n\nconst DELETE_STATUSES: readonly DeleteMailOutcome['status'][] = ['deleted', 'would-delete', 'refused', 'failed'];\n\n/** The status a delete script reported for one item; anything it shouldn't have said is a failure. */\nexport function deleteStatus(reported: string): DeleteMailOutcome['status'] {\n return DELETE_STATUSES.find(status => status === reported) ?? 'failed';\n}\n\n/** A deleteOutlookEmails result: every outcome, and how many ended each way. */\nexport function deleteMailResult(dryRun: boolean, items: DeleteMailOutcome[]): DeleteMailResult {\n const tally = (status: DeleteMailOutcome['status']) => items.filter(item => item.status === status).length;\n return { dryRun, deleted: tally('deleted'), refused: tally('refused'), failed: tally('failed'), items };\n}\n\n/**\n * Roots whose items are outgoing mail, which carries a sent time where incoming\n * mail carries a received one — so a date filter has to know which it's on.\n */\nexport function isOutgoingRoot(rootId: number): boolean {\n return rootId === FolderId.SentMail || rootId === FolderId.Outbox || rootId === FolderId.Drafts;\n}\n\n/**\n * Roots an Inbox walk must never descend into.\n *\n * On an Exchange profile these sit beside the Inbox and a walk from it can't\n * reach them anyway. On an IMAP profile Outlook nests them UNDER the Inbox, so\n * the same walk would hand back mail already sent, drafted or thrown away as if\n * it had just arrived. They are matched by id, never by name: the names are\n * localized.\n */\nexport const NON_INCOMING_ROOTS: readonly number[] = Object.freeze([\n FolderId.DeletedItems,\n FolderId.Outbox,\n FolderId.SentMail,\n FolderId.Drafts,\n FolderId.Junk,\n]);\n\n/**\n * Subject prefixes that mark a reply or a forward.\n *\n * Kept as a pattern SOURCE because it runs in two engines: macOS tests it as a\n * JavaScript RegExp, Windows inside PowerShell's .NET one, and both read every\n * construct used here the same way. Non-ASCII is written as \\uXXXX so the\n * generated PowerShell stays plain ASCII.\n *\n * Not only English: mail threaded in Chinese, Japanese or Korean replies under\n * 回复:/返信:/답장: and forwards under 转发:/転送:/전달:, and the European\n * clients use AW:, WG:, SV:, TR:, RV:, ENC: and friends. The colon may be the\n * full-width one a CJK input method types, and Outlook's reply counter (RE[2]:)\n * can sit before it.\n */\nconst REPLY_PREFIXES: readonly string[] = [\n 're', 'fwd?', 'aw', 'wg', 'sv', 'vs', 'vb', 'tr', 'res', 'rv', 'enc', 'odp',\n '\\\\u56DE\\\\u590D', '\\\\u56DE\\\\u8986', '\\\\u7B54\\\\u590D', '\\\\u7B54\\\\u8986',\n '\\\\u8F6C\\\\u53D1', '\\\\u8F49\\\\u767C', '\\\\u8FD4\\\\u4FE1', '\\\\u8EE2\\\\u9001',\n '\\\\uB2F5\\\\uC7A5', '\\\\uC804\\\\uB2EC',\n];\n\n/** The reply/forward prefixes as one case-insensitive pattern source. */\nexport const REPLY_PREFIX_SOURCE =\n '^\\\\s*(?:' + REPLY_PREFIXES.join('|') + ')\\\\s*(?:\\\\[\\\\d+\\\\])?\\\\s*[:\\\\uFF1A]';\nexport const REPLY_PREFIX = new RegExp(REPLY_PREFIX_SOURCE, 'i');\n\n/**\n * Whether a subject opens on a reply or forward prefix.\n *\n * This reads what the subject claims, not how the email was made: a forward\n * carries a prefix too, and a client can leave one off a real reply.\n */\nexport function hasReplyPrefix(subject: string): boolean {\n return REPLY_PREFIX.test(subject);\n}\n\n/**\n * The subject every message in a thread shares: every reply and forward prefix\n * stripped, however many clients stacked them (`RE: FW: RE[2]: Rate request`\n * is `Rate request`), and whitespace collapsed. Case is kept, so lower-case\n * both sides to compare two threads.\n */\nexport function threadSubject(subject: string): string {\n let rest = subject;\n // Each pass removes at least the prefix's colon, so this always ends.\n while (REPLY_PREFIX.test(rest)) rest = rest.replace(REPLY_PREFIX, '');\n return rest.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * A subject glob as an anchored regex source: `*` matches any run, `?` one\n * character, everything else itself. Written with constructs JavaScript and\n * .NET read identically, so both platforms apply exactly the same test.\n */\nexport function subjectGlobSource(glob: string): string {\n let source = '';\n for (const ch of glob) {\n if (ch === '*') source += '.*';\n else if (ch === '?') source += '.';\n else source += ch.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n return `^${source}$`;\n}\n\n/** The leaf name of a folder path or bare name. */\nexport function folderLeafName(entry: string): string {\n return entry.trim().split(/[\\\\/]/).filter(Boolean).pop() ?? '';\n}\n\n/** A full Outlook folder path starts with the mailbox: `\\\\mailbox@example.com\\…`. */\nconst MAILBOX_PREFIX = /^\\\\\\\\[^\\\\/]+[\\\\/]/;\n\nfunction folderSegments(folder: string): string[] {\n return folder.trim().replace(MAILBOX_PREFIX, '').split(/[\\\\/]+/).map(s => s.trim()).filter(Boolean);\n}\n\n/**\n * Resolve a folder string to a well-known root plus the segments below it.\n *\n * Accepts every shape a person has to hand: a bare name ('Invoices'), a\n * relative path ('Invoices\\Paid'), a well-known folder ('Sent Items'), a path\n * under one ('Deleted Items\\2026'), and the full path listInboxFolders prints\n * ('\\\\team@example.com\\Inbox\\Invoices'). Anything not starting at a well-known\n * folder is taken as relative to the Inbox.\n */\nexport function mailFolderRef(folder: string): MailFolderRef {\n const segments = folderSegments(folder);\n const head = segments[0]?.toLowerCase();\n if (head !== undefined && Object.prototype.hasOwnProperty.call(WELL_KNOWN_FOLDERS, head)) {\n return { rootId: WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1) };\n }\n return { rootId: FolderId.Inbox, rootLabel: 'Inbox', segments };\n}\n\n/**\n * A caller's folder option as a reference: none given means the Inbox root,\n * and a string that names no folder at all (only separators) is refused rather\n * than silently read as the Inbox root.\n */\nexport function folderOption(folder: string | undefined): { ref: MailFolderRef; label: string } {\n if (folder === undefined || folder.trim() === '') return { ref: INBOX_REF, label: 'Inbox' };\n if (folderSegments(folder).length === 0) {\n throw new InvalidRequestError(`Folder '${folder}' does not name a folder.`);\n }\n return { ref: mailFolderRef(folder), label: folder.trim() };\n}\n\n/**\n * Split a plain-text reply into the sender's own text and the quoted thread\n * below it.\n *\n * This matters more than it looks: the quoted original carries the text of the\n * message being answered, and handing a reader one blob invites a figure from\n * that message to be read back as the sender's own.\n *\n * Splits at the earliest separator Outlook or the common webmail clients emit.\n * If that would leave no new text at all — a bottom-posted reply, or a body\n * that opens on a quote — the split is abandoned and everything is `body`,\n * since losing the sender's actual words is the one outcome worth avoiding.\n */\nexport function splitQuotedOriginal(text: string): { body: string; quoted: string; separator: string } {\n const lines = text.split(/\\r?\\n/);\n const whole = { body: text, quoted: '', separator: '' };\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n let separator = '';\n if (/^-{2,}\\s*original message\\s*-{2,}$/i.test(line)) separator = '-----Original Message-----';\n // Outlook's HTML thread rule, directly above the From:/Sent: block.\n else if (/^_{10,}$/.test(line)) separator = 'Outlook thread rule';\n // \"On Tue, Jul 21, 2026 at 9:14 AM Jo Doe <jo@example.com> wrote:\"\n else if (/^on\\b.{5,300}\\bwrote:$/i.test(line)) separator = 'On … wrote:';\n else if (/^>/.test(lines[i])) separator = '> quoted lines';\n // A bare header block: \"From: …\" followed by another header line.\n else if (/^from:\\s*\\S/i.test(line) && /^(sent|date|to|subject|cc):/i.test((lines[i + 1] || '').trim())) {\n separator = 'From:/Sent: header block';\n }\n if (!separator) continue;\n const body = lines.slice(0, i).join('\\n').trimEnd();\n if (!body.trim()) return whole;\n return { body, quoted: lines.slice(i).join('\\n').trim(), separator };\n }\n return whole;\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -1,8 +1,8 @@
1
+ import { type ScriptRunner } from './errors';
1
2
  /** One completed script run, handed to the `debug` hook. */
2
3
  export interface BridgeDebugEvent {
3
- /** Which interpreter ran it. */
4
- runner: 'powershell' | 'osascript';
5
- /** The generated script, verbatim — the thing you actually want when a run fails. */
4
+ runner: ScriptRunner;
5
+ /** The script exactly as it ran. */
6
6
  script: string;
7
7
  durationMs: number;
8
8
  /** Present only when the run failed. */
@@ -10,67 +10,100 @@ export interface BridgeDebugEvent {
10
10
  }
11
11
  export interface BridgeOptions {
12
12
  /**
13
- * Default ceiling (ms) on a single script run; 0 disables it. Calls that
14
- * already carry a specific budget (a full-mailbox scan, a purge) keep their
15
- * own and ignore this.
13
+ * Ceiling on every script run, in milliseconds, replacing each operation's
14
+ * own budget; 0 disables timeouts. Leave unset (or pass null to go back) to
15
+ * let each operation use a budget sized for its work — a minute for a
16
+ * lookup, up to ten for purging Deleted Items. `editEmailTemplate` waits on
17
+ * a person and is never timed; cancel it with `signal`.
16
18
  */
17
- timeoutMs?: number;
19
+ timeoutMs?: number | null;
18
20
  /**
19
- * Max bytes a script may write to stdout before the run is killed. A read
20
- * that returns message bodies is what pushes against this — raise it before
21
- * scanning a mailbox that returns thousands of matches.
21
+ * Max bytes a script may print before the run is killed with
22
+ * OUTPUT_TOO_LARGE. Searches that return whole bodies push against this.
23
+ * Default 8 MiB.
22
24
  */
23
25
  maxBufferBytes?: number;
24
- /** Directory for generated scripts and for saved attachments when the caller
25
- * names no destination. Defaults to the OS temp directory. */
26
+ /** Directory for generated scripts, and for saved attachments when a call
27
+ * names no destination. Default: the OS temp directory. */
26
28
  tempDir?: string;
27
29
  /**
28
- * Cancels in-flight runs: the interpreter is killed and the call rejects with
29
- * an `AbortedError`. A timeout only caps a run's length — this is how a UI
30
- * with a Cancel button, or a shutting-down process, calls one off early.
31
- *
32
- * Most useful scoped to the calls you want to cancel rather than set process
33
- * wide: `bridge.withOptions({ signal }).searchInboxByFilter(...)`.
30
+ * Cancels in-flight runs: the interpreter is killed and the call rejects
31
+ * with ABORTED. Most useful scoped to the calls it should cancel:
32
+ * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.
34
33
  */
35
34
  signal?: AbortSignal;
36
- /** `true` logs every generated script to stderr; a function receives each run
37
- * instead. Defaults to on when OUTLOOK_BRIDGE_DEBUG is set to a non-empty,
38
- * non-'0' value. */
35
+ /** `true` logs every script to stderr; a function receives each run
36
+ * instead. Default: on when OUTLOOK_BRIDGE_DEBUG is set to anything but '' or '0'. */
39
37
  debug?: boolean | ((event: BridgeDebugEvent) => void);
40
38
  }
41
- export type ResolvedConfig = Required<Pick<BridgeOptions, 'timeoutMs' | 'maxBufferBytes' | 'tempDir'>> & Pick<BridgeOptions, 'debug' | 'signal'>;
42
- /** Merge caller options over a base, ignoring the keys they left out. */
39
+ /** The settings a call runs with, every default applied. */
40
+ export interface ResolvedConfig {
41
+ /** undefined = each operation's own budget. */
42
+ readonly timeoutMs: number | undefined;
43
+ readonly maxBufferBytes: number;
44
+ readonly tempDir: string;
45
+ readonly signal: AbortSignal | undefined;
46
+ readonly debug: boolean | ((event: BridgeDebugEvent) => void);
47
+ }
48
+ /** Caller options merged over a base; keys left out keep the base's value. */
43
49
  export declare function mergeOptions(base: ResolvedConfig, options?: BridgeOptions): ResolvedConfig;
44
50
  /**
45
- * Override the process-wide defaults. Merges, so naming one option leaves the
46
- * rest alone. Call before the first automation call.
51
+ * Change the process-wide settings. Merges, so naming one option leaves the
52
+ * rest alone.
47
53
  *
48
- * Affects every caller in the process. Prefer `createOutlookBridge(options)` in
49
- * anything that shares a process with code you don't own.
54
+ * Affects every caller in the process that uses the exported functions. Prefer
55
+ * `createOutlookBridge(options)` in a process shared with code you don't own.
50
56
  */
51
57
  export declare function configure(options: BridgeOptions): void;
52
- /** The settings in force for the current call — the enclosing scope's, else global. */
53
- export declare function getConfig(): Readonly<ResolvedConfig>;
54
- /** The process-wide defaults, ignoring any active scope. */
55
- export declare function getGlobalConfig(): Readonly<ResolvedConfig>;
58
+ /** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */
59
+ export declare function getConfig(): ResolvedConfig;
60
+ /** The process-wide settings, ignoring any bridge scope. */
61
+ export declare function getGlobalConfig(): ResolvedConfig;
56
62
  /** Run `fn` with `config` in force for it and everything it awaits. */
57
63
  export declare function withConfig<T>(config: ResolvedConfig, fn: () => T): T;
58
- /** Report one finished script run to the configured debug hook. */
64
+ /**
65
+ * How long each kind of run may take when no `timeoutMs` is set.
66
+ *
67
+ * Generous on purpose: a budget is a guard against a hung interpreter, not a
68
+ * performance target, and the first call against an Outlook that isn't running
69
+ * yet spends much of its budget starting it.
70
+ */
71
+ export declare const RUN_BUDGETS: {
72
+ /** One lookup: accounts, signatures, opening or reading a single email. */
73
+ readonly quick: 60000;
74
+ /** Composing, filing, saving attachments, or reading one folder. */
75
+ readonly standard: 120000;
76
+ /** Walking a mailbox tree, or acting on a batch of items. */
77
+ readonly scan: 300000;
78
+ /** Emptying Deleted Items. */
79
+ readonly purge: 600000;
80
+ };
81
+ /** A budget name, or 'interactive' for a run that waits on a person and is never timed. */
82
+ export type RunBudget = keyof typeof RUN_BUDGETS | 'interactive';
83
+ /** The timeout one run gets: the configured ceiling if there is one, else its budget. */
84
+ export declare function timeoutFor(budget: RunBudget): number;
85
+ /** Report one finished run to the configured debug hook. */
59
86
  export declare function reportRun(event: BridgeDebugEvent): void;
60
87
  /**
61
- * Path for a scratch file in the configured temp directory. The random suffix
62
- * matters: two concurrent calls landing in the same millisecond would otherwise
63
- * pick the same name and overwrite each other's script mid-run.
88
+ * A scratch-file path in the configured temp directory. The random part
89
+ * matters: two concurrent calls in the same millisecond would otherwise pick the
90
+ * same name and overwrite each other's script mid-run.
64
91
  */
65
92
  export declare function tempFile(kind: string, extension: string): string;
93
+ /** Delete a scratch file, ignoring one that is already gone. */
94
+ export declare function removeTempFile(file: string): void;
95
+ /** A destination for saved attachments, and how to undo creating it. */
96
+ export interface AttachmentDestination {
97
+ dir: string;
98
+ /** Remove the directory if this call created it and left it empty. */
99
+ discardIfUnused(): void;
100
+ }
66
101
  /**
67
- * A fresh directory for one call's saved attachments.
102
+ * Where one call saves its attachments: the caller's directory (created if
103
+ * absent), or a fresh private directory of its own.
68
104
  *
69
- * Deliberately per-call rather than one shared folder: attachments are saved
70
- * under the name the sender gave them, so two emails that both carry
71
- * "invoice.pdf" would otherwise silently overwrite each other — and so would two
72
- * unrelated programs using this package on the same machine.
105
+ * Private by default because attachments keep the names senders gave them, so
106
+ * one shared folder means two emails carrying 'invoice.pdf' overwrite each
107
+ * other — and so do two programs using this package on one machine.
73
108
  */
74
- export declare function makeAttachmentDir(): string;
75
- /** Resolve a caller-supplied destination, creating it when absent. */
76
- export declare function resolveDestDir(destDir?: string): string;
109
+ export declare function attachmentDestination(destDir?: string): AttachmentDestination;