@postalsys/email-content 0.0.1

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 (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +288 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/analyze.d.ts +91 -0
  5. package/dist/analyze.d.ts.map +1 -0
  6. package/dist/analyze.js +274 -0
  7. package/dist/analyze.js.map +1 -0
  8. package/dist/annotate/client.d.ts +64 -0
  9. package/dist/annotate/client.d.ts.map +1 -0
  10. package/dist/annotate/client.js +139 -0
  11. package/dist/annotate/client.js.map +1 -0
  12. package/dist/annotate/llm.d.ts +46 -0
  13. package/dist/annotate/llm.d.ts.map +1 -0
  14. package/dist/annotate/llm.js +107 -0
  15. package/dist/annotate/llm.js.map +1 -0
  16. package/dist/annotate/prompt.d.ts +38 -0
  17. package/dist/annotate/prompt.d.ts.map +1 -0
  18. package/dist/annotate/prompt.js +165 -0
  19. package/dist/annotate/prompt.js.map +1 -0
  20. package/dist/annotate/store.d.ts +43 -0
  21. package/dist/annotate/store.d.ts.map +1 -0
  22. package/dist/annotate/store.js +46 -0
  23. package/dist/annotate/store.js.map +1 -0
  24. package/dist/canonical/html.d.ts +33 -0
  25. package/dist/canonical/html.d.ts.map +1 -0
  26. package/dist/canonical/html.js +156 -0
  27. package/dist/canonical/html.js.map +1 -0
  28. package/dist/canonical/text.d.ts +75 -0
  29. package/dist/canonical/text.d.ts.map +1 -0
  30. package/dist/canonical/text.js +217 -0
  31. package/dist/canonical/text.js.map +1 -0
  32. package/dist/classify/confidence.d.ts +38 -0
  33. package/dist/classify/confidence.d.ts.map +1 -0
  34. package/dist/classify/confidence.js +59 -0
  35. package/dist/classify/confidence.js.map +1 -0
  36. package/dist/classify/reconcile.d.ts +52 -0
  37. package/dist/classify/reconcile.d.ts.map +1 -0
  38. package/dist/classify/reconcile.js +100 -0
  39. package/dist/classify/reconcile.js.map +1 -0
  40. package/dist/classify/rules.d.ts +51 -0
  41. package/dist/classify/rules.d.ts.map +1 -0
  42. package/dist/classify/rules.js +648 -0
  43. package/dist/classify/rules.js.map +1 -0
  44. package/dist/cli/annotate.d.ts +23 -0
  45. package/dist/cli/annotate.d.ts.map +1 -0
  46. package/dist/cli/annotate.js +328 -0
  47. package/dist/cli/annotate.js.map +1 -0
  48. package/dist/cli/args.d.ts +17 -0
  49. package/dist/cli/args.d.ts.map +1 -0
  50. package/dist/cli/args.js +72 -0
  51. package/dist/cli/args.js.map +1 -0
  52. package/dist/cli/benchmark.d.ts +13 -0
  53. package/dist/cli/benchmark.d.ts.map +1 -0
  54. package/dist/cli/benchmark.js +172 -0
  55. package/dist/cli/benchmark.js.map +1 -0
  56. package/dist/cli/corpus.d.ts +9 -0
  57. package/dist/cli/corpus.d.ts.map +1 -0
  58. package/dist/cli/corpus.js +152 -0
  59. package/dist/cli/corpus.js.map +1 -0
  60. package/dist/cli/disagreements.d.ts +14 -0
  61. package/dist/cli/disagreements.d.ts.map +1 -0
  62. package/dist/cli/disagreements.js +133 -0
  63. package/dist/cli/disagreements.js.map +1 -0
  64. package/dist/cli/evaluate.d.ts +15 -0
  65. package/dist/cli/evaluate.d.ts.map +1 -0
  66. package/dist/cli/evaluate.js +215 -0
  67. package/dist/cli/evaluate.js.map +1 -0
  68. package/dist/cli/extract.d.ts +10 -0
  69. package/dist/cli/extract.d.ts.map +1 -0
  70. package/dist/cli/extract.js +139 -0
  71. package/dist/cli/extract.js.map +1 -0
  72. package/dist/cli/main.d.ts +9 -0
  73. package/dist/cli/main.d.ts.map +1 -0
  74. package/dist/cli/main.js +147 -0
  75. package/dist/cli/main.js.map +1 -0
  76. package/dist/corpus/analyze.d.ts +66 -0
  77. package/dist/corpus/analyze.d.ts.map +1 -0
  78. package/dist/corpus/analyze.js +214 -0
  79. package/dist/corpus/analyze.js.map +1 -0
  80. package/dist/corpus/migrations/index.d.ts +14 -0
  81. package/dist/corpus/migrations/index.d.ts.map +1 -0
  82. package/dist/corpus/migrations/index.js +158 -0
  83. package/dist/corpus/migrations/index.js.map +1 -0
  84. package/dist/corpus/sample.d.ts +66 -0
  85. package/dist/corpus/sample.d.ts.map +1 -0
  86. package/dist/corpus/sample.js +98 -0
  87. package/dist/corpus/sample.js.map +1 -0
  88. package/dist/corpus/scanner.d.ts +79 -0
  89. package/dist/corpus/scanner.d.ts.map +1 -0
  90. package/dist/corpus/scanner.js +256 -0
  91. package/dist/corpus/scanner.js.map +1 -0
  92. package/dist/corpus/stats.d.ts +76 -0
  93. package/dist/corpus/stats.d.ts.map +1 -0
  94. package/dist/corpus/stats.js +240 -0
  95. package/dist/corpus/stats.js.map +1 -0
  96. package/dist/corpus/store.d.ts +67 -0
  97. package/dist/corpus/store.d.ts.map +1 -0
  98. package/dist/corpus/store.js +196 -0
  99. package/dist/corpus/store.js.map +1 -0
  100. package/dist/corpus/thread-index.d.ts +31 -0
  101. package/dist/corpus/thread-index.d.ts.map +1 -0
  102. package/dist/corpus/thread-index.js +182 -0
  103. package/dist/corpus/thread-index.js.map +1 -0
  104. package/dist/corpus/worker.d.ts +21 -0
  105. package/dist/corpus/worker.d.ts.map +1 -0
  106. package/dist/corpus/worker.js +74 -0
  107. package/dist/corpus/worker.js.map +1 -0
  108. package/dist/detect/client.d.ts +28 -0
  109. package/dist/detect/client.d.ts.map +1 -0
  110. package/dist/detect/client.js +121 -0
  111. package/dist/detect/client.js.map +1 -0
  112. package/dist/detect/disclaimer.d.ts +12 -0
  113. package/dist/detect/disclaimer.d.ts.map +1 -0
  114. package/dist/detect/disclaimer.js +28 -0
  115. package/dist/detect/disclaimer.js.map +1 -0
  116. package/dist/detect/forward-header.d.ts +10 -0
  117. package/dist/detect/forward-header.d.ts.map +1 -0
  118. package/dist/detect/forward-header.js +35 -0
  119. package/dist/detect/forward-header.js.map +1 -0
  120. package/dist/detect/patterns/disclaimers.json +113 -0
  121. package/dist/detect/patterns/forward-headers.json +40 -0
  122. package/dist/detect/patterns/html-clients.json +157 -0
  123. package/dist/detect/patterns/quoted-headers.json +41 -0
  124. package/dist/detect/patterns/reply-headers.json +139 -0
  125. package/dist/detect/patterns/signatures.json +58 -0
  126. package/dist/detect/patterns.d.ts +43 -0
  127. package/dist/detect/patterns.d.ts.map +1 -0
  128. package/dist/detect/patterns.js +125 -0
  129. package/dist/detect/patterns.js.map +1 -0
  130. package/dist/detect/reply-header.d.ts +44 -0
  131. package/dist/detect/reply-header.d.ts.map +1 -0
  132. package/dist/detect/reply-header.js +134 -0
  133. package/dist/detect/reply-header.js.map +1 -0
  134. package/dist/detect/signature.d.ts +28 -0
  135. package/dist/detect/signature.d.ts.map +1 -0
  136. package/dist/detect/signature.js +77 -0
  137. package/dist/detect/signature.js.map +1 -0
  138. package/dist/detect/traffic.d.ts +26 -0
  139. package/dist/detect/traffic.d.ts.map +1 -0
  140. package/dist/detect/traffic.js +98 -0
  141. package/dist/detect/traffic.js.map +1 -0
  142. package/dist/eval/metrics.d.ts +87 -0
  143. package/dist/eval/metrics.d.ts.map +1 -0
  144. package/dist/eval/metrics.js +185 -0
  145. package/dist/eval/metrics.js.map +1 -0
  146. package/dist/index.d.ts +54 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +163 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/match/history.d.ts +62 -0
  151. package/dist/match/history.d.ts.map +1 -0
  152. package/dist/match/history.js +128 -0
  153. package/dist/match/history.js.map +1 -0
  154. package/dist/match/normalize.d.ts +33 -0
  155. package/dist/match/normalize.d.ts.map +1 -0
  156. package/dist/match/normalize.js +63 -0
  157. package/dist/match/normalize.js.map +1 -0
  158. package/dist/match/shingle.d.ts +44 -0
  159. package/dist/match/shingle.d.ts.map +1 -0
  160. package/dist/match/shingle.js +78 -0
  161. package/dist/match/shingle.js.map +1 -0
  162. package/dist/mime/charset.d.ts +33 -0
  163. package/dist/mime/charset.d.ts.map +1 -0
  164. package/dist/mime/charset.js +118 -0
  165. package/dist/mime/charset.js.map +1 -0
  166. package/dist/mime/headers.d.ts +32 -0
  167. package/dist/mime/headers.d.ts.map +1 -0
  168. package/dist/mime/headers.js +127 -0
  169. package/dist/mime/headers.js.map +1 -0
  170. package/dist/mime/model.d.ts +111 -0
  171. package/dist/mime/model.d.ts.map +1 -0
  172. package/dist/mime/model.js +17 -0
  173. package/dist/mime/model.js.map +1 -0
  174. package/dist/mime/parse.d.ts +27 -0
  175. package/dist/mime/parse.d.ts.map +1 -0
  176. package/dist/mime/parse.js +343 -0
  177. package/dist/mime/parse.js.map +1 -0
  178. package/dist/segment/features.d.ts +29 -0
  179. package/dist/segment/features.d.ts.map +1 -0
  180. package/dist/segment/features.js +102 -0
  181. package/dist/segment/features.js.map +1 -0
  182. package/dist/segment/html-blocks.d.ts +28 -0
  183. package/dist/segment/html-blocks.d.ts.map +1 -0
  184. package/dist/segment/html-blocks.js +596 -0
  185. package/dist/segment/html-blocks.js.map +1 -0
  186. package/dist/segment/text-blocks.d.ts +30 -0
  187. package/dist/segment/text-blocks.d.ts.map +1 -0
  188. package/dist/segment/text-blocks.js +204 -0
  189. package/dist/segment/text-blocks.js.map +1 -0
  190. package/dist/segment/types.d.ts +88 -0
  191. package/dist/segment/types.d.ts.map +1 -0
  192. package/dist/segment/types.js +11 -0
  193. package/dist/segment/types.js.map +1 -0
  194. package/dist/types.d.ts +85 -0
  195. package/dist/types.d.ts.map +1 -0
  196. package/dist/types.js +17 -0
  197. package/dist/types.js.map +1 -0
  198. package/dist/util/hash.d.ts +49 -0
  199. package/dist/util/hash.d.ts.map +1 -0
  200. package/dist/util/hash.js +0 -0
  201. package/dist/util/hash.js.map +1 -0
  202. package/dist/util/lang.d.ts +25 -0
  203. package/dist/util/lang.d.ts.map +1 -0
  204. package/dist/util/lang.js +135 -0
  205. package/dist/util/lang.js.map +1 -0
  206. package/dist/util/worker-pool.d.ts +51 -0
  207. package/dist/util/worker-pool.d.ts.map +1 -0
  208. package/dist/util/worker-pool.js +155 -0
  209. package/dist/util/worker-pool.js.map +1 -0
  210. package/package.json +74 -0
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /**
3
+ * Thread reconstruction over a scanned corpus.
4
+ *
5
+ * Resolves a message's In-Reply-To and References to files that are actually present, so that
6
+ * Stage 5 has something to match against. Measured over the working corpus, 90.9 percent of replies
7
+ * resolve their direct parent this way.
8
+ *
9
+ * Ancestors are returned nearest first. The direct parent carries most of the value: it is what the
10
+ * sender's client quoted from. Older ancestors matter for deeply nested threads, where the reply
11
+ * contains several generations of history at once.
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ var __importDefault = (this && this.__importDefault) || function (mod) {
47
+ return (mod && mod.__esModule) ? mod : { "default": mod };
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.DEFAULT_MAX_ANCESTORS = void 0;
51
+ exports.corpusThreadResolver = corpusThreadResolver;
52
+ exports.folderThreadResolver = folderThreadResolver;
53
+ const node_fs_1 = __importDefault(require("node:fs"));
54
+ const node_path_1 = __importDefault(require("node:path"));
55
+ const history_1 = require("../match/history");
56
+ /** How many ancestors to load by default. Each one costs a file read and a parse. */
57
+ exports.DEFAULT_MAX_ANCESTORS = 3;
58
+ /**
59
+ * Build a resolver backed by a scanned corpus database.
60
+ *
61
+ * The lookup is a single indexed query per candidate Message-ID, so no in memory index of the whole
62
+ * corpus is needed. That matters: a Message-ID map over 320k messages is tens of megabytes that
63
+ * would otherwise be paid for on every process start.
64
+ */
65
+ function corpusThreadResolver(store, root) {
66
+ const cache = new Map();
67
+ const load = async (messageId, isParent) => {
68
+ const cached = cache.get(messageId);
69
+ if (cached !== undefined) {
70
+ return cached ? { ...cached, isParent } : null;
71
+ }
72
+ const row = store.queryOne('SELECT path FROM messages WHERE message_id = ? LIMIT 1', messageId);
73
+ if (!row) {
74
+ cache.set(messageId, null);
75
+ return null;
76
+ }
77
+ try {
78
+ const raw = node_fs_1.default.readFileSync(node_path_1.default.join(root, row.path));
79
+ const document = await (0, history_1.buildHistoryDocument)(raw, row.path, isParent);
80
+ cache.set(messageId, document);
81
+ return document;
82
+ }
83
+ catch {
84
+ cache.set(messageId, null);
85
+ return null;
86
+ }
87
+ };
88
+ return {
89
+ async resolve(headers, maxAncestors = exports.DEFAULT_MAX_ANCESTORS) {
90
+ const documents = [];
91
+ const seen = new Set();
92
+ if (headers.inReplyTo) {
93
+ seen.add(headers.inReplyTo);
94
+ const parent = await load(headers.inReplyTo, true);
95
+ if (parent) {
96
+ documents.push(parent);
97
+ }
98
+ }
99
+ // References run oldest first, so walk backwards for nearest ancestors.
100
+ for (let i = headers.references.length - 1; i >= 0 && documents.length < maxAncestors; i--) {
101
+ const reference = headers.references[i];
102
+ if (seen.has(reference)) {
103
+ continue;
104
+ }
105
+ seen.add(reference);
106
+ const ancestor = await load(reference, false);
107
+ if (ancestor) {
108
+ documents.push(ancestor);
109
+ }
110
+ }
111
+ return documents;
112
+ }
113
+ };
114
+ }
115
+ /** Build a resolver from a folder of .eml files, for callers with no corpus database. */
116
+ function folderThreadResolver(folder) {
117
+ let index = null;
118
+ const buildIndex = async () => {
119
+ const map = new Map();
120
+ let entries;
121
+ try {
122
+ entries = node_fs_1.default.readdirSync(folder).filter(name => /\.eml$/i.test(name));
123
+ }
124
+ catch {
125
+ return map;
126
+ }
127
+ const { parseMessage } = await Promise.resolve().then(() => __importStar(require('../mime/parse')));
128
+ for (const name of entries) {
129
+ try {
130
+ // Headers live at the front, so a bounded read is enough to index a folder.
131
+ const handle = node_fs_1.default.openSync(node_path_1.default.join(folder, name), 'r');
132
+ const buffer = Buffer.alloc(32768);
133
+ const read = node_fs_1.default.readSync(handle, buffer, 0, buffer.length, 0);
134
+ node_fs_1.default.closeSync(handle);
135
+ const message = await parseMessage(buffer.subarray(0, read));
136
+ if (message.headers.messageId) {
137
+ map.set(message.headers.messageId, name);
138
+ }
139
+ }
140
+ catch {
141
+ // A file that cannot be indexed simply is not available as history.
142
+ }
143
+ }
144
+ return map;
145
+ };
146
+ return {
147
+ async resolve(headers, maxAncestors = exports.DEFAULT_MAX_ANCESTORS) {
148
+ if (!index) {
149
+ index = await buildIndex();
150
+ }
151
+ const documents = [];
152
+ const wanted = [];
153
+ if (headers.inReplyTo) {
154
+ wanted.push({ id: headers.inReplyTo, isParent: true });
155
+ }
156
+ for (let i = headers.references.length - 1; i >= 0; i--) {
157
+ const reference = headers.references[i];
158
+ if (reference !== headers.inReplyTo) {
159
+ wanted.push({ id: reference, isParent: false });
160
+ }
161
+ }
162
+ for (const item of wanted) {
163
+ if (documents.length >= maxAncestors) {
164
+ break;
165
+ }
166
+ const name = index.get(item.id);
167
+ if (!name) {
168
+ continue;
169
+ }
170
+ try {
171
+ const raw = node_fs_1.default.readFileSync(node_path_1.default.join(folder, name));
172
+ documents.push(await (0, history_1.buildHistoryDocument)(raw, name, item.isParent));
173
+ }
174
+ catch {
175
+ // Unreadable ancestor, skip.
176
+ }
177
+ }
178
+ return documents;
179
+ }
180
+ };
181
+ }
182
+ //# sourceMappingURL=thread-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-index.js","sourceRoot":"","sources":["../../src/corpus/thread-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Corpus scan worker.
3
+ *
4
+ * One message per task. Every failure mode is caught and reported as a result rather than as a
5
+ * thrown error, so that a malformed file degrades to a row with status 'failed' instead of killing
6
+ * the worker. The only thing that can still take a worker down is running out of memory or an
7
+ * infinite loop, and the pool's task timeout covers both.
8
+ */
9
+ import { type MessageRecord } from './analyze';
10
+ import { type FileRecord } from './store';
11
+ export interface ScanTask {
12
+ /** Path relative to the corpus root. */
13
+ relPath: string;
14
+ size: number;
15
+ mtime: number;
16
+ }
17
+ export interface ScanResult {
18
+ file: FileRecord;
19
+ message: MessageRecord | null;
20
+ }
21
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/corpus/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACrB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CACjC"}
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * Corpus scan worker.
4
+ *
5
+ * One message per task. Every failure mode is caught and reported as a result rather than as a
6
+ * thrown error, so that a malformed file degrades to a row with status 'failed' instead of killing
7
+ * the worker. The only thing that can still take a worker down is running out of memory or an
8
+ * infinite loop, and the pool's task timeout covers both.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const node_fs_1 = __importDefault(require("node:fs"));
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const node_worker_threads_1 = require("node:worker_threads");
17
+ const parse_1 = require("../mime/parse");
18
+ const hash_1 = require("../util/hash");
19
+ const analyze_1 = require("./analyze");
20
+ const config = node_worker_threads_1.workerData;
21
+ function errorDetail(err) {
22
+ if (err instanceof Error) {
23
+ return `${err.name}: ${err.message}`.slice(0, 300);
24
+ }
25
+ return String(err).slice(0, 300);
26
+ }
27
+ async function handle(task) {
28
+ const absolute = node_path_1.default.join(config.root, task.relPath);
29
+ const started = Date.now();
30
+ const base = {
31
+ path: task.relPath,
32
+ size: task.size,
33
+ mtime: task.mtime,
34
+ sha256: null,
35
+ status: 'ok',
36
+ errorCode: null,
37
+ errorDetail: null,
38
+ durationMs: null
39
+ };
40
+ if (task.size > config.maxFileBytes) {
41
+ // Oversize messages are recorded, not silently dropped, so the corpus report can show how
42
+ // much of the tail was skipped and why.
43
+ return { file: { ...base, status: 'oversize', errorCode: 'oversize', durationMs: Date.now() - started }, message: null };
44
+ }
45
+ let raw;
46
+ try {
47
+ raw = node_fs_1.default.readFileSync(absolute);
48
+ }
49
+ catch (err) {
50
+ return { file: { ...base, status: 'failed', errorCode: 'read_error', errorDetail: errorDetail(err), durationMs: Date.now() - started }, message: null };
51
+ }
52
+ const hash = (0, hash_1.sha256)(raw);
53
+ try {
54
+ const parsed = await (0, parse_1.parseMessage)(raw, { maxTextPartBytes: config.maxTextPartBytes });
55
+ const record = (0, analyze_1.analyzeMessage)(parsed, config.salt);
56
+ return {
57
+ file: { ...base, sha256: hash, status: 'ok', durationMs: Date.now() - started },
58
+ message: record
59
+ };
60
+ }
61
+ catch (err) {
62
+ return {
63
+ file: { ...base, sha256: hash, status: 'failed', errorCode: 'parse_error', errorDetail: errorDetail(err), durationMs: Date.now() - started },
64
+ message: null
65
+ };
66
+ }
67
+ }
68
+ if (node_worker_threads_1.parentPort) {
69
+ const port = node_worker_threads_1.parentPort;
70
+ port.on('message', (envelope) => {
71
+ handle(envelope.task).then(result => port.postMessage({ id: envelope.id, ok: true, result }), err => port.postMessage({ id: envelope.id, ok: false, error: errorDetail(err) }));
72
+ });
73
+ }
74
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/corpus/worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Client fingerprinting.
3
+ *
4
+ * Knowing which client composed a message is evidence, not truth. It is used to prioritise
5
+ * structural detectors and to stratify sampling, never to decide a label on its own. A message
6
+ * whose client cannot be identified must still extract correctly, which is why "unknown" is a
7
+ * first class outcome rather than a failure.
8
+ *
9
+ * Signals, in decreasing reliability: Message-ID domain (hard to spoof accidentally, set by the
10
+ * submitting server), X-Mailer and User-Agent, MIME boundary shape, then HTML structure hints.
11
+ */
12
+ import { type MessageHeaders } from '../mime/model';
13
+ export type ClientId = 'gmail' | 'gmail-mobile' | 'outlook-web' | 'outlook-desktop' | 'outlook-mobile' | 'apple-mail' | 'apple-mail-mobile' | 'thunderbird' | 'proton' | 'yahoo' | 'zimbra' | 'roundcube' | 'superhuman' | 'hey' | 'front' | 'zendesk' | 'hubspot' | 'github' | 'atlassian' | 'mailchimp' | 'sendgrid' | 'amazon-ses' | 'nodemailer' | 'phpmailer' | 'mutt' | 'webmail-generic' | 'library-generic' | 'unknown';
14
+ export interface ClientFingerprint {
15
+ client: ClientId;
16
+ /** Which signal decided it. */
17
+ source: 'message-id' | 'x-mailer' | 'user-agent' | 'boundary' | 'html' | 'header' | 'none';
18
+ confidence: number;
19
+ }
20
+ /**
21
+ * Identify the composing client.
22
+ *
23
+ * @param headers decoded message headers
24
+ * @param html first text/html part, or null. Only the head of the document is inspected.
25
+ * @param boundary Content-Type boundary parameter of the root part, if any
26
+ */
27
+ export declare function fingerprintClient(headers: MessageHeaders, html: string | null, boundary: string | null): ClientFingerprint;
28
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/detect/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,MAAM,QAAQ,GACd,OAAO,GACP,cAAc,GACd,aAAa,GACb,iBAAiB,GACjB,gBAAgB,GAChB,YAAY,GACZ,mBAAmB,GACnB,aAAa,GACb,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,WAAW,GACX,YAAY,GACZ,KAAK,GACL,OAAO,GACP,SAAS,GACT,SAAS,GACT,QAAQ,GACR,WAAW,GACX,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,WAAW,GACX,MAAM,GACN,iBAAiB,GACjB,iBAAiB,GACjB,SAAS,CAAC;AAEhB,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,QAAQ,CAAC;IACjB,+BAA+B;IAC/B,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3F,UAAU,EAAE,MAAM,CAAC;CACtB;AA8ED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAsC1H"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /**
3
+ * Client fingerprinting.
4
+ *
5
+ * Knowing which client composed a message is evidence, not truth. It is used to prioritise
6
+ * structural detectors and to stratify sampling, never to decide a label on its own. A message
7
+ * whose client cannot be identified must still extract correctly, which is why "unknown" is a
8
+ * first class outcome rather than a failure.
9
+ *
10
+ * Signals, in decreasing reliability: Message-ID domain (hard to spoof accidentally, set by the
11
+ * submitting server), X-Mailer and User-Agent, MIME boundary shape, then HTML structure hints.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.fingerprintClient = fingerprintClient;
15
+ /** Message-ID domain patterns. Set by the submitting infrastructure, so quite reliable. */
16
+ const MESSAGE_ID_RULES = [
17
+ { client: 'gmail', regex: /@mail\.gmail\.com>?$/i, confidence: 0.95 },
18
+ { client: 'outlook-web', regex: /@[a-z0-9.-]*prod\.outlook\.com>?$/i, confidence: 0.9 },
19
+ { client: 'outlook-web', regex: /@[a-z0-9.-]*namprd[0-9]+\.prod\.outlook\.com>?$/i, confidence: 0.92 },
20
+ { client: 'yahoo', regex: /@[a-z0-9.-]*(yahoo|ymail)\.com>?$/i, confidence: 0.8 },
21
+ { client: 'proton', regex: /@(protonmail|proton\.me)/i, confidence: 0.9 },
22
+ { client: 'github', regex: /@github\.com>?$/i, confidence: 0.95 },
23
+ { client: 'atlassian', regex: /@(atlassian\.net|jira)/i, confidence: 0.85 },
24
+ { client: 'zendesk', regex: /@[a-z0-9.-]*zendesk\.com>?$/i, confidence: 0.9 },
25
+ { client: 'superhuman', regex: /@superhuman\.com>?$/i, confidence: 0.9 },
26
+ { client: 'hey', regex: /@hey\.com>?$/i, confidence: 0.85 },
27
+ { client: 'front', regex: /@(frontapp|front)\.com>?$/i, confidence: 0.85 },
28
+ { client: 'mailchimp', regex: /@mail[0-9]*\.(mcsv|mailchimpapp)\.net>?$/i, confidence: 0.9 },
29
+ { client: 'sendgrid', regex: /@(sendgrid|sendgrid\.net)/i, confidence: 0.85 },
30
+ { client: 'amazon-ses', regex: /@(email\.amazonses\.com|[a-z0-9.-]*amazonses\.com)>?$/i, confidence: 0.9 }
31
+ ];
32
+ /** X-Mailer and User-Agent values. Self declared, so slightly weaker. */
33
+ const AGENT_RULES = [
34
+ { client: 'apple-mail', regex: /\bApple\s?Mail\b/i, confidence: 0.9 },
35
+ { client: 'apple-mail-mobile', regex: /\biPhone Mail\b|\biPad Mail\b/i, confidence: 0.9 },
36
+ { client: 'outlook-desktop', regex: /Microsoft (Outlook|Office Outlook)(?! Express)/i, confidence: 0.9 },
37
+ { client: 'outlook-desktop', regex: /Microsoft Outlook Express/i, confidence: 0.9 },
38
+ { client: 'outlook-mobile', regex: /Outlook for (iOS|Android)|ms-outlook-mobile/i, confidence: 0.9 },
39
+ { client: 'thunderbird', regex: /Thunderbird|Icedove/i, confidence: 0.95 },
40
+ { client: 'mutt', regex: /\bMutt\b|\bNeoMutt\b/i, confidence: 0.95 },
41
+ { client: 'roundcube', regex: /Roundcube/i, confidence: 0.95 },
42
+ { client: 'zimbra', regex: /Zimbra/i, confidence: 0.95 },
43
+ { client: 'proton', regex: /ProtonMail|Proton Mail/i, confidence: 0.9 },
44
+ { client: 'nodemailer', regex: /nodemailer/i, confidence: 0.95 },
45
+ { client: 'phpmailer', regex: /PHPMailer|phpmailer/i, confidence: 0.95 },
46
+ { client: 'webmail-generic', regex: /SquirrelMail|Horde|Webmail|MessagingEngine/i, confidence: 0.7 },
47
+ { client: 'library-generic', regex: /JavaMail|Mailer|SwiftMailer|Sendmail|python|Zend_Mail|CakePHP/i, confidence: 0.6 }
48
+ ];
49
+ /** MIME boundary shapes that some clients use consistently. */
50
+ const BOUNDARY_RULES = [
51
+ { client: 'apple-mail', regex: /Apple-Mail[=-]/i, confidence: 0.85 },
52
+ { client: 'library-generic', regex: /----=_Part_[0-9]+_[0-9]+/, confidence: 0.6 },
53
+ { client: 'outlook-desktop', regex: /----_=_NextPart_/i, confidence: 0.7 }
54
+ ];
55
+ /** HTML structure hints. Weakest signal, used only when nothing else matched. */
56
+ const HTML_RULES = [
57
+ { client: 'gmail', regex: /class="?gmail_(quote|attr|signature)/i, confidence: 0.8 },
58
+ { client: 'outlook-web', regex: /id="?(divRplyFwdMsg|appendonsend)/i, confidence: 0.8 },
59
+ { client: 'outlook-web', regex: /class="?[^"]*elementToProof/i, confidence: 0.7 },
60
+ { client: 'outlook-desktop', regex: /class="?MsoNormal|WordSection1/i, confidence: 0.6 },
61
+ { client: 'thunderbird', regex: /moz-cite-prefix|moz-signature/i, confidence: 0.85 },
62
+ { client: 'apple-mail', regex: /Apple-interchange-newline|AppleMailSignature/i, confidence: 0.75 },
63
+ { client: 'proton', regex: /protonmail_quote/i, confidence: 0.85 },
64
+ { client: 'yahoo', regex: /yahoo_quoted/i, confidence: 0.85 },
65
+ { client: 'zimbra', regex: /id="?zwchr/i, confidence: 0.8 },
66
+ { client: 'hubspot', regex: /hubspot/i, confidence: 0.6 },
67
+ { client: 'zendesk', regex: /zendesk/i, confidence: 0.6 }
68
+ ];
69
+ function match(rules, value) {
70
+ if (!value) {
71
+ return null;
72
+ }
73
+ for (const rule of rules) {
74
+ if (rule.regex.test(value)) {
75
+ return rule;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Identify the composing client.
82
+ *
83
+ * @param headers decoded message headers
84
+ * @param html first text/html part, or null. Only the head of the document is inspected.
85
+ * @param boundary Content-Type boundary parameter of the root part, if any
86
+ */
87
+ function fingerprintClient(headers, html, boundary) {
88
+ const byMessageId = match(MESSAGE_ID_RULES, headers.messageId);
89
+ if (byMessageId) {
90
+ // A mobile Outlook or Gmail message still carries the provider Message-ID, so refine with
91
+ // the self declared agent when it disagrees on the same provider family.
92
+ const agent = `${headers.xMailer || ''} ${headers.userAgent || ''}`;
93
+ if (byMessageId.client === 'outlook-web' && /Outlook for (iOS|Android)/i.test(agent)) {
94
+ return { client: 'outlook-mobile', source: 'user-agent', confidence: 0.9 };
95
+ }
96
+ if (byMessageId.client === 'gmail' && /Android|iPhone|iPad/i.test(agent)) {
97
+ return { client: 'gmail-mobile', source: 'user-agent', confidence: 0.8 };
98
+ }
99
+ return { client: byMessageId.client, source: 'message-id', confidence: byMessageId.confidence };
100
+ }
101
+ const byMailer = match(AGENT_RULES, headers.xMailer);
102
+ if (byMailer) {
103
+ return { client: byMailer.client, source: 'x-mailer', confidence: byMailer.confidence };
104
+ }
105
+ const byAgent = match(AGENT_RULES, headers.userAgent);
106
+ if (byAgent) {
107
+ return { client: byAgent.client, source: 'user-agent', confidence: byAgent.confidence };
108
+ }
109
+ const byBoundary = match(BOUNDARY_RULES, boundary);
110
+ if (byBoundary) {
111
+ return { client: byBoundary.client, source: 'boundary', confidence: byBoundary.confidence };
112
+ }
113
+ // Only the head of the document is scanned. Client markers appear early, and a full scan of a
114
+ // multi megabyte newsletter would dominate the cost of the whole pipeline.
115
+ const byHtml = match(HTML_RULES, html ? html.slice(0, 65536) : null);
116
+ if (byHtml) {
117
+ return { client: byHtml.client, source: 'html', confidence: byHtml.confidence };
118
+ }
119
+ return { client: 'unknown', source: 'none', confidence: 0 };
120
+ }
121
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/detect/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Legal and organisational footer detection.
3
+ *
4
+ * A disclaimer is appended by a gateway or a policy engine rather than typed by the sender, so it
5
+ * is collapsed by default. Detection requires both a phrase match and a trailing position: a
6
+ * discussion that happens to mention confidentiality in the middle of a thread is ordinary content
7
+ * and must stay visible.
8
+ */
9
+ import { type Evidence } from '../segment/types';
10
+ import { type BlockFeatures } from '../segment/types';
11
+ export declare function detectDisclaimer(text: string, features: BlockFeatures): Evidence[];
12
+ //# sourceMappingURL=disclaimer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disclaimer.d.ts","sourceRoot":"","sources":["../../src/detect/disclaimer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,QAAQ,EAAE,CAYlF"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * Legal and organisational footer detection.
4
+ *
5
+ * A disclaimer is appended by a gateway or a policy engine rather than typed by the sender, so it
6
+ * is collapsed by default. Detection requires both a phrase match and a trailing position: a
7
+ * discussion that happens to mention confidentiality in the middle of a thread is ordinary content
8
+ * and must stay visible.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.detectDisclaimer = detectDisclaimer;
12
+ const patterns_1 = require("./patterns");
13
+ /** Disclaimers live at the end. Anything earlier is discussion about a disclaimer. */
14
+ const MIN_POSITION = 0.4;
15
+ function detectDisclaimer(text, features) {
16
+ if (features.position < MIN_POSITION || features.isAllBlank) {
17
+ return [];
18
+ }
19
+ const patterns = (0, patterns_1.loadPatterns)();
20
+ const found = [];
21
+ for (const pattern of patterns.disclaimerPhrases) {
22
+ if (pattern.regex.test(text)) {
23
+ found.push({ id: `disclaimer:${pattern.id}`, kind: 'disclaimer', strength: pattern.strength });
24
+ }
25
+ }
26
+ return found;
27
+ }
28
+ //# sourceMappingURL=disclaimer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disclaimer.js","sourceRoot":"","sources":["../../src/detect/disclaimer.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AASH,4CAYC;AAnBD,yCAA0C;AAI1C,sFAAsF;AACtF,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB,SAAgB,gBAAgB,CAAC,IAAY,EAAE,QAAuB;IAClE,IAAI,QAAQ,CAAC,QAAQ,GAAG,YAAY,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC1D,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,IAAA,uBAAY,GAAE,CAAC;IAChC,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAC/C,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,cAAc,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnG,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Forward marker detection.
3
+ *
4
+ * Forwarded content was included on purpose, so a forward anchor opens a region that is collapsed
5
+ * and kept, never hidden. That difference in consequence is why forward markers are detected
6
+ * separately from reply markers rather than being folded into one "quote anchor" concept.
7
+ */
8
+ import { type Evidence } from '../segment/types';
9
+ export declare function detectForwardMarker(line: string): Evidence | null;
10
+ //# sourceMappingURL=forward-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-header.d.ts","sourceRoot":"","sources":["../../src/detect/forward-header.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAsBjE"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Forward marker detection.
4
+ *
5
+ * Forwarded content was included on purpose, so a forward anchor opens a region that is collapsed
6
+ * and kept, never hidden. That difference in consequence is why forward markers are detected
7
+ * separately from reply markers rather than being folded into one "quote anchor" concept.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.detectForwardMarker = detectForwardMarker;
11
+ const patterns_1 = require("./patterns");
12
+ function detectForwardMarker(line) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed || trimmed.length > 300) {
15
+ return null;
16
+ }
17
+ const patterns = (0, patterns_1.loadPatterns)();
18
+ let best = null;
19
+ for (const pattern of patterns.forwardHeaders) {
20
+ // The bare "From:" pattern is only useful as part of a header block, which
21
+ // detectQuotedHeaderBlock handles. Treating it as a forward anchor on its own would fire on
22
+ // ordinary prose.
23
+ if (pattern.id === 'fyi_forward_header_block') {
24
+ continue;
25
+ }
26
+ if (!pattern.regex.test(trimmed)) {
27
+ continue;
28
+ }
29
+ if (!best || pattern.strength > best.strength) {
30
+ best = { id: `forward:${pattern.id}`, kind: 'forward-anchor', strength: pattern.strength };
31
+ }
32
+ }
33
+ return best;
34
+ }
35
+ //# sourceMappingURL=forward-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-header.js","sourceRoot":"","sources":["../../src/detect/forward-header.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAKH,kDAsBC;AAzBD,yCAA0C;AAG1C,SAAgB,mBAAmB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,QAAQ,GAAG,IAAA,uBAAY,GAAE,CAAC;IAChC,IAAI,IAAI,GAAoB,IAAI,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;QAC5C,2EAA2E;QAC3E,4FAA4F;QAC5F,kBAAkB;QAClB,IAAI,OAAO,CAAC,EAAE,KAAK,0BAA0B,EAAE,CAAC;YAC5C,SAAS;QACb,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,SAAS;QACb,CAAC;QACD,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5C,IAAI,GAAG,EAAE,EAAE,EAAE,WAAW,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC/F,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC"}