@dawnlit/outlook-bridge 2.0.0 → 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 (88) hide show
  1. package/README.md +6 -4
  2. package/dist/backend.d.ts +2 -2
  3. package/dist/backend.js.map +1 -1
  4. package/dist/bridge.js +3 -3
  5. package/dist/bridge.js.map +1 -1
  6. package/dist/errors.d.ts +0 -2
  7. package/dist/errors.js +1 -6
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/mac/accounts.js.map +1 -1
  12. package/dist/mac/attachments.js +1 -1
  13. package/dist/mac/attachments.js.map +1 -1
  14. package/dist/mac/bounces.js +8 -12
  15. package/dist/mac/bounces.js.map +1 -1
  16. package/dist/mac/cleanup.js +4 -41
  17. package/dist/mac/cleanup.js.map +1 -1
  18. package/dist/mac/drafts.d.ts +3 -1
  19. package/dist/mac/drafts.js +34 -33
  20. package/dist/mac/drafts.js.map +1 -1
  21. package/dist/mac/folders.d.ts +5 -3
  22. package/dist/mac/folders.js +18 -19
  23. package/dist/mac/folders.js.map +1 -1
  24. package/dist/mac/profile.js.map +1 -1
  25. package/dist/mac/read.d.ts +4 -0
  26. package/dist/mac/read.js +54 -16
  27. package/dist/mac/read.js.map +1 -1
  28. package/dist/mac/run.d.ts +19 -2
  29. package/dist/mac/run.js +42 -2
  30. package/dist/mac/run.js.map +1 -1
  31. package/dist/mac/scripts.d.ts +21 -14
  32. package/dist/mac/scripts.js +52 -21
  33. package/dist/mac/scripts.js.map +1 -1
  34. package/dist/mac/send.d.ts +3 -3
  35. package/dist/mac/send.js +44 -12
  36. package/dist/mac/send.js.map +1 -1
  37. package/dist/mac/templates.js +15 -18
  38. package/dist/mac/templates.js.map +1 -1
  39. package/dist/mail.d.ts +5 -3
  40. package/dist/mail.js +19 -6
  41. package/dist/mail.js.map +1 -1
  42. package/dist/runtime.js.map +1 -1
  43. package/dist/shared/args.js.map +1 -1
  44. package/dist/shared/attachmentMatch.d.ts +0 -6
  45. package/dist/shared/attachmentMatch.js +0 -1
  46. package/dist/shared/attachmentMatch.js.map +1 -1
  47. package/dist/shared/exec.js.map +1 -1
  48. package/dist/shared/json.d.ts +11 -6
  49. package/dist/shared/json.js +12 -7
  50. package/dist/shared/json.js.map +1 -1
  51. package/dist/shared/locate.d.ts +0 -8
  52. package/dist/shared/locate.js +0 -1
  53. package/dist/shared/locate.js.map +1 -1
  54. package/dist/shared/replyBody.d.ts +0 -15
  55. package/dist/shared/replyBody.js +0 -2
  56. package/dist/shared/replyBody.js.map +1 -1
  57. package/dist/shared/templateLimits.d.ts +14 -0
  58. package/dist/shared/templateLimits.js +21 -0
  59. package/dist/shared/templateLimits.js.map +1 -0
  60. package/dist/templateBody.d.ts +6 -0
  61. package/dist/templateBody.js +8 -1
  62. package/dist/templateBody.js.map +1 -1
  63. package/dist/types.d.ts +23 -3
  64. package/dist/types.js.map +1 -1
  65. package/dist/windows/bounces.js +4 -4
  66. package/dist/windows/bounces.js.map +1 -1
  67. package/dist/windows/cleanup.d.ts +5 -1
  68. package/dist/windows/cleanup.js +16 -11
  69. package/dist/windows/cleanup.js.map +1 -1
  70. package/dist/windows/drafts.d.ts +4 -4
  71. package/dist/windows/drafts.js +20 -31
  72. package/dist/windows/drafts.js.map +1 -1
  73. package/dist/windows/folders.js +1 -1
  74. package/dist/windows/folders.js.map +1 -1
  75. package/dist/windows/read.js +6 -2
  76. package/dist/windows/read.js.map +1 -1
  77. package/dist/windows/run.d.ts +0 -2
  78. package/dist/windows/run.js +1 -2
  79. package/dist/windows/run.js.map +1 -1
  80. package/dist/windows/scripts.d.ts +4 -4
  81. package/dist/windows/scripts.js +10 -10
  82. package/dist/windows/scripts.js.map +1 -1
  83. package/dist/windows/send.d.ts +8 -3
  84. package/dist/windows/send.js +10 -2
  85. package/dist/windows/send.js.map +1 -1
  86. package/dist/windows/templates.js +9 -15
  87. package/dist/windows/templates.js.map +1 -1
  88. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mac/templates.ts"],"names":[],"mappings":"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","sourcesContent":["// Template emails: ordinary mail items kept in a mailbox folder and reused as\n// email bodies, plus the compose-window editor for one.\n//\n// Outlook for Mac serves a message's HTML through `content` — the same thing\n// COM's HTMLBody returns — which is what makes templates portable between the\n// two platforms.\nimport {\n asIdList,\n asInt,\n asRow,\n asString,\n boolField,\n field,\n runOsaScript,\n splitFields,\n splitList,\n splitRecords,\n summaryFields\n} from './run';\nimport {\n accountLookupSnippet,\n FIND_FOLDER_HANDLER,\n macFolderPath,\n resolveMacAccount,\n rootFolderSnippet\n} from './scripts';\nimport { findTemplateMarkers } from '../templateBody';\nimport { NotFoundError } from '../errors';\nimport type { EditTemplateRequest, ReadTemplatesRequest, SaveTemplateRequest } from '../backend';\nimport type { SaveTemplateResult, TemplateEmail, TemplateFolderResult } from '../types';\n\n/** How deep under the mailbox root a template folder is searched for. */\nconst TEMPLATE_SEARCH_DEPTH = 3;\n\n/** Cap on one template body, matching Windows. */\nconst MAX_TEMPLATE_HTML = 100_000;\n\n/** Plain text standing in for a body that wasn't asked for. */\nconst PREVIEW_CHARS = 200;\n\n/**\n * Embedded (cid:) images live as attachments on the TEMPLATE item, so its HTML\n * reused elsewhere shows broken-image placeholders. Template emails are text\n * and markup only.\n */\nfunction stripEmbeddedImages(html: string): string {\n return html.replace(/<img[^>]*src=\"cid:[^\"]*\"[^>]*>/gi, '');\n}\n\n/**\n * The template emails in a mailbox folder, newest first; a missing folder is\n * reported as folderFound:false with the mailbox's top-level folder names.\n *\n * Bodies are read in a second pass, for the chosen templates only: a folder of\n * Word-sized templates is megabytes, and pulling all of them through stdout on\n * a call that wanted one is what overruns the output limit.\n */\nexport async function readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult> {\n const acct = await resolveMacAccount(request.account);\n // Pass 1 — find the folder and index it: three bulk reads, no bodies.\n const records = splitRecords(await runOsaScript(`${FIND_FOLDER_HANDLER}\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'root folder', 'rootFolder')}\nend tell\nset theFolder to my findFolderByName(rootFolder, ${asString(request.folder)}, ${asInt(TEMPLATE_SEARCH_DEPTH)})\ntell application \"Microsoft Outlook\"\n if theFolder is missing value then\n set names to {}\n repeat with f in (mail folders of rootFolder)\n try\n set end of names to (name of f) as string\n end try\n end repeat\n return ${asRow(['\"0\"', 'my sanitizeList(names)'])}\n end if\n set idList to id of every message of theFolder\n set subjList to subject of every message of theFolder\n try\n set modList to modification date of every message of theFolder\n on error\n set modList to {}\n end try\n set hasMods to ((count of modList) is (count of idList))\n set out to ${asRow(['\"1\"', '(name of theFolder as string)'])}\n repeat with i from 1 to (count of idList)\n set modAt to \"\"\n if hasMods then set modAt to my isoDate(item i of modList)\n set out to out & ${asRow(['(item i of idList as string)', '(item i of subjList)', 'modAt'])}\n end repeat\n return out\nend tell`, 'standard'));\n\n const header = splitFields(records[0] ?? '');\n if (field(header, 0) !== '1') {\n return {folderFound: false, folderPath: '', templates: [], availableFolders: splitList(field(header, 1))};\n }\n const folderPath = macFolderPath(request.account, field(header, 1) || request.folder, []);\n const wanted = (request.subject ?? '').toLowerCase();\n const chosen = records.slice(1)\n .map(record => {\n const parts = splitFields(record);\n return {id: field(parts, 0), subject: field(parts, 1).trim(), lastModified: field(parts, 2)};\n })\n // Newest first, before the cap, so \"the newest 20\" means that.\n .sort((a, b) => b.lastModified.localeCompare(a.lastModified))\n .filter(item => !wanted || item.subject.toLowerCase() === wanted)\n .slice(0, request.limit);\n if (chosen.length === 0) return {folderFound: true, folderPath, templates: [], availableFolders: []};\n\n // Pass 2 — the bodies, for the chosen templates only.\n const bodyProperty = request.includeBody ? 'content' : 'plain text content';\n const bodies = new Map<string, string>();\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n set out to \"\"\n repeat with theId in ${asIdList(chosen.map(item => item.id))}\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is not missing value then\n set bodyText to \"\"\n try\n set bodyText to (${bodyProperty} of theMsg) as string\n end try\n set out to out & ${asRow(['(theId as string)', 'bodyText'])}\n end if\n end repeat\n return out\nend tell`, 'standard');\n for (const record of splitRecords(raw)) {\n const parts = splitFields(record);\n bodies.set(field(parts, 0), field(parts, 1));\n }\n\n const templates: TemplateEmail[] = chosen.map(item => {\n const body = bodies.get(item.id) ?? '';\n const html = request.includeBody ? stripEmbeddedImages(body).slice(0, MAX_TEMPLATE_HTML) : '';\n // With the HTML in hand it is authoritative (markers survive Word\n // splitting them across tags); without it, the plain text is scanned,\n // where a marker can't have been split.\n const markers = findTemplateMarkers(request.includeBody ? html : body);\n return {\n entryId: item.id,\n subject: item.subject,\n htmlBody: html,\n bodyPreview: request.includeBody ? '' : body.trim().slice(0, PREVIEW_CHARS),\n sections: markers.sections,\n placeholders: markers.placeholders,\n lastModified: item.lastModified,\n };\n });\n return {folderFound: true, folderPath, templates, availableFolders: []};\n}\n\n/**\n * Save a new template email into a folder, creating the folder at the mailbox\n * root when absent. Always adds a new item — never overwrites one.\n */\nexport async function saveTemplateEmail(request: SaveTemplateRequest): Promise<SaveTemplateResult> {\n const acct = await resolveMacAccount(request.account);\n const parts = summaryFields(await runOsaScript(`${FIND_FOLDER_HANDLER}\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'root folder', 'rootFolder')}\nend tell\nset theFolder to my findFolderByName(rootFolder, ${asString(request.folder)}, ${asInt(TEMPLATE_SEARCH_DEPTH)})\ntell application \"Microsoft Outlook\"\n set wasCreated to false\n if theFolder is missing value then\n set theFolder to (make new mail folder at rootFolder with properties {name:${asString(request.folder)}})\n set wasCreated to true\n end if\n set countBefore to count of messages of theFolder\n set newMsg to make new outgoing message with properties {subject:${asString(request.subject)}, content:${asString(request.htmlBody)}}\n try\n set account of newMsg to targetAcct\n end try\n -- 'move' reports success even where it silently does nothing, so a template\n -- that was never filed must not be reported as saved.\n move newMsg to theFolder\n if (count of messages of theFolder) is not greater than countBefore then error \"Outlook did not file the template in '\" & (name of theFolder) & \"'.\"\n return ${asRow(['(name of theFolder as string)', '(wasCreated as string)'])}\nend tell`, 'standard'));\n return {\n folderPath: macFolderPath(request.account, field(parts, 0) || request.folder, []),\n folderCreated: boolField(parts, 1),\n };\n}\n\n/**\n * Open a compose window pre-filled with the HTML, wait for it to close, and\n * return the saved draft's HTML. On macOS the edit is kept by answering\n * Outlook's own \"save this message?\" prompt on close.\n */\nexport async function editEmailTemplate(request: EditTemplateRequest): Promise<string> {\n const subject = `${request.label} - Save and close when done`;\n const saved = await runOsaScript(`tell application \"Microsoft Outlook\"\n set editSubject to ${asString(subject)}\n set draftMsg to make new outgoing message with properties {subject:editSubject, content:${asString(request.html)}}\n open draftMsg\n activate\nend tell\n\n-- Poll until the compose window is gone, identifying ours by subject rather\n-- than counting windows the person may also have open.\nset stillOpen to true\nrepeat while stillOpen\n delay 0.7\n set stillOpen to false\n tell application \"Microsoft Outlook\"\n try\n repeat with w in draft windows\n try\n if ((subject of (object of w)) as string) is editSubject then\n set stillOpen to true\n exit repeat\n end if\n end try\n end repeat\n end try\n end tell\nend repeat\n\ntell application \"Microsoft Outlook\"\n -- Prefer the saved draft: it is what the person chose to keep, and it\n -- outlives the window. Every editor draft is removed so none pile up.\n set savedHtml to \"\"\n try\n repeat with acctList in {exchange accounts, imap accounts, pop accounts}\n repeat with a in acctList\n try\n repeat with m in (messages of (drafts of a))\n try\n if ((subject of m) as string) is editSubject then\n if savedHtml is \"\" then set savedHtml to (content of m) as string\n delete m\n end if\n end try\n end repeat\n end try\n end repeat\n end repeat\n end try\n return my sanitize(savedHtml)\nend tell`, 'interactive');\n if (!saved) {\n throw new NotFoundError('template', 'No template was saved. Save the draft before closing the window.');\n }\n return saved;\n}\n"]}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mac/templates.ts"],"names":[],"mappings":"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","sourcesContent":["// Template emails: ordinary mail items kept in a mailbox folder and reused as\n// email bodies, plus the compose-window editor for one.\n//\n// Outlook for Mac serves a message's HTML through `content` — the same thing\n// COM's HTMLBody returns — which is what makes templates portable between the\n// two platforms.\nimport {\n asIdList,\n asInt,\n asRow,\n asString,\n boolField,\n field,\n runOsaScript,\n splitFields,\n splitList,\n splitRecords,\n summaryFields,\n} from './run';\nimport {\n accountLookupSnippet,\n FIND_FOLDER_HANDLER,\n folderChainSnippet,\n macFolderPath,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport {\n EMBEDDED_IMAGE_SOURCE,\n MAX_TEMPLATE_HTML,\n TEMPLATE_PREVIEW_CHARS,\n TEMPLATE_SEARCH_DEPTH,\n} from '../shared/templateLimits';\nimport { findTemplateMarkers } from '../templateBody';\nimport { NotFoundError } from '../errors';\nimport type { EditTemplateRequest, ReadTemplatesRequest, SaveTemplateRequest } from '../backend';\nimport type { SaveTemplateResult, TemplateEmail, TemplateFolderResult } from '../types';\n\n/** A template body without its embedded images — see EMBEDDED_IMAGE_SOURCE. */\nfunction stripEmbeddedImages(html: string): string {\n return html.replace(new RegExp(EMBEDDED_IMAGE_SOURCE, 'gi'), '');\n}\n\n/**\n * The template emails in a mailbox folder, newest first; a missing folder is\n * reported as folderFound:false with the mailbox's top-level folder names.\n *\n * Bodies are read in a second pass, for the chosen templates only: a folder of\n * Word-sized templates is megabytes, and pulling all of them through stdout on\n * a call that wanted one is what overruns the output limit.\n */\nexport async function readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult> {\n const acct = await resolveMacAccount(request.account);\n // Pass 1 — find the folder and index it: three bulk reads, no bodies.\n const records = splitRecords(await runOsaScript(`${FIND_FOLDER_HANDLER}\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'root folder', 'rootFolder')}\nend tell\nset theFolder to my findFolderByName(rootFolder, ${asString(request.folder)}, ${asInt(TEMPLATE_SEARCH_DEPTH)})\ntell application \"Microsoft Outlook\"\n if theFolder is missing value then\n set names to {}\n repeat with f in (mail folders of rootFolder)\n try\n set end of names to (name of f) as string\n end try\n end repeat\n return ${asRow(['\"0\"', 'my sanitizeList(names)'])}\n end if\n set idList to id of every message of theFolder\n set subjList to subject of every message of theFolder\n try\n set modList to modification date of every message of theFolder\n on error\n set modList to {}\n end try\n set hasMods to ((count of modList) is (count of idList))\n -- The folder may sit up to the search depth down, so its whole chain is\n -- reported, not just its name: the path is the one Windows' FolderPath gives.\n${folderChainSnippet(' ')}\n set out to ${asRow(['\"1\"', 'my sanitizeList(chainNames)'])}\n repeat with i from 1 to (count of idList)\n set modAt to \"\"\n if hasMods then set modAt to my isoDate(item i of modList)\n set out to out & ${asRow(['(item i of idList as string)', '(item i of subjList)', 'modAt'])}\n end repeat\n return out\nend tell`, 'standard'));\n\n const header = splitFields(records[0] ?? '');\n if (field(header, 0) !== '1') {\n return { folderFound: false, folderPath: '', templates: [], availableFolders: splitList(field(header, 1)) };\n }\n const chain = splitList(field(header, 1)).reverse();\n const folderPath = chain.length\n ? macFolderPath(request.account, chain[0], chain.slice(1))\n : macFolderPath(request.account, request.folder, []);\n const wanted = (request.subject ?? '').toLowerCase();\n const chosen = records.slice(1)\n .map(record => {\n const parts = splitFields(record);\n return { id: field(parts, 0), subject: field(parts, 1).trim(), lastModified: field(parts, 2) };\n })\n // Newest first, before the cap, so \"the newest 20\" means that.\n .sort((a, b) => b.lastModified.localeCompare(a.lastModified))\n .filter(item => !wanted || item.subject.toLowerCase() === wanted)\n .slice(0, request.limit);\n if (chosen.length === 0) return { folderFound: true, folderPath, templates: [], availableFolders: [] };\n\n // Pass 2 — the bodies, for the chosen templates only.\n const bodyProperty = request.includeBody ? 'content' : 'plain text content';\n const bodies = new Map<string, string>();\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n set out to \"\"\n repeat with theId in ${asIdList(chosen.map(item => item.id))}\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is not missing value then\n set bodyText to \"\"\n try\n set bodyText to (${bodyProperty} of theMsg) as string\n end try\n set out to out & ${asRow(['(theId as string)', 'bodyText'])}\n end if\n end repeat\n return out\nend tell`, 'standard');\n for (const record of splitRecords(raw)) {\n const parts = splitFields(record);\n bodies.set(field(parts, 0), field(parts, 1));\n }\n\n const templates: TemplateEmail[] = chosen.map(item => {\n const body = bodies.get(item.id) ?? '';\n const html = request.includeBody ? stripEmbeddedImages(body).slice(0, MAX_TEMPLATE_HTML) : '';\n // With the HTML in hand it is authoritative (markers survive Word\n // splitting them across tags); without it, the plain text is scanned,\n // where a marker can't have been split.\n const markers = findTemplateMarkers(request.includeBody ? html : body);\n return {\n entryId: item.id,\n subject: item.subject,\n htmlBody: html,\n bodyPreview: request.includeBody ? '' : body.trim().slice(0, TEMPLATE_PREVIEW_CHARS),\n sections: markers.sections,\n placeholders: markers.placeholders,\n lastModified: item.lastModified,\n };\n });\n return { folderFound: true, folderPath, templates, availableFolders: [] };\n}\n\n/**\n * Save a new template email into a folder, creating the folder at the mailbox\n * root when absent. Always adds a new item — never overwrites one.\n */\nexport async function saveTemplateEmail(request: SaveTemplateRequest): Promise<SaveTemplateResult> {\n const acct = await resolveMacAccount(request.account);\n const parts = summaryFields(await runOsaScript(`${FIND_FOLDER_HANDLER}\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'root folder', 'rootFolder')}\nend tell\nset theFolder to my findFolderByName(rootFolder, ${asString(request.folder)}, ${asInt(TEMPLATE_SEARCH_DEPTH)})\ntell application \"Microsoft Outlook\"\n set wasCreated to false\n if theFolder is missing value then\n set theFolder to (make new mail folder at rootFolder with properties {name:${asString(request.folder)}})\n set wasCreated to true\n end if\n set countBefore to count of messages of theFolder\n set newMsg to make new outgoing message with properties {subject:${asString(request.subject)}, content:${asString(request.htmlBody)}}\n try\n set account of newMsg to targetAcct\n end try\n -- 'move' reports success even where it silently does nothing, so a template\n -- that was never filed must not be reported as saved.\n move newMsg to theFolder\n if (count of messages of theFolder) is not greater than countBefore then error \"Outlook did not file the template in '\" & (name of theFolder) & \"'.\"\n return ${asRow(['(name of theFolder as string)', '(wasCreated as string)'])}\nend tell`, 'standard'));\n return {\n folderPath: macFolderPath(request.account, field(parts, 0) || request.folder, []),\n folderCreated: boolField(parts, 1),\n };\n}\n\n/**\n * Open a compose window pre-filled with the HTML, wait for it to close, and\n * return the saved draft's HTML. On macOS the edit is kept by answering\n * Outlook's own \"save this message?\" prompt on close.\n */\nexport async function editEmailTemplate(request: EditTemplateRequest): Promise<string> {\n const subject = `${request.label} - Save and close when done`;\n const saved = await runOsaScript(`tell application \"Microsoft Outlook\"\n set editSubject to ${asString(subject)}\n set draftMsg to make new outgoing message with properties {subject:editSubject, content:${asString(request.html)}}\n open draftMsg\n activate\nend tell\n\n-- Poll until the compose window is gone, identifying ours by subject rather\n-- than counting windows the person may also have open.\nset stillOpen to true\nrepeat while stillOpen\n delay 0.7\n set stillOpen to false\n tell application \"Microsoft Outlook\"\n try\n repeat with w in draft windows\n try\n if ((subject of (object of w)) as string) is editSubject then\n set stillOpen to true\n exit repeat\n end if\n end try\n end repeat\n end try\n end tell\nend repeat\n\ntell application \"Microsoft Outlook\"\n -- Prefer the saved draft: it is what the person chose to keep, and it\n -- outlives the window. Every editor draft is removed so none pile up.\n set savedHtml to \"\"\n try\n repeat with acctList in {exchange accounts, imap accounts, pop accounts}\n repeat with a in acctList\n try\n repeat with m in (messages of (drafts of a))\n try\n if ((subject of m) as string) is editSubject then\n if savedHtml is \"\" then set savedHtml to (content of m) as string\n delete m\n end if\n end try\n end repeat\n end try\n end repeat\n end repeat\n end try\n return my sanitize(savedHtml)\nend tell`, 'interactive');\n if (!saved) {\n throw new NotFoundError('template', 'No template was saved. Save the draft before closing the window.');\n }\n return saved;\n}\n"]}
