jxa-mail-mcp 0.1.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of jxa-mail-mcp might be problematic. Click here for more details.

@@ -291,4 +291,72 @@ const MailCore = {
291
291
  }
292
292
  return null;
293
293
  },
294
+
295
+ /**
296
+ * Fast body search - optimized for long text.
297
+ * Uses substring match first, then trigram-only (skips expensive Levenshtein).
298
+ *
299
+ * @param {string} query - Search query
300
+ * @param {string} body - Email body content
301
+ * @param {number} maxChars - Max characters to search (default 2000)
302
+ * @returns {object|null} {score, matched, tier} or null if no match
303
+ */
304
+ fuzzyMatchBody(query, body, maxChars = 2000) {
305
+ const q = (query || "").toLowerCase().trim();
306
+ const b = (body || "").toLowerCase().substring(0, maxChars);
307
+
308
+ if (!q || !b) return null;
309
+
310
+ // Tier 1: Exact substring match (fastest, best result)
311
+ const exactIndex = b.indexOf(q);
312
+ if (exactIndex !== -1) {
313
+ // Extract context around the match
314
+ const start = Math.max(0, exactIndex - 20);
315
+ const end = Math.min(b.length, exactIndex + q.length + 20);
316
+ const context = b.substring(start, end).trim();
317
+ return { score: 0.95, matched: context, tier: "exact" };
318
+ }
319
+
320
+ // Tier 2: Word-level trigram matching (no Levenshtein)
321
+ // Find words that share enough trigrams with query
322
+ const queryTrigrams = this.trigrams(q);
323
+ const words = b.split(/\s+/);
324
+ let bestSim = 0;
325
+ let bestWord = null;
326
+
327
+ for (const word of words) {
328
+ if (word.length < 2) continue;
329
+ const wordTrigrams = this.trigrams(word);
330
+ const sim = this.trigramSimilarity(queryTrigrams, wordTrigrams);
331
+ if (sim > bestSim) {
332
+ bestSim = sim;
333
+ bestWord = word;
334
+ }
335
+ }
336
+
337
+ // Also check multi-word phrases for multi-word queries
338
+ const queryWords = q.split(/\s+/).length;
339
+ if (queryWords > 1 && words.length >= queryWords) {
340
+ for (let i = 0; i <= words.length - queryWords; i++) {
341
+ const phrase = words.slice(i, i + queryWords).join(" ");
342
+ const phraseTrigrams = this.trigrams(phrase);
343
+ const sim = this.trigramSimilarity(queryTrigrams, phraseTrigrams);
344
+ if (sim > bestSim) {
345
+ bestSim = sim;
346
+ bestWord = phrase;
347
+ }
348
+ }
349
+ }
350
+
351
+ // Require higher threshold for trigram-only matching
352
+ if (bestSim >= 0.25) {
353
+ return {
354
+ score: Math.round(bestSim * 0.85 * 100) / 100,
355
+ matched: bestWord,
356
+ tier: "trigram",
357
+ };
358
+ }
359
+
360
+ return null;
361
+ },
294
362
  };