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.
- jxa_mail_mcp/__init__.py +14 -6
- jxa_mail_mcp/cli.py +358 -0
- jxa_mail_mcp/config.py +52 -0
- jxa_mail_mcp/executor.py +178 -4
- jxa_mail_mcp/index/__init__.py +14 -0
- jxa_mail_mcp/index/disk.py +485 -0
- jxa_mail_mcp/index/manager.py +458 -0
- jxa_mail_mcp/index/schema.py +277 -0
- jxa_mail_mcp/index/search.py +331 -0
- jxa_mail_mcp/index/sync.py +305 -0
- jxa_mail_mcp/index/watcher.py +341 -0
- jxa_mail_mcp/jxa/mail_core.js +68 -0
- jxa_mail_mcp/server.py +546 -85
- jxa_mail_mcp-0.3.0.dist-info/METADATA +355 -0
- jxa_mail_mcp-0.3.0.dist-info/RECORD +20 -0
- jxa_mail_mcp-0.1.0.dist-info/METADATA +0 -223
- jxa_mail_mcp-0.1.0.dist-info/RECORD +0 -12
- {jxa_mail_mcp-0.1.0.dist-info → jxa_mail_mcp-0.3.0.dist-info}/WHEEL +0 -0
- {jxa_mail_mcp-0.1.0.dist-info → jxa_mail_mcp-0.3.0.dist-info}/entry_points.txt +0 -0
- {jxa_mail_mcp-0.1.0.dist-info → jxa_mail_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
jxa_mail_mcp/jxa/mail_core.js
CHANGED
|
@@ -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
|
};
|