package/dist/mail.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MailFolderRef } from './types';
1
+ import type { DeleteMailOutcome, DeleteMailResult, MailFolderRef } from './types';
2
2
  /**
3
3
  * Outlook's olDefaultFolders ids for the folders this package addresses by
4
4
  * role rather than by name. The ids are Outlook's own, and a folder's NAME is
@@ -18,13 +18,15 @@ export declare const FolderId: Readonly<{
18
18
  * same name; reach the latter by qualifying it (`Inbox\Drafts`).
19
19
  */
20
20
  export declare const WELL_KNOWN_FOLDERS: Readonly<Record<string, number>>;
21
- /** The Inbox root, as a folder reference. */
22
- export declare const INBOX_REF: Readonly<MailFolderRef>;
23
21
  /**
24
22
  * Why `deleteOutlookEmails` refuses an item, worded once for both platforms —
25
23
  * it reaches the caller verbatim as a per-item `reason`.
26
24
  */
27
25
  export declare const PROTECTED_MAIL_REASON = "received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override";
26
+ /** The status a delete script reported for one item; anything it shouldn't have said is a failure. */
27
+ export declare function deleteStatus(reported: string): DeleteMailOutcome['status'];
28
+ /** A deleteOutlookEmails result: every outcome, and how many ended each way. */
29
+ export declare function deleteMailResult(dryRun: boolean, items: DeleteMailOutcome[]): DeleteMailResult;
28
30
  /**
29
31
  * Roots whose items are outgoing mail, which carries a sent time where incoming
30
32
  * mail carries a received one — so a date filter has to know which it's on.
package/dist/mail.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.REPLY_PREFIX = exports.REPLY_PREFIX_SOURCE = exports.NON_INCOMING_ROOTS = exports.PROTECTED_MAIL_REASON = exports.INBOX_REF = exports.WELL_KNOWN_FOLDERS = exports.FolderId = void 0;
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;
4
6
  exports.isOutgoingRoot = isOutgoingRoot;
5
7
  exports.hasReplyPrefix = hasReplyPrefix;
6
8
  exports.threadSubject = threadSubject;
@@ -9,9 +11,10 @@ exports.folderLeafName = folderLeafName;
9
11
  exports.mailFolderRef = mailFolderRef;
10
12
  exports.folderOption = folderOption;
11
13
  exports.splitQuotedOriginal = splitQuotedOriginal;
12
- // Platform-neutral mail helpers: folder strings, reply prefixes, and splitting a
13
- // reply from the thread it quotes. Pure functions, usable without Outlook, and
14
- // shared by both platforms so neither can come to read these differently.
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.
15
18
  const errors_1 = require("./errors");
16
19
  /**
17
20
  * Outlook's olDefaultFolders ids for the folders this package addresses by
@@ -45,7 +48,7 @@ exports.WELL_KNOWN_FOLDERS = Object.freeze({
45
48
  'outbox': exports.FolderId.Outbox,
46
49
  });
47
50
  /** The Inbox root, as a folder reference. */
