imsg-mcp 1.6.2 → 1.7.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.
- package/CHANGELOG.md +7 -0
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/{imessage-db-Dpxn-q2z.js → imessage-db-Cid38QGT.js} +2 -2
- package/dist/{imessage-db-Dpxn-q2z.js.map → imessage-db-Cid38QGT.js.map} +1 -1
- package/dist/index.js +63 -10
- package/dist/index.js.map +1 -1
- package/dist/{meta-GGXPVODc.js → meta-Lpz_PHJ5.js} +2 -2
- package/dist/{meta-GGXPVODc.js.map → meta-Lpz_PHJ5.js.map} +1 -1
- package/dist/{setup-CysR5gId.js → setup-CgLVaoGu.js} +2 -2
- package/dist/{setup-CysR5gId.js.map → setup-CgLVaoGu.js.map} +1 -1
- package/dist/{shutdown-Vh0gjb6m.js → shutdown-CQ9wzrxA.js} +65 -50
- package/dist/shutdown-CQ9wzrxA.js.map +1 -0
- package/dist/tui.js +3 -3
- package/dist/{watchdog-DZ92dXs_.js → watchdog-Cl5PoHhW.js} +2 -2
- package/dist/{watchdog-DZ92dXs_.js.map → watchdog-Cl5PoHhW.js.map} +1 -1
- package/package.json +1 -1
- package/dist/shutdown-Vh0gjb6m.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, execFile } from "node:child_process";
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync, chmodSync, statSync, mkdtempSync, rmSync } from "node:fs";
|
|
4
|
-
import { join, dirname, isAbsolute } from "node:path";
|
|
4
|
+
import { join, dirname, basename, isAbsolute } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { g as getHumansDirPath, s as sanitizeSlugPart, c as checkLocalAccess, f as formatAccessReport,
|
|
9
|
+
import { g as getHumansDirPath, s as sanitizeSlugPart, a as getTranscribeCloudConfig, c as checkLocalAccess, f as formatAccessReport, b as shutdown, d as getImsgDbPath, e as getContactsDbPaths, h as getSlugsDbPath, i as appendLog, j as getFileLogLines, k as getLogDirectory, p as perf, l as checkMessagesAvailable, m as sendToChat, n as sendToChatId, o as sendMessageReliable, q as sendMessageAlt, r as sendAttachment, t as checkImessageAvailability, u as computeRelationshipLeaderboard, v as dispatchAnalytic, w as installShutdownHandlers, x as registerCleanup, y as enableStdinEofDetection, z as enableOrphanWatchdog, A as logStartup, B as info, C as startHeapMonitor, D as getLogs, E as setLastSendError, F as logShutdown, G as stopHeapMonitor, H as getLogFilePath, I as getLastSendError } from "./shutdown-CQ9wzrxA.js";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import Database from "better-sqlite3";
|
|
12
|
-
import { A as APP_VERSION, a as APP_NAME, r as renderAnalyticText } from "./meta-
|
|
13
|
-
import { n as normalizedPhoneVariants, I as IMessageDB, r as rankFuzzy, h as hasNativeModule } from "./imessage-db-
|
|
12
|
+
import { A as APP_VERSION, a as APP_NAME, r as renderAnalyticText } from "./meta-Lpz_PHJ5.js";
|
|
13
|
+
import { n as normalizedPhoneVariants, I as IMessageDB, r as rankFuzzy, h as hasNativeModule } from "./imessage-db-Cid38QGT.js";
|
|
14
14
|
import { parseUserDate } from "./date-parse-DJXMfq3a.js";
|
|
15
15
|
import { streamExport } from "./exportStream-DgPVRZTZ.js";
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { randomUUID } from "node:crypto";
|
|
18
|
-
import { m as minMessageId, r as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog, n as noteActivity, a as readWatchdogState } from "./watchdog-
|
|
18
|
+
import { m as minMessageId, r as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog, n as noteActivity, a as readWatchdogState } from "./watchdog-Cl5PoHhW.js";
|
|
19
19
|
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
20
20
|
let dbPath = join(homedir(), ".imsg-mcp", "analytics-cache.db");
|
|
21
21
|
let db = null;
|
|
@@ -1948,7 +1948,10 @@ const GetAttachmentOutputSchema = z.object({
|
|
|
1948
1948
|
base64: z.string().optional(),
|
|
1949
1949
|
converted: z.string().optional().describe("Set when source was HEIC and we converted to PNG."),
|
|
1950
1950
|
mediaInfo: z.string().optional().describe("Compact duration/dimensions/codec summary for audio/video (via mdls)."),
|
|
1951
|
-
transcript: z.string().optional().describe(
|
|
1951
|
+
transcript: z.string().optional().describe(
|
|
1952
|
+
"Audio transcript. Local on-device transcriber (hear/yap/whisper-cli) when installed; otherwise the opt-in cloud fallback (IMSG_TRANSCRIBE_PROVIDER + IMSG_TRANSCRIBE_API_KEY) if configured."
|
|
1953
|
+
),
|
|
1954
|
+
transcriptSource: z.enum(["local", "cloud"]).optional().describe("Where the transcript came from: on-device (local) or the opt-in cloud provider."),
|
|
1952
1955
|
imageBlockIncluded: z.boolean().optional().describe("True when the tool result carries a real MCP image content block.")
|
|
1953
1956
|
});
|
|
1954
1957
|
const noArgsSchema = { type: "object", properties: {} };
|
|
@@ -2336,7 +2339,7 @@ const TOOLS = [
|
|
|
2336
2339
|
},
|
|
2337
2340
|
{
|
|
2338
2341
|
name: "get_attachment",
|
|
2339
|
-
description: "Fetch an attachment by ROWID (from search_attachments or a message's attachments[].rowId). IMAGES: returned as a real MCP image content block (downscaled ≤1536px, HEIC auto-converted) so the model can SEE it — plus full-size base64 in structuredContent when ≤ inlineMaxBytes (default 5MB). VIDEO: QuickLook poster frame as an image block + duration/resolution metadata + file path. AUDIO (voice memos): metadata + file path, and a transcript when a transcriber (hear, yap, or whisper-cli) is installed on PATH.",
|
|
2342
|
+
description: "Fetch an attachment by ROWID (from search_attachments or a message's attachments[].rowId). IMAGES: returned as a real MCP image content block (downscaled ≤1536px, HEIC auto-converted) so the model can SEE it — plus full-size base64 in structuredContent when ≤ inlineMaxBytes (default 5MB). VIDEO: QuickLook poster frame as an image block + duration/resolution metadata + file path. AUDIO (voice memos): metadata + file path, and a transcript when a local transcriber (hear, yap, or whisper-cli) is installed on PATH, or via an opt-in OpenAI-compatible cloud endpoint (set IMSG_TRANSCRIBE_PROVIDER + IMSG_TRANSCRIBE_API_KEY — audio leaves the device; local always preferred).",
|
|
2340
2343
|
annotations: annotations.read,
|
|
2341
2344
|
inputSchema: {
|
|
2342
2345
|
type: "object",
|
|
@@ -2568,6 +2571,42 @@ function transcribeAudio(path, timeoutMs = 6e4) {
|
|
|
2568
2571
|
return null;
|
|
2569
2572
|
}
|
|
2570
2573
|
}
|
|
2574
|
+
async function transcribeAudioCloud(path, config, opts = {}) {
|
|
2575
|
+
if (!existsSync(path)) return null;
|
|
2576
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
2577
|
+
const controller = new AbortController();
|
|
2578
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 6e4);
|
|
2579
|
+
try {
|
|
2580
|
+
const form = new FormData();
|
|
2581
|
+
form.append("file", new Blob([readFileSync(path)]), basename(path));
|
|
2582
|
+
form.append("model", config.model);
|
|
2583
|
+
form.append("response_format", "text");
|
|
2584
|
+
const res = await doFetch(`${config.baseUrl}/audio/transcriptions`, {
|
|
2585
|
+
method: "POST",
|
|
2586
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
2587
|
+
body: form,
|
|
2588
|
+
signal: controller.signal
|
|
2589
|
+
});
|
|
2590
|
+
if (!res.ok) return null;
|
|
2591
|
+
const text = (await res.text()).trim();
|
|
2592
|
+
return text.length > 0 ? text : null;
|
|
2593
|
+
} catch {
|
|
2594
|
+
return null;
|
|
2595
|
+
} finally {
|
|
2596
|
+
clearTimeout(timer);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
async function transcribeAudioBest(path, opts = {}) {
|
|
2600
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
2601
|
+
const local = transcribeAudio(path, timeoutMs);
|
|
2602
|
+
if (local) return { transcript: local, source: "local" };
|
|
2603
|
+
const cloud = opts.cloudConfig !== void 0 ? opts.cloudConfig : getTranscribeCloudConfig();
|
|
2604
|
+
if (cloud) {
|
|
2605
|
+
const t = await transcribeAudioCloud(path, cloud, { timeoutMs, fetchImpl: opts.fetchImpl });
|
|
2606
|
+
if (t) return { transcript: t, source: "cloud" };
|
|
2607
|
+
}
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2571
2610
|
const INSTRUCTIONS_UUID = randomUUID();
|
|
2572
2611
|
function wrapUntrusted(text) {
|
|
2573
2612
|
if (text == null || text === "") return "";
|
|
@@ -3919,6 +3958,7 @@ ${formatted}`, {
|
|
|
3919
3958
|
let finalPath = resolvedPath;
|
|
3920
3959
|
let mediaInfo;
|
|
3921
3960
|
let transcript;
|
|
3961
|
+
let transcriptSource;
|
|
3922
3962
|
let imageBlock = null;
|
|
3923
3963
|
if (isImage) {
|
|
3924
3964
|
imageBlock = imageBlockFromFile(resolvedPath);
|
|
@@ -3950,7 +3990,11 @@ ${formatted}`, {
|
|
|
3950
3990
|
} else if (isAudio) {
|
|
3951
3991
|
mediaInfo = mediaMetadata(resolvedPath) ?? void 0;
|
|
3952
3992
|
if (sizeBytes <= TRANSCRIBE_MAX_BYTES) {
|
|
3953
|
-
|
|
3993
|
+
const t = await transcribeAudioBest(resolvedPath);
|
|
3994
|
+
if (t) {
|
|
3995
|
+
transcript = t.transcript;
|
|
3996
|
+
transcriptSource = t.source;
|
|
3997
|
+
}
|
|
3954
3998
|
}
|
|
3955
3999
|
}
|
|
3956
4000
|
const lines = [];
|
|
@@ -3963,10 +4007,18 @@ ${formatted}`, {
|
|
|
3963
4007
|
} else if (isAudio) {
|
|
3964
4008
|
lines.push(`Audio attachment ${rowId} (${sizeBytes}B${mediaInfo ? `, ${mediaInfo}` : ""}).`);
|
|
3965
4009
|
if (transcript) {
|
|
3966
|
-
lines.push(
|
|
4010
|
+
lines.push(
|
|
4011
|
+
"",
|
|
4012
|
+
transcriptSource === "cloud" ? "Transcript (via cloud provider):" : "Transcript:",
|
|
4013
|
+
wrapUntrusted(sanitizeUserText(transcript) ?? "")
|
|
4014
|
+
);
|
|
4015
|
+
} else if (detectTranscriber()) {
|
|
4016
|
+
lines.push("Transcription produced no text.");
|
|
4017
|
+
} else if (getTranscribeCloudConfig()) {
|
|
4018
|
+
lines.push("Cloud transcription produced no text.");
|
|
3967
4019
|
} else {
|
|
3968
4020
|
lines.push(
|
|
3969
|
-
|
|
4021
|
+
"No transcriber installed — `brew install yap` (macOS 26+) or `brew install sveinbjornt/hear/hear` enables on-device transcription; or set IMSG_TRANSCRIBE_PROVIDER + IMSG_TRANSCRIBE_API_KEY for an OpenAI-compatible cloud fallback (audio leaves this device)."
|
|
3970
4022
|
);
|
|
3971
4023
|
}
|
|
3972
4024
|
lines.push(`Path: ${resolvedPath}`);
|
|
@@ -3998,6 +4050,7 @@ ${formatted}`, {
|
|
|
3998
4050
|
converted: convertedNote,
|
|
3999
4051
|
mediaInfo,
|
|
4000
4052
|
transcript,
|
|
4053
|
+
transcriptSource,
|
|
4001
4054
|
imageBlockIncluded: imageBlock != null
|
|
4002
4055
|
}
|
|
4003
4056
|
};
|