48
- exports.INBOX_REF = Object.freeze({
51
+ const INBOX_REF = Object.freeze({
49
52
  rootId: exports.FolderId.Inbox,
50
53
  rootLabel: 'Inbox',
51
54
  segments: [],
@@ -55,6 +58,16 @@ exports.INBOX_REF = Object.freeze({
55
58
  * it reaches the caller verbatim as a per-item `reason`.
56
59
  */
57
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 };
70
+ }
58
71
  /**
59
72
  * Roots whose items are outgoing mail, which carries a sent time where incoming
60
73
  * mail carries a received one — so a date filter has to know which it's on.
@@ -173,7 +186,7 @@ function mailFolderRef(folder) {
173
186
  */
174
187
  function folderOption(folder) {
175
188
  if (folder === undefined || folder.trim() === '')
176
- return { ref: exports.INBOX_REF, label: 'Inbox' };
189
+ return { ref: INBOX_REF, label: 'Inbox' };
177
190
  if (folderSegments(folder).length === 0) {
178
191
  throw new errors_1.InvalidRequestError(`Folder '${folder}' does not name a folder.`);
179
192
  }
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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","sourcesContent":["// Platform-neutral mail helpers: folder strings, reply prefixes, and splitting a\n// reply from the thread it quotes. Pure functions, usable without Outlook, and\n// shared by both platforms so neither can come to read these differently.\nimport { InvalidRequestError } from './errors';\nimport type { 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. */\nexport const 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\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"]}
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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// Settings, and the scratch files every run needs.\n//\n// Each operation generates a script and runs it under an interpreter, which\n// leaves a caller four things they cannot otherwise reach: how long a run may\n// take, how much it may print, how to call it off, and what the script actually\n// said. Those are the settings here — process-wide through `configure()`, or per\n// bridge through `createOutlookBridge()`.\nimport { AsyncLocalStorage } from 'async_hooks';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { InvalidRequestError, type ScriptRunner } from './errors';\n\n/** One completed script run, handed to the `debug` hook. */\nexport interface BridgeDebugEvent {\n runner: ScriptRunner;\n /** The script exactly as it ran. */\n script: string;\n durationMs: number;\n /** Present only when the run failed. */\n error?: string;\n}\n\nexport interface BridgeOptions {\n /**\n * Ceiling on every script run, in milliseconds, replacing each operation's\n * own budget; 0 disables timeouts. Leave unset (or pass null to go back) to\n * let each operation use a budget sized for its work — a minute for a\n * lookup, up to ten for purging Deleted Items. `editEmailTemplate` waits on\n * a person and is never timed; cancel it with `signal`.\n */\n timeoutMs?: number | null;\n /**\n * Max bytes a script may print before the run is killed with\n * OUTPUT_TOO_LARGE. Searches that return whole bodies push against this.\n * Default 8 MiB.\n */\n maxBufferBytes?: number;\n /** Directory for generated scripts, and for saved attachments when a call\n * names no destination. Default: the OS temp directory. */\n tempDir?: string;\n /**\n * Cancels in-flight runs: the interpreter is killed and the call rejects\n * with ABORTED. Most useful scoped to the calls it should cancel:\n * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.\n */\n signal?: AbortSignal;\n /** `true` logs every script to stderr; a function receives each run\n * instead. Default: on when OUTLOOK_BRIDGE_DEBUG is set to anything but '' or '0'. */\n debug?: boolean | ((event: BridgeDebugEvent) => void);\n}\n\n/** The settings a call runs with, every default applied. */\nexport interface ResolvedConfig {\n /** undefined = each operation's own budget. */\n readonly timeoutMs: number | undefined;\n readonly maxBufferBytes: number;\n readonly tempDir: string;\n readonly signal: AbortSignal | undefined;\n readonly debug: boolean | ((event: BridgeDebugEvent) => void);\n}\n\nconst envDebug = process.env.OUTLOOK_BRIDGE_DEBUG;\n\n/** The process-wide settings, as `configure()` leaves them. */\nlet globalConfig: ResolvedConfig = {\n timeoutMs: undefined,\n maxBufferBytes: 8 * 1024 * 1024,\n tempDir: os.tmpdir(),\n signal: undefined,\n debug: !!envDebug && envDebug !== '0',\n};\n\n/**\n * Per-bridge settings, scoped to an async call tree.\n *\n * The platform code reads its settings through `getConfig()` wherever it needs\n * them rather than having a config threaded through every function. A bridge\n * instance runs each call inside `withConfig`, and the store follows every\n * `await` beneath it — so two bridges with different timeouts can run at once\n * in one process without seeing each other's.\n */\nconst scoped = new AsyncLocalStorage<ResolvedConfig>();\n\n/** Caller options merged over a base; keys left out keep the base's value. */\nexport function mergeOptions(base: ResolvedConfig, options: BridgeOptions = {}): ResolvedConfig {\n const next = {...base};\n if (options.timeoutMs === null) next.timeoutMs = undefined;\n else if (options.timeoutMs !== undefined) next.timeoutMs = nonNegative(options.timeoutMs, 'timeoutMs');\n if (options.maxBufferBytes !== undefined) next.maxBufferBytes = Math.max(1, nonNegative(options.maxBufferBytes, 'maxBufferBytes'));\n if (options.tempDir !== undefined) next.tempDir = options.tempDir;\n if (options.signal !== undefined) next.signal = options.signal;\n if (options.debug !== undefined) next.debug = options.debug;\n return next;\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n throw new InvalidRequestError(`${name} must be a number (got ${String(value)}).`);\n }\n return Math.max(0, Math.floor(value));\n}\n\n/**\n * Change the process-wide settings. Merges, so naming one option leaves the\n * rest alone.\n *\n * Affects every caller in the process that uses the exported functions. Prefer\n * `createOutlookBridge(options)` in a process shared with code you don't own.\n */\nexport function configure(options: BridgeOptions): void {\n globalConfig = mergeOptions(globalConfig, options);\n}\n\n/** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */\nexport function getConfig(): ResolvedConfig {\n return scoped.getStore() ?? globalConfig;\n}\n\n/** The process-wide settings, ignoring any bridge scope. */\nexport function getGlobalConfig(): ResolvedConfig {\n return globalConfig;\n}\n\n/** Run `fn` with `config` in force for it and everything it awaits. */\nexport function withConfig<T>(config: ResolvedConfig, fn: () => T): T {\n return scoped.run(config, fn);\n}\n\n/**\n * How long each kind of run may take when no `timeoutMs` is set.\n *\n * Generous on purpose: a budget is a guard against a hung interpreter, not a\n * performance target, and the first call against an Outlook that isn't running\n * yet spends much of its budget starting it.\n */\nexport const RUN_BUDGETS = {\n /** One lookup: accounts, signatures, opening or reading a single email. */\n quick: 60_000,\n /** Composing, filing, saving attachments, or reading one folder. */\n standard: 120_000,\n /** Walking a mailbox tree, or acting on a batch of items. */\n scan: 300_000,\n /** Emptying Deleted Items. */\n purge: 600_000,\n} as const;\n\n/** A budget name, or 'interactive' for a run that waits on a person and is never timed. */\nexport type RunBudget = keyof typeof RUN_BUDGETS | 'interactive';\n\n/** The timeout one run gets: the configured ceiling if there is one, else its budget. */\nexport function timeoutFor(budget: RunBudget): number {\n if (budget === 'interactive') return 0;\n return getConfig().timeoutMs ?? RUN_BUDGETS[budget];\n}\n\n/** Report one finished run to the configured debug hook. */\nexport function reportRun(event: BridgeDebugEvent): void {\n const {debug} = getConfig();\n if (!debug) return;\n if (typeof debug === 'function') {\n debug(event);\n return;\n }\n const outcome = event.error ? `FAILED: ${event.error}` : 'ok';\n process.stderr.write(`[outlook-bridge] ${event.runner} ${event.durationMs}ms ${outcome}\\n${event.script}\\n`);\n}\n\n/**\n * A scratch-file path in the configured temp directory. The random part\n * matters: two concurrent calls in the same millisecond would otherwise pick the\n * same name and overwrite each other's script mid-run.\n */\nexport function tempFile(kind: string, extension: string): string {\n const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n return path.join(getConfig().tempDir, `outlook-bridge-${kind}-${unique}.${extension}`);\n}\n\n/** Delete a scratch file, ignoring one that is already gone. */\nexport function removeTempFile(file: string): void {\n try {\n fs.unlinkSync(file);\n } catch {\n // The run is over either way; a leftover scratch file isn't worth failing it.\n }\n}\n\n/** A destination for saved attachments, and how to undo creating it. */\nexport interface AttachmentDestination {\n dir: string;\n\n /** Remove the directory if this call created it and left it empty. */\n discardIfUnused(): void;\n}\n\n/**\n * Where one call saves its attachments: the caller's directory (created if\n * absent), or a fresh private directory of its own.\n *\n * Private by default because attachments keep the names senders gave them, so\n * one shared folder means two emails carrying 'invoice.pdf' overwrite each\n * other — and so do two programs using this package on one machine.\n */\nexport function attachmentDestination(destDir?: string): AttachmentDestination {\n if (destDir) {\n const existed = fs.existsSync(destDir);\n fs.mkdirSync(destDir, {recursive: true});\n return {\n dir: destDir,\n discardIfUnused() {\n if (!existed) removeIfEmpty(destDir);\n },\n };\n }\n const dir = fs.mkdtempSync(path.join(getConfig().tempDir, 'outlook-bridge-attachments-'));\n return {dir, discardIfUnused: () => removeIfEmpty(dir)};\n}\n\nfunction removeIfEmpty(dir: string): void {\n try {\n if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);\n } catch {\n // Nothing to undo if it can't be read.\n }\n}\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// Settings, and the scratch files every run needs.\n//\n// Each operation generates a script and runs it under an interpreter, which\n// leaves a caller four things they cannot otherwise reach: how long a run may\n// take, how much it may print, how to call it off, and what the script actually\n// said. Those are the settings here — process-wide through `configure()`, or per\n// bridge through `createOutlookBridge()`.\nimport { AsyncLocalStorage } from 'async_hooks';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { InvalidRequestError, type ScriptRunner } from './errors';\n\n/** One completed script run, handed to the `debug` hook. */\nexport interface BridgeDebugEvent {\n runner: ScriptRunner;\n /** The script exactly as it ran. */\n script: string;\n durationMs: number;\n /** Present only when the run failed. */\n error?: string;\n}\n\nexport interface BridgeOptions {\n /**\n * Ceiling on every script run, in milliseconds, replacing each operation's\n * own budget; 0 disables timeouts. Leave unset (or pass null to go back) to\n * let each operation use a budget sized for its work — a minute for a\n * lookup, up to ten for purging Deleted Items. `editEmailTemplate` waits on\n * a person and is never timed; cancel it with `signal`.\n */\n timeoutMs?: number | null;\n /**\n * Max bytes a script may print before the run is killed with\n * OUTPUT_TOO_LARGE. Searches that return whole bodies push against this.\n * Default 8 MiB.\n */\n maxBufferBytes?: number;\n /** Directory for generated scripts, and for saved attachments when a call\n * names no destination. Default: the OS temp directory. */\n tempDir?: string;\n /**\n * Cancels in-flight runs: the interpreter is killed and the call rejects\n * with ABORTED. Most useful scoped to the calls it should cancel:\n * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.\n */\n signal?: AbortSignal;\n /** `true` logs every script to stderr; a function receives each run\n * instead. Default: on when OUTLOOK_BRIDGE_DEBUG is set to anything but '' or '0'. */\n debug?: boolean | ((event: BridgeDebugEvent) => void);\n}\n\n/** The settings a call runs with, every default applied. */\nexport interface ResolvedConfig {\n /** undefined = each operation's own budget. */\n readonly timeoutMs: number | undefined;\n readonly maxBufferBytes: number;\n readonly tempDir: string;\n readonly signal: AbortSignal | undefined;\n readonly debug: boolean | ((event: BridgeDebugEvent) => void);\n}\n\nconst envDebug = process.env.OUTLOOK_BRIDGE_DEBUG;\n\n/** The process-wide settings, as `configure()` leaves them. */\nlet globalConfig: ResolvedConfig = {\n timeoutMs: undefined,\n maxBufferBytes: 8 * 1024 * 1024,\n tempDir: os.tmpdir(),\n signal: undefined,\n debug: !!envDebug && envDebug !== '0',\n};\n\n/**\n * Per-bridge settings, scoped to an async call tree.\n *\n * The platform code reads its settings through `getConfig()` wherever it needs\n * them rather than having a config threaded through every function. A bridge\n * instance runs each call inside `withConfig`, and the store follows every\n * `await` beneath it — so two bridges with different timeouts can run at once\n * in one process without seeing each other's.\n */\nconst scoped = new AsyncLocalStorage<ResolvedConfig>();\n\n/** Caller options merged over a base; keys left out keep the base's value. */\nexport function mergeOptions(base: ResolvedConfig, options: BridgeOptions = {}): ResolvedConfig {\n const next = { ...base };\n if (options.timeoutMs === null) next.timeoutMs = undefined;\n else if (options.timeoutMs !== undefined) next.timeoutMs = nonNegative(options.timeoutMs, 'timeoutMs');\n if (options.maxBufferBytes !== undefined) next.maxBufferBytes = Math.max(1, nonNegative(options.maxBufferBytes, 'maxBufferBytes'));\n if (options.tempDir !== undefined) next.tempDir = options.tempDir;\n if (options.signal !== undefined) next.signal = options.signal;\n if (options.debug !== undefined) next.debug = options.debug;\n return next;\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n throw new InvalidRequestError(`${name} must be a number (got ${String(value)}).`);\n }\n return Math.max(0, Math.floor(value));\n}\n\n/**\n * Change the process-wide settings. Merges, so naming one option leaves the\n * rest alone.\n *\n * Affects every caller in the process that uses the exported functions. Prefer\n * `createOutlookBridge(options)` in a process shared with code you don't own.\n */\nexport function configure(options: BridgeOptions): void {\n globalConfig = mergeOptions(globalConfig, options);\n}\n\n/** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */\nexport function getConfig(): ResolvedConfig {\n return scoped.getStore() ?? globalConfig;\n}\n\n/** The process-wide settings, ignoring any bridge scope. */\nexport function getGlobalConfig(): ResolvedConfig {\n return globalConfig;\n}\n\n/** Run `fn` with `config` in force for it and everything it awaits. */\nexport function withConfig<T>(config: ResolvedConfig, fn: () => T): T {\n return scoped.run(config, fn);\n}\n\n/**\n * How long each kind of run may take when no `timeoutMs` is set.\n *\n * Generous on purpose: a budget is a guard against a hung interpreter, not a\n * performance target, and the first call against an Outlook that isn't running\n * yet spends much of its budget starting it.\n */\nexport const RUN_BUDGETS = {\n /** One lookup: accounts, signatures, opening or reading a single email. */\n quick: 60_000,\n /** Composing, filing, saving attachments, or reading one folder. */\n standard: 120_000,\n /** Walking a mailbox tree, or acting on a batch of items. */\n scan: 300_000,\n /** Emptying Deleted Items. */\n purge: 600_000,\n} as const;\n\n/** A budget name, or 'interactive' for a run that waits on a person and is never timed. */\nexport type RunBudget = keyof typeof RUN_BUDGETS | 'interactive';\n\n/** The timeout one run gets: the configured ceiling if there is one, else its budget. */\nexport function timeoutFor(budget: RunBudget): number {\n if (budget === 'interactive') return 0;\n return getConfig().timeoutMs ?? RUN_BUDGETS[budget];\n}\n\n/** Report one finished run to the configured debug hook. */\nexport function reportRun(event: BridgeDebugEvent): void {\n const { debug } = getConfig();\n if (!debug) return;\n if (typeof debug === 'function') {\n debug(event);\n return;\n }\n const outcome = event.error ? `FAILED: ${event.error}` : 'ok';\n process.stderr.write(`[outlook-bridge] ${event.runner} ${event.durationMs}ms ${outcome}\\n${event.script}\\n`);\n}\n\n/**\n * A scratch-file path in the configured temp directory. The random part\n * matters: two concurrent calls in the same millisecond would otherwise pick the\n * same name and overwrite each other's script mid-run.\n */\nexport function tempFile(kind: string, extension: string): string {\n const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n return path.join(getConfig().tempDir, `outlook-bridge-${kind}-${unique}.${extension}`);\n}\n\n/** Delete a scratch file, ignoring one that is already gone. */\nexport function removeTempFile(file: string): void {\n try {\n fs.unlinkSync(file);\n } catch {\n // The run is over either way; a leftover scratch file isn't worth failing it.\n }\n}\n\n/** A destination for saved attachments, and how to undo creating it. */\nexport interface AttachmentDestination {\n dir: string;\n\n /** Remove the directory if this call created it and left it empty. */\n discardIfUnused(): void;\n}\n\n/**\n * Where one call saves its attachments: the caller's directory (created if\n * absent), or a fresh private directory of its own.\n *\n * Private by default because attachments keep the names senders gave them, so\n * one shared folder means two emails carrying 'invoice.pdf' overwrite each\n * other — and so do two programs using this package on one machine.\n */\nexport function attachmentDestination(destDir?: string): AttachmentDestination {\n if (destDir) {\n const existed = fs.existsSync(destDir);\n fs.mkdirSync(destDir, { recursive: true });\n return {\n dir: destDir,\n discardIfUnused() {\n if (!existed) removeIfEmpty(destDir);\n },\n };\n }\n const dir = fs.mkdtempSync(path.join(getConfig().tempDir, 'outlook-bridge-attachments-'));\n return { dir, discardIfUnused: () => removeIfEmpty(dir) };\n}\n\nfunction removeIfEmpty(dir: string): void {\n try {\n if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);\n } catch {\n // Nothing to undo if it can't be read.\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/shared/args.ts"],"names":[],"mappings":"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","sourcesContent":["// Checking and normalizing what a caller passes, once, before either platform\n// sees it.\n//\n// This is a security boundary as much as a convenience. Arguments end up\n// inside generated scripts, and TypeScript's types vanish at run time: a\n// JavaScript caller, or a tool layer passing JSON through, can hand a string\n// where a count belongs. So every value is checked for its type here, every\n// count is made a whole number within its documented range, and the platform\n// code only ever receives values of the shape its script templates assume.\nimport { InvalidRequestError } from '../errors';\nimport type { EmailLocator, EmailRef, Recipients } from '../types';\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'an array';\n return typeof value === 'string' ? `'${value}'` : typeof value;\n}\n\n/** A required parameters object. */\nexport function requireObject<T extends object>(value: T | undefined | null, name: string): Partial<T> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** An optional options object; undefined and null are an empty one. */\nexport function optionsObject<T extends object>(value: T | undefined | null, name = 'options'): Partial<T> {\n if (value === undefined || value === null) return {};\n return requireObject(value, name);\n}\n\n/** A required string, trimmed. Blank is an error. */\nexport function requiredText(value: unknown, name: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new InvalidRequestError(`${name} is required and must be a non-empty string (got ${describe(value)}).`);\n }\n return value.trim();\n}\n\n/** A required string kept exactly as given — content, not an identifier. May be empty. */\nexport function requiredContent(value: unknown, name: string): string {\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** Optional content kept exactly as given; undefined and null mean \"not given\". */\nexport function optionalContent(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n return requiredContent(value, name);\n}\n\n/** An optional regular expression. */\nexport function optionalRegExp(value: unknown, name: string): RegExp | undefined {\n if (value === undefined || value === null) return undefined;\n // Checked by tag rather than instanceof, so a RegExp from another realm passes.\n if (Object.prototype.toString.call(value) !== '[object RegExp]') {\n throw new InvalidRequestError(`${name} must be a RegExp (got ${describe(value)}).`);\n }\n return value as RegExp;\n}\n\n/** An optional string, trimmed; undefined, null and blank all mean \"not given\". */\nexport function optionalText(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value.trim() || undefined;\n}\n\n/** An optional flag; undefined and null take the fallback. */\nexport function flag(value: unknown, name: string, fallback: boolean): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'boolean') {\n throw new InvalidRequestError(`${name} must be true or false (got ${describe(value)}).`);\n }\n return value;\n}\n\n/**\n * An optional count: undefined and null take the fallback, a fraction is\n * rounded down, and the result is clamped into [min, max] — the documented\n * range is a guard, not a trap. Anything that isn't a finite number is an error.\n */\nexport function count(value: unknown, name: string, fallback: number, min: number, max: number): number {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new InvalidRequestError(`${name} must be a finite number (got ${describe(value)}).`);\n }\n return Math.min(max, Math.max(min, Math.floor(value)));\n}\n\n/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */\nexport function textList(value: unknown, name: string): string[] {\n if (value === undefined || value === null) return [];\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => requiredText(item, `${name}[${index}]`));\n}\n\n/** A list of names matched exactly as given — attachment file names, say. */\nexport function nameList(value: unknown, name: string): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => {\n if (typeof item !== 'string' || item.trim() === '') {\n throw new InvalidRequestError(`${name}[${index}] must be a non-empty string (got ${describe(item)}).`);\n }\n return item;\n });\n}\n\n/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */\nexport function entryIdList(value: unknown, name = 'entryIds'): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of entry ids (got ${describe(value)}).`);\n }\n return [...new Set(value.map((id, index) => requiredText(id, `${name}[${index}]`)))];\n}\n\n/** Where an email lives, from an id or anything carrying a locator. */\nexport function emailLocator(email: EmailRef | undefined | null, name = 'email'): EmailLocator {\n if (typeof email === 'string') return {entryId: requiredText(email, name)};\n if (email && typeof email === 'object') {\n const entryId = requiredText(email.entryId, `${name}.entryId`);\n const storeId = optionalText(email.storeId, `${name}.storeId`);\n return storeId ? {entryId, storeId} : {entryId};\n }\n throw new InvalidRequestError(`${name} must be an entry id or an object with an entryId (got ${describe(email)}).`);\n}\n\n/** A string→string map, e.g. template placeholders. */\nexport function stringRecord(value: unknown, name: string): Record<string, string> | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object of strings (got ${describe(value)}).`);\n }\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new InvalidRequestError(`${name}.${key} must be a string (got ${describe(entry)}).`);\n }\n out[key] = entry;\n }\n return out;\n}\n\n/**\n * Split a recipient list into its entries.\n *\n * Separators are commas and semicolons — but not inside a quoted display name\n * or between angle brackets, where `\"Doe, Jo\" <jo@example.com>` has a comma\n * that belongs to the name.\n */\nexport function recipientList(value: Recipients | undefined | null, name: string): string[] {\n if (value === undefined || value === null) return [];\n const parts = Array.isArray(value) ? value : [value];\n const out: string[] = [];\n parts.forEach((part, index) => {\n if (typeof part !== 'string') {\n throw new InvalidRequestError(`${name}[${index}] must be a string (got ${describe(part)}).`);\n }\n out.push(...splitAddressList(part));\n });\n return out;\n}\n\nfunction splitAddressList(text: string): string[] {\n const entries: string[] = [];\n let current = '';\n let quoted = false;\n let angled = false;\n for (const ch of text) {\n if (ch === '\"' && !angled) quoted = !quoted;\n else if (ch === '<' && !quoted) angled = true;\n else if (ch === '>' && !quoted) angled = false;\n if ((ch === ',' || ch === ';') && !quoted && !angled) {\n entries.push(current);\n current = '';\n } else {\n current += ch;\n }\n }\n entries.push(current);\n return entries.map(entry => entry.trim()).filter(Boolean);\n}\n\n/** One recipient entry as a display name and an address. */\nexport interface ParsedRecipient {\n name: string;\n address: string;\n}\n\n/** `\"Doe, Jo\" <jo@example.com>` → name + address; a bare address has no name. */\nexport function parseRecipient(entry: string): ParsedRecipient {\n const angled = /^(.*)<([^<>]+)>\\s*$/.exec(entry);\n if (!angled) return {name: '', address: entry.trim()};\n const name = angled[1].trim().replace(/^\"(.*)\"$/, '$1').trim();\n return {name, address: angled[2].trim()};\n}\n"]}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/shared/args.ts"],"names":[],"mappings":"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","sourcesContent":["// Checking and normalizing what a caller passes, once, before either platform\n// sees it.\n//\n// This is a security boundary as much as a convenience. Arguments end up\n// inside generated scripts, and TypeScript's types vanish at run time: a\n// JavaScript caller, or a tool layer passing JSON through, can hand a string\n// where a count belongs. So every value is checked for its type here, every\n// count is made a whole number within its documented range, and the platform\n// code only ever receives values of the shape its script templates assume.\nimport { InvalidRequestError } from '../errors';\nimport type { EmailLocator, EmailRef, Recipients } from '../types';\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'an array';\n return typeof value === 'string' ? `'${value}'` : typeof value;\n}\n\n/** A required parameters object. */\nexport function requireObject<T extends object>(value: T | undefined | null, name: string): Partial<T> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** An optional options object; undefined and null are an empty one. */\nexport function optionsObject<T extends object>(value: T | undefined | null, name = 'options'): Partial<T> {\n if (value === undefined || value === null) return {};\n return requireObject(value, name);\n}\n\n/** A required string, trimmed. Blank is an error. */\nexport function requiredText(value: unknown, name: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new InvalidRequestError(`${name} is required and must be a non-empty string (got ${describe(value)}).`);\n }\n return value.trim();\n}\n\n/** A required string kept exactly as given — content, not an identifier. May be empty. */\nexport function requiredContent(value: unknown, name: string): string {\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** Optional content kept exactly as given; undefined and null mean \"not given\". */\nexport function optionalContent(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n return requiredContent(value, name);\n}\n\n/** An optional regular expression. */\nexport function optionalRegExp(value: unknown, name: string): RegExp | undefined {\n if (value === undefined || value === null) return undefined;\n // Checked by tag rather than instanceof, so a RegExp from another realm passes.\n if (Object.prototype.toString.call(value) !== '[object RegExp]') {\n throw new InvalidRequestError(`${name} must be a RegExp (got ${describe(value)}).`);\n }\n return value as RegExp;\n}\n\n/** An optional string, trimmed; undefined, null and blank all mean \"not given\". */\nexport function optionalText(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value.trim() || undefined;\n}\n\n/** An optional flag; undefined and null take the fallback. */\nexport function flag(value: unknown, name: string, fallback: boolean): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'boolean') {\n throw new InvalidRequestError(`${name} must be true or false (got ${describe(value)}).`);\n }\n return value;\n}\n\n/**\n * An optional count: undefined and null take the fallback, a fraction is\n * rounded down, and the result is clamped into [min, max] — the documented\n * range is a guard, not a trap. Anything that isn't a finite number is an error.\n */\nexport function count(value: unknown, name: string, fallback: number, min: number, max: number): number {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new InvalidRequestError(`${name} must be a finite number (got ${describe(value)}).`);\n }\n return Math.min(max, Math.max(min, Math.floor(value)));\n}\n\n/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */\nexport function textList(value: unknown, name: string): string[] {\n if (value === undefined || value === null) return [];\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => requiredText(item, `${name}[${index}]`));\n}\n\n/** A list of names matched exactly as given — attachment file names, say. */\nexport function nameList(value: unknown, name: string): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => {\n if (typeof item !== 'string' || item.trim() === '') {\n throw new InvalidRequestError(`${name}[${index}] must be a non-empty string (got ${describe(item)}).`);\n }\n return item;\n });\n}\n\n/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */\nexport function entryIdList(value: unknown, name = 'entryIds'): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of entry ids (got ${describe(value)}).`);\n }\n return [...new Set(value.map((id, index) => requiredText(id, `${name}[${index}]`)))];\n}\n\n/** Where an email lives, from an id or anything carrying a locator. */\nexport function emailLocator(email: EmailRef | undefined | null, name = 'email'): EmailLocator {\n if (typeof email === 'string') return { entryId: requiredText(email, name) };\n if (email && typeof email === 'object') {\n const entryId = requiredText(email.entryId, `${name}.entryId`);\n const storeId = optionalText(email.storeId, `${name}.storeId`);\n return storeId ? { entryId, storeId } : { entryId };\n }\n throw new InvalidRequestError(`${name} must be an entry id or an object with an entryId (got ${describe(email)}).`);\n}\n\n/** A string→string map, e.g. template placeholders. */\nexport function stringRecord(value: unknown, name: string): Record<string, string> | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object of strings (got ${describe(value)}).`);\n }\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new InvalidRequestError(`${name}.${key} must be a string (got ${describe(entry)}).`);\n }\n out[key] = entry;\n }\n return out;\n}\n\n/**\n * Split a recipient list into its entries.\n *\n * Separators are commas and semicolons — but not inside a quoted display name\n * or between angle brackets, where `\"Doe, Jo\" <jo@example.com>` has a comma\n * that belongs to the name.\n */\nexport function recipientList(value: Recipients | undefined | null, name: string): string[] {\n if (value === undefined || value === null) return [];\n const parts = Array.isArray(value) ? value : [value];\n const out: string[] = [];\n parts.forEach((part, index) => {\n if (typeof part !== 'string') {\n throw new InvalidRequestError(`${name}[${index}] must be a string (got ${describe(part)}).`);\n }\n out.push(...splitAddressList(part));\n });\n return out;\n}\n\nfunction splitAddressList(text: string): string[] {\n const entries: string[] = [];\n let current = '';\n let quoted = false;\n let angled = false;\n for (const ch of text) {\n if (ch === '\"' && !angled) quoted = !quoted;\n else if (ch === '<' && !quoted) angled = true;\n else if (ch === '>' && !quoted) angled = false;\n if ((ch === ',' || ch === ';') && !quoted && !angled) {\n entries.push(current);\n current = '';\n } else {\n current += ch;\n }\n }\n entries.push(current);\n return entries.map(entry => entry.trim()).filter(Boolean);\n}\n\n/** One recipient entry as a display name and an address. */\nexport interface ParsedRecipient {\n name: string;\n address: string;\n}\n\n/** `\"Doe, Jo\" <jo@example.com>` → name + address; a bare address has no name. */\nexport function parseRecipient(entry: string): ParsedRecipient {\n const angled = /^(.*)<([^<>]+)>\\s*$/.exec(entry);\n if (!angled) return { name: '', address: entry.trim() };\n const name = angled[1].trim().replace(/^\"(.*)\"$/, '$1').trim();\n return { name, address: angled[2].trim() };\n}\n"]}
@@ -1,9 +1,3 @@
1
- /**
2
- * Collapse whitespace runs (non-breaking spaces included) and trim, so a
3
- * trivially reformatted file name still resolves. Both platforms' regex engines
4
- * count U+00A0 as whitespace.
5
- */
6
- export declare function normalizeAttachmentName(name: string): string;
7
1
  /**
8
2
  * Index of the attachment matching `wanted`, or -1: exact first, then
9
3
  * whitespace-normalized and case-insensitive — in that order, so two names that
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.MAX_FILE_STEM = exports.RESERVED_FILE_NAME = exports.UNSAFE_FILE_NAME_CHARS = void 0;
7
- exports.normalizeAttachmentName = normalizeAttachmentName;
8
7
  exports.findAttachmentIndex = findAttachmentIndex;
9
8
  exports.attachmentNotFoundMessage = attachmentNotFoundMessage;
10
9
  exports.safeFileName = safeFileName;
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentMatch.js","sourceRoot":"","sources":["../../src/shared/attachmentMatch.ts"],"names":[],"mappings":"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","sourcesContent":["// Matching an attachment by name, the error when nothing matches, and the\n// file name it is saved under.\n//\n// The error is the useful half. Resolving an email by id can return a\n// DIFFERENT message when the id is stale or wrong — common exactly when many\n// replies share one subject — so a bare \"not found\" points at the wrong\n// problem. Naming the email that actually resolved, and the attachments it does\n// carry, is what makes the mistake visible.\nimport path from 'path';\n\n/**\n * Collapse whitespace runs (non-breaking spaces included) and trim, so a\n * trivially reformatted file name still resolves. Both platforms' regex engines\n * count U+00A0 as whitespace.\n */\nexport function normalizeAttachmentName(name: string): string {\n return name.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Index of the attachment matching `wanted`, or -1: exact first, then\n * whitespace-normalized and case-insensitive — in that order, so two names that\n * differ only in case still resolve the one asked for.\n */\nexport function findAttachmentIndex(names: readonly string[], wanted: string): number {\n const exact = names.indexOf(wanted);\n if (exact !== -1) return exact;\n const normalized = normalizeAttachmentName(wanted).toLowerCase();\n return names.findIndex(name => normalizeAttachmentName(name).toLowerCase() === normalized);\n}\n\n/**\n * The \"attachment not found\" message, worded once for both platforms. Fields\n * arrive as strings so the Windows script can build the same sentence from\n * PowerShell variables where macOS passes the values it read.\n */\nexport function attachmentNotFoundMessage(init: {\n fileName: string;\n senderEmail: string;\n subject: string;\n receivedTime: string;\n /** The attachments the email does carry, joined for display. */\n present: string;\n}): string {\n return `Attachment '${init.fileName}' not found. `\n + `Resolved email: from=${init.senderEmail}; subject=${init.subject}; received=${init.receivedTime}. `\n + `Attachments present: ${init.present}. `\n + 'If this is not the email you expected, its id is likely wrong or stale - list the mail again for a current one.';\n}\n\n/** Characters no file name may carry on Windows (a superset of macOS's). */\nexport const UNSAFE_FILE_NAME_CHARS = '[<>:\"/\\\\\\\\|?*\\\\x00-\\\\x1F]';\n\n/** Names Windows reserves for devices, with or without an extension. */\nexport const RESERVED_FILE_NAME = '^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\\\..*)?$';\n\n/** How long a saved name's stem may run before it is cut. */\nexport const MAX_FILE_STEM = 150;\n\n/**\n * A sender-chosen attachment name made safe to write: no path separators or\n * characters a file system rejects, no trailing dots or spaces, no reserved\n * device name, and a stem short enough to stay clear of path-length limits. A\n * name like `..\\..\\x.exe` therefore lands inside the destination as\n * `.._.._x.exe`, never outside it.\n */\nexport function safeFileName(name: string): string {\n let safe = name.replace(new RegExp(UNSAFE_FILE_NAME_CHARS, 'g'), '_').replace(/[. ]+$/, '');\n if (new RegExp(RESERVED_FILE_NAME, 'i').test(safe)) safe = `_${safe}`;\n const ext = path.extname(safe);\n const stem = safe.slice(0, safe.length - ext.length);\n if (stem.length > MAX_FILE_STEM) safe = stem.slice(0, MAX_FILE_STEM) + ext;\n return safe || 'attachment';\n}\n\n/**\n * A path in `dir` for `name` that `exists` reports free: the name itself, else\n * `name (1).ext`, `name (2).ext`, … Existing files are never overwritten.\n */\nexport function uniqueSavePath(dir: string, name: string, exists: (file: string) => boolean): string {\n const ext = path.extname(name);\n const stem = name.slice(0, name.length - ext.length);\n let candidate = path.join(dir, name);\n for (let n = 1; exists(candidate); n++) {\n candidate = path.join(dir, `${stem} (${n})${ext}`);\n }\n return candidate;\n}\n"]}
1
+ {"version":3,"file":"attachmentMatch.js","sourceRoot":"","sources":["../../src/shared/attachmentMatch.ts"],"names":[],"mappings":"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","sourcesContent":["// Matching an attachment by name, the error when nothing matches, and the\n// file name it is saved under.\n//\n// The error is the useful half. Resolving an email by id can return a\n// DIFFERENT message when the id is stale or wrong — common exactly when many\n// replies share one subject — so a bare \"not found\" points at the wrong\n// problem. Naming the email that actually resolved, and the attachments it does\n// carry, is what makes the mistake visible.\nimport path from 'path';\n\n/**\n * Collapse whitespace runs (non-breaking spaces included) and trim, so a\n * trivially reformatted file name still resolves. Both platforms' regex engines\n * count U+00A0 as whitespace.\n */\nfunction normalizeAttachmentName(name: string): string {\n return name.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Index of the attachment matching `wanted`, or -1: exact first, then\n * whitespace-normalized and case-insensitive — in that order, so two names that\n * differ only in case still resolve the one asked for.\n */\nexport function findAttachmentIndex(names: readonly string[], wanted: string): number {\n const exact = names.indexOf(wanted);\n if (exact !== -1) return exact;\n const normalized = normalizeAttachmentName(wanted).toLowerCase();\n return names.findIndex(name => normalizeAttachmentName(name).toLowerCase() === normalized);\n}\n\n/**\n * The \"attachment not found\" message, worded once for both platforms. Fields\n * arrive as strings so the Windows script can build the same sentence from\n * PowerShell variables where macOS passes the values it read.\n */\nexport function attachmentNotFoundMessage(init: {\n fileName: string;\n senderEmail: string;\n subject: string;\n receivedTime: string;\n /** The attachments the email does carry, joined for display. */\n present: string;\n}): string {\n return `Attachment '${init.fileName}' not found. `\n + `Resolved email: from=${init.senderEmail}; subject=${init.subject}; received=${init.receivedTime}. `\n + `Attachments present: ${init.present}. `\n + 'If this is not the email you expected, its id is likely wrong or stale - list the mail again for a current one.';\n}\n\n/** Characters no file name may carry on Windows (a superset of macOS's). */\nexport const UNSAFE_FILE_NAME_CHARS = '[<>:\"/\\\\\\\\|?*\\\\x00-\\\\x1F]';\n\n/** Names Windows reserves for devices, with or without an extension. */\nexport const RESERVED_FILE_NAME = '^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\\\..*)?$';\n\n/** How long a saved name's stem may run before it is cut. */\nexport const MAX_FILE_STEM = 150;\n\n/**\n * A sender-chosen attachment name made safe to write: no path separators or\n * characters a file system rejects, no trailing dots or spaces, no reserved\n * device name, and a stem short enough to stay clear of path-length limits. A\n * name like `..\\..\\x.exe` therefore lands inside the destination as\n * `.._.._x.exe`, never outside it.\n */\nexport function safeFileName(name: string): string {\n let safe = name.replace(new RegExp(UNSAFE_FILE_NAME_CHARS, 'g'), '_').replace(/[. ]+$/, '');\n if (new RegExp(RESERVED_FILE_NAME, 'i').test(safe)) safe = `_${safe}`;\n const ext = path.extname(safe);\n const stem = safe.slice(0, safe.length - ext.length);\n if (stem.length > MAX_FILE_STEM) safe = stem.slice(0, MAX_FILE_STEM) + ext;\n return safe || 'attachment';\n}\n\n/**\n * A path in `dir` for `name` that `exists` reports free: the name itself, else\n * `name (1).ext`, `name (2).ext`, … Existing files are never overwritten.\n */\nexport function uniqueSavePath(dir: string, name: string, exists: (file: string) => boolean): string {\n const ext = path.extname(name);\n const stem = name.slice(0, name.length - ext.length);\n let candidate = path.join(dir, name);\n for (let n = 1; exists(candidate); n++) {\n candidate = path.join(dir, `${stem} (${n})${ext}`);\n }\n return candidate;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/shared/exec.ts"],"names":[],"mappings":";;AA4BA,4CAoCC;AAhED,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,6EAA6E;AAC7E,kDAAkD;AAClD,iDAAyC;AACzC,sCAAkE;AAClE,wCAA8E;AAmB9E,8FAA8F;AAC9F,SAAgB,gBAAgB,CAAC,GAAkB;IAC/C,MAAM,EAAC,cAAc,EAAE,MAAM,EAAC,GAAG,IAAA,mBAAS,GAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAA,oBAAU,EAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAA,wBAAQ,EACJ,GAAG,CAAC,OAAO,EACX,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;QACb,sEAAsE;QACtE,+BAA+B;QAC/B,EAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAC,EACjF,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,KAAK,EAAE,CAAC;gBACR,MAAM,EAAC,OAAO,EAAE,IAAI,EAAC,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;gBACvD,MAAM,OAAO,GAAG,IAAA,2BAAkB,EAAC;oBAC/B,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,MAAM,IAAI,EAAE;oBACpB,OAAO;oBACP,IAAI;oBACJ,UAAU;oBACV,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,OAAO;oBAClB,cAAc;oBACd,MAAM;iBACT,CAAC,CAAC;gBACH,IAAA,mBAAS,EAAC,EAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAC,CAAC,CAAC;gBACxF,MAAM,CAAC,OAAO,CAAC,CAAC;gBAChB,OAAO;YACX,CAAC;YACD,IAAA,mBAAS,EAAC,EAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAC,CAAC,CAAC;YAChE,OAAO,CAAC,EAAC,MAAM,EAAE,UAAU,EAAC,CAAC,CAAC;QAClC,CAAC,CACJ,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["// Running one interpreter process: the part both platforms' runners share.\n//\n// Each platform decides how its script reaches the interpreter and how a\n// failure reads in that interpreter's stderr. Everything else — the settings a\n// run takes, telling an abort from a timeout from an overflow, and reporting\n// the run to the debug hook — happens here, once.\nimport { execFile } from 'child_process';\nimport { classifyRunFailure, type ScriptRunner } from '../errors';\nimport { getConfig, reportRun, type RunBudget, timeoutFor } from '../runtime';\n\nexport interface ScriptProcess {\n runner: ScriptRunner;\n command: string;\n args: readonly string[];\n /** The script exactly as it runs, for errors and the debug hook. */\n script: string;\n budget: RunBudget;\n\n /** The script's own failure message, and the line it came from, out of stderr. */\n parseFailure(stderr: string): { message: string; line?: number };\n}\n\nexport interface ProcessOutput {\n stdout: string;\n durationMs: number;\n}\n\n/** Run one script to completion, rejecting with the most specific OutlookError on failure. */\nexport function runScriptProcess(run: ScriptProcess): Promise<ProcessOutput> {\n const {maxBufferBytes, signal} = getConfig();\n const timeout = timeoutFor(run.budget);\n const startedAt = Date.now();\n return new Promise((resolve, reject) => {\n execFile(\n run.command,\n [...run.args],\n // windowsHide keeps a GUI host (an Electron app, say) from flashing a\n // console window on every run.\n {maxBuffer: maxBufferBytes, timeout, signal, windowsHide: true, encoding: 'utf8'},\n (error, stdout, stderr) => {\n const durationMs = Date.now() - startedAt;\n if (error) {\n const {message, line} = run.parseFailure(stderr ?? '');\n const failure = classifyRunFailure({\n runner: run.runner,\n script: run.script,\n stderr: stderr ?? '',\n message,\n line,\n durationMs,\n nodeError: error,\n timeoutMs: timeout,\n maxBufferBytes,\n signal,\n });\n reportRun({runner: run.runner, script: run.script, durationMs, error: failure.message});\n reject(failure);\n return;\n }\n reportRun({runner: run.runner, script: run.script, durationMs});\n resolve({stdout, durationMs});\n },\n );\n });\n}\n"]}
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/shared/exec.ts"],"names":[],"mappings":";;AA4BA,4CAoCC;AAhED,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,6EAA6E;AAC7E,kDAAkD;AAClD,iDAAyC;AACzC,sCAAkE;AAClE,wCAA8E;AAmB9E,8FAA8F;AAC9F,SAAgB,gBAAgB,CAAC,GAAkB;IAC/C,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,IAAA,mBAAS,GAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAA,oBAAU,EAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAA,wBAAQ,EACJ,GAAG,CAAC,OAAO,EACX,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;QACb,sEAAsE;QACtE,+BAA+B;QAC/B,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EACnF,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,IAAA,2BAAkB,EAAC;oBAC/B,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,MAAM,IAAI,EAAE;oBACpB,OAAO;oBACP,IAAI;oBACJ,UAAU;oBACV,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,OAAO;oBAClB,cAAc;oBACd,MAAM;iBACT,CAAC,CAAC;gBACH,IAAA,mBAAS,EAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC1F,MAAM,CAAC,OAAO,CAAC,CAAC;gBAChB,OAAO;YACX,CAAC;YACD,IAAA,mBAAS,EAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YAClE,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACpC,CAAC,CACJ,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["// Running one interpreter process: the part both platforms' runners share.\n//\n// Each platform decides how its script reaches the interpreter and how a\n// failure reads in that interpreter's stderr. Everything else — the settings a\n// run takes, telling an abort from a timeout from an overflow, and reporting\n// the run to the debug hook — happens here, once.\nimport { execFile } from 'child_process';\nimport { classifyRunFailure, type ScriptRunner } from '../errors';\nimport { getConfig, reportRun, type RunBudget, timeoutFor } from '../runtime';\n\nexport interface ScriptProcess {\n runner: ScriptRunner;\n command: string;\n args: readonly string[];\n /** The script exactly as it runs, for errors and the debug hook. */\n script: string;\n budget: RunBudget;\n\n /** The script's own failure message, and the line it came from, out of stderr. */\n parseFailure(stderr: string): { message: string; line?: number };\n}\n\nexport interface ProcessOutput {\n stdout: string;\n durationMs: number;\n}\n\n/** Run one script to completion, rejecting with the most specific OutlookError on failure. */\nexport function runScriptProcess(run: ScriptProcess): Promise<ProcessOutput> {\n const { maxBufferBytes, signal } = getConfig();\n const timeout = timeoutFor(run.budget);\n const startedAt = Date.now();\n return new Promise((resolve, reject) => {\n execFile(\n run.command,\n [...run.args],\n // windowsHide keeps a GUI host (an Electron app, say) from flashing a\n // console window on every run.\n { maxBuffer: maxBufferBytes, timeout, signal, windowsHide: true, encoding: 'utf8' },\n (error, stdout, stderr) => {\n const durationMs = Date.now() - startedAt;\n if (error) {\n const { message, line } = run.parseFailure(stderr ?? '');\n const failure = classifyRunFailure({\n runner: run.runner,\n script: run.script,\n stderr: stderr ?? '',\n message,\n line,\n durationMs,\n nodeError: error,\n timeoutMs: timeout,\n maxBufferBytes,\n signal,\n });\n reportRun({ runner: run.runner, script: run.script, durationMs, error: failure.message });\n reject(failure);\n return;\n }\n reportRun({ runner: run.runner, script: run.script, durationMs });\n resolve({ stdout, durationMs });\n },\n );\n });\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import type { ItemFailure } from '../types';
1
2
  /** One value as a list: an array stays, null and undefined become empty, anything else wraps. */
2
3
  export declare function toArray(value: unknown): unknown[];
3
4
  /** A value as a plain object, for reading fields off one row. */
@@ -9,9 +10,13 @@ export declare function bool(value: unknown): boolean;
9
10
  export declare function strList(value: unknown): string[];
10
11
  /** A base64 field decoded as UTF-8 — how scripts pass bodies that must survive intact. */
11
12
  export declare function base64Text(value: unknown): string;
12
- /** A list of per-item failures, as the scripts report them. */
13
- export declare function itemFailures(value: unknown): {
14
- entryId: string;
15
- subject: string;
16
- error: string;
17
- }[];
13
+ /**
14
+ * The PowerShell that appends one item's failure to `$failed`, inside the
15
+ * `catch` of a per-item loop: the row `itemFailures` reads back, written once
16
+ * so no script can name its fields differently. `idVariable` holds the item's
17
+ * id. `$subject` must be reset before each item's `try`, or a failure reports
18
+ * the previous item's subject.
19
+ */
20
+ export declare function itemFailurePs(idVariable: '$id' | '$entryId'): string;
21
+ /** A list of per-item failures, as `itemFailurePs` writes them. */
22
+ export declare function itemFailures(value: unknown): ItemFailure[];
@@ -1,10 +1,4 @@
1
1
  "use strict";
2
- // Coercing the JSON a PowerShell script prints into the package's types.
3
- //
4
- // ConvertTo-Json has two habits a caller must never see: it unwraps a
5
- // single-element array into a bare object, and it renders an empty one as
6
- // nothing at all. Every list crossing that boundary goes through `toArray`, and
7
- // every scalar through a coercion that survives a missing property.
8
2
  Object.defineProperty(exports, "__esModule", { value: true });
9
3
  exports.toArray = toArray;
10
4
  exports.record = record;
@@ -13,6 +7,7 @@ exports.num = num;
13
7
  exports.bool = bool;
14
8
  exports.strList = strList;
15
9
  exports.base64Text = base64Text;
10
+ exports.itemFailurePs = itemFailurePs;
16
11
  exports.itemFailures = itemFailures;
17
12
  /** One value as a list: an array stays, null and undefined become empty, anything else wraps. */
18
13
  function toArray(value) {
@@ -41,7 +36,17 @@ function strList(value) {
41
36
  function base64Text(value) {
42
37
  return Buffer.from(str(value), 'base64').toString('utf8');
43
38
  }
44
- /** A list of per-item failures, as the scripts report them. */
39
+ /**
40
+ * The PowerShell that appends one item's failure to `$failed`, inside the
41
+ * `catch` of a per-item loop: the row `itemFailures` reads back, written once
42
+ * so no script can name its fields differently. `idVariable` holds the item's
43
+ * id. `$subject` must be reset before each item's `try`, or a failure reports
44
+ * the previous item's subject.
45
+ */
46
+ function itemFailurePs(idVariable) {
47
+ return `$failed += [PSCustomObject]@{ entryId = ${idVariable}; subject = $subject; error = $_.Exception.Message }`;
48
+ }
49
+ /** A list of per-item failures, as `itemFailurePs` writes them. */
45
50
  function itemFailures(value) {
46
51
  return toArray(value).map(row => {
47
52
  const e = record(row);
@@ -1 +1 @@
1
- {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/shared/json.ts"],"names":[],"mappings":";AAAA,yEAAyE;AACzE,EAAE;AACF,sEAAsE;AACtE,0EAA0E;AAC1E,gFAAgF;AAChF,oEAAoE;;AAGpE,0BAEC;AAGD,wBAIC;AAED,kBAEC;AAED,kBAEC;AAED,oBAEC;AAGD,0BAEC;AAGD,gCAEC;AAGD,oCAKC;AAxCD,iGAAiG;AACjG,SAAgB,OAAO,CAAC,KAAc;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAED,iEAAiE;AACjE,SAAgB,MAAM,CAAC,KAAc;IACjC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACvE,CAAC,CAAC,KAAgC;QAClC,CAAC,CAAC,EAAE,CAAC;AACb,CAAC;AAED,SAAgB,GAAG,CAAC,KAAc;IAC9B,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,GAAG,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAgB,IAAI,CAAC,KAAc;IAC/B,OAAO,KAAK,KAAK,IAAI,CAAC;AAC1B,CAAC;AAED,mCAAmC;AACnC,SAAgB,OAAO,CAAC,KAAc;IAClC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,0FAA0F;AAC1F,SAAgB,UAAU,CAAC,KAAc;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED,+DAA+D;AAC/D,SAAgB,YAAY,CAAC,KAAc;IACvC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,EAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["// Coercing the JSON a PowerShell script prints into the package's types.\n//\n// ConvertTo-Json has two habits a caller must never see: it unwraps a\n// single-element array into a bare object, and it renders an empty one as\n// nothing at all. Every list crossing that boundary goes through `toArray`, and\n// every scalar through a coercion that survives a missing property.\n\n/** One value as a list: an array stays, null and undefined become empty, anything else wraps. */\nexport function toArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : value == null ? [] : [value];\n}\n\n/** A value as a plain object, for reading fields off one row. */\nexport function record(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function str(value: unknown): string {\n return value == null ? '' : String(value);\n}\n\nexport function num(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nexport function bool(value: unknown): boolean {\n return value === true;\n}\n\n/** A list of non-empty strings. */\nexport function strList(value: unknown): string[] {\n return toArray(value).map(str).filter(Boolean);\n}\n\n/** A base64 field decoded as UTF-8 — how scripts pass bodies that must survive intact. */\nexport function base64Text(value: unknown): string {\n return Buffer.from(str(value), 'base64').toString('utf8');\n}\n\n/** A list of per-item failures, as the scripts report them. */\nexport function itemFailures(value: unknown): { entryId: string; subject: string; error: string }[] {\n return toArray(value).map(row => {\n const e = record(row);\n return {entryId: str(e.entryId), subject: str(e.subject), error: str(e.error)};\n });\n}\n"]}
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/shared/json.ts"],"names":[],"mappings":";;AASA,0BAEC;AAGD,wBAIC;AAED,kBAEC;AAED,kBAEC;AAED,oBAEC;AAGD,0BAEC;AAGD,gCAEC;AASD,sCAEC;AAGD,oCAKC;AAnDD,iGAAiG;AACjG,SAAgB,OAAO,CAAC,KAAc;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAED,iEAAiE;AACjE,SAAgB,MAAM,CAAC,KAAc;IACjC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACvE,CAAC,CAAC,KAAgC;QAClC,CAAC,CAAC,EAAE,CAAC;AACb,CAAC;AAED,SAAgB,GAAG,CAAC,KAAc;IAC9B,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,GAAG,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAgB,IAAI,CAAC,KAAc;IAC/B,OAAO,KAAK,KAAK,IAAI,CAAC;AAC1B,CAAC;AAED,mCAAmC;AACnC,SAAgB,OAAO,CAAC,KAAc;IAClC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,0FAA0F;AAC1F,SAAgB,UAAU,CAAC,KAAc;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,UAA8B;IACxD,OAAO,2CAA2C,UAAU,sDAAsD,CAAC;AACvH,CAAC;AAED,mEAAmE;AACnE,SAAgB,YAAY,CAAC,KAAc;IACvC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;IACrF,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["// Coercing the JSON a PowerShell script prints into the package's types.\n//\n// ConvertTo-Json has two habits a caller must never see: it unwraps a\n// single-element array into a bare object, and it renders an empty one as\n// nothing at all. Every list crossing that boundary goes through `toArray`, and\n// every scalar through a coercion that survives a missing property.\nimport type { ItemFailure } from '../types';\n\n/** One value as a list: an array stays, null and undefined become empty, anything else wraps. */\nexport function toArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : value == null ? [] : [value];\n}\n\n/** A value as a plain object, for reading fields off one row. */\nexport function record(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function str(value: unknown): string {\n return value == null ? '' : String(value);\n}\n\nexport function num(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nexport function bool(value: unknown): boolean {\n return value === true;\n}\n\n/** A list of non-empty strings. */\nexport function strList(value: unknown): string[] {\n return toArray(value).map(str).filter(Boolean);\n}\n\n/** A base64 field decoded as UTF-8 — how scripts pass bodies that must survive intact. */\nexport function base64Text(value: unknown): string {\n return Buffer.from(str(value), 'base64').toString('utf8');\n}\n\n/**\n * The PowerShell that appends one item's failure to `$failed`, inside the\n * `catch` of a per-item loop: the row `itemFailures` reads back, written once\n * so no script can name its fields differently. `idVariable` holds the item's\n * id. `$subject` must be reset before each item's `try`, or a failure reports\n * the previous item's subject.\n */\nexport function itemFailurePs(idVariable: '$id' | '$entryId'): string {\n return `$failed += [PSCustomObject]@{ entryId = ${idVariable}; subject = $subject; error = $_.Exception.Message }`;\n}\n\n/** A list of per-item failures, as `itemFailurePs` writes them. */\nexport function itemFailures(value: unknown): ItemFailure[] {\n return toArray(value).map(row => {\n const e = record(row);\n return { entryId: str(e.entryId), subject: str(e.subject), error: str(e.error) };\n });\n}\n"]}
@@ -22,14 +22,6 @@ export declare function subjectPrefilter(subject: string | undefined): string |
22
22
  * the stamp was written in, and a search started either side of midnight.
23
23
  */
24
24
  export declare function daysBackFor(receivedTime: string | undefined, fallback: number, now?: number): number;
25
- /**
26
- * How well a found email answers the description, or 0 for "not this one".
27
- *
28
- * Disagreement on any given field disqualifies outright. Agreement is then
29
- * ranked, so an exact minute beats the same day: a sender who wrote twice in
30
- * one afternoon still resolves to the email described.
31
- */
32
- export declare function matchScore(wanted: WantedEmail, match: InboxSearchMatch): number;
33
25
  /** The best match, or null when nothing agrees with the description. */
34
26
  export declare function bestMatch(wanted: WantedEmail, matches: readonly InboxSearchMatch[]): InboxSearchMatch | null;
35
27
  /**
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.requireDescribed = requireDescribed;
4
4
  exports.subjectPrefilter = subjectPrefilter;
5
5
  exports.daysBackFor = daysBackFor;
6
- exports.matchScore = matchScore;
7
6
  exports.bestMatch = bestMatch;
8
7
  exports.accountSearchOrder = accountSearchOrder;
9
8
  // Finding an email again from what was kept about it: the rules, as pure