imsg-mcp 1.3.2 → 1.5.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 +44 -0
- package/README.md +3 -2
- package/dist/cli.js +126 -20
- package/dist/cli.js.map +1 -1
- package/dist/{imessage-db-Cz8MIIpG.js → imessage-db-BrHJPOq7.js} +147 -19
- package/dist/imessage-db-BrHJPOq7.js.map +1 -0
- package/dist/index.js +847 -73
- package/dist/index.js.map +1 -1
- package/dist/meta-E4VgVmPs.js +165 -0
- package/dist/meta-E4VgVmPs.js.map +1 -0
- package/dist/{setup-BuXuKAHA.js → setup-CysR5gId.js} +2 -2
- package/dist/{setup-BuXuKAHA.js.map → setup-CysR5gId.js.map} +1 -1
- package/dist/{shutdown-pPAPSnk3.js → shutdown-Vh0gjb6m.js} +434 -48
- package/dist/shutdown-Vh0gjb6m.js.map +1 -0
- package/dist/tui.js +226 -64
- package/dist/tui.js.map +1 -1
- package/dist/{watchdog-Cbgm050b.js → watchdog-B3T-dQT-.js} +21 -266
- package/dist/watchdog-B3T-dQT-.js.map +1 -0
- package/native/imsg-native.darwin-arm64.node +0 -0
- package/package.json +1 -1
- package/skills/humans/SKILL.md +136 -0
- package/skills/imsg-mcp/SKILL.md +29 -0
- package/dist/imessage-db-Cz8MIIpG.js.map +0 -1
- package/dist/meta-DzE7CSrZ.js +0 -7
- package/dist/meta-DzE7CSrZ.js.map +0 -1
- package/dist/shutdown-pPAPSnk3.js.map +0 -1
- package/dist/watchdog-Cbgm050b.js.map +0 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
function humanDuration(ms) {
|
|
2
|
+
if (!Number.isFinite(ms) || ms < 0) return "—";
|
|
3
|
+
const s = Math.round(ms / 1e3);
|
|
4
|
+
if (s < 60) return `${s}s`;
|
|
5
|
+
const m = Math.round(s / 60);
|
|
6
|
+
if (m < 60) return `${m}m`;
|
|
7
|
+
const h = Math.round(m / 60);
|
|
8
|
+
if (h < 24) return `${h}h`;
|
|
9
|
+
return `${Math.round(h / 24)}d`;
|
|
10
|
+
}
|
|
11
|
+
const TOP = 20;
|
|
12
|
+
function renderStreaks(rows) {
|
|
13
|
+
if (rows.length === 0) return "(no streaks in window)";
|
|
14
|
+
return rows.slice(0, TOP).map((r, i) => {
|
|
15
|
+
const span = r.longestStreakStart && r.longestStreakEnd ? ` (${r.longestStreakStart}→${r.longestStreakEnd})` : "";
|
|
16
|
+
const cur = r.currentStreakDays > 0 ? `, current ${r.currentStreakDays}d` : "";
|
|
17
|
+
return `${String(i + 1).padStart(2)}. ${r.contact} — longest ${r.longestStreakDays}d${span}${cur}`;
|
|
18
|
+
}).join("\n");
|
|
19
|
+
}
|
|
20
|
+
function renderDoubleTexts(rows) {
|
|
21
|
+
if (rows.length === 0) return "(no double-texts in window)";
|
|
22
|
+
return rows.slice(0, TOP).map(
|
|
23
|
+
(r, i) => `${String(i + 1).padStart(2)}. ${r.contact} — you ${r.doubleTextsFromMe}×, them ${r.doubleTextsFromThem}×`
|
|
24
|
+
).join("\n");
|
|
25
|
+
}
|
|
26
|
+
function renderResponseTimes(rows) {
|
|
27
|
+
if (rows.length === 0) return "(no response-time data in window)";
|
|
28
|
+
return rows.slice(0, TOP).map(
|
|
29
|
+
(r, i) => `${String(i + 1).padStart(2)}. ${r.contact} — median ${humanDuration(r.medianMs)}, p95 ${humanDuration(r.p95Ms)}, mean ${humanDuration(r.meanMs)} (n=${r.count})`
|
|
30
|
+
).join("\n");
|
|
31
|
+
}
|
|
32
|
+
const DOW = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
33
|
+
const HEAT = " ░▒▓█";
|
|
34
|
+
function renderHeatmap(result) {
|
|
35
|
+
if (result.total === 0) return "(no activity in window)";
|
|
36
|
+
const max = Math.max(...result.grid.flat());
|
|
37
|
+
const header = ` ${Array.from({ length: 24 }, (_, h) => String(h % 10)).join("")}`;
|
|
38
|
+
const rows = result.grid.map((row, dow) => {
|
|
39
|
+
const cells = row.map((v) => {
|
|
40
|
+
if (v === 0) return HEAT[0];
|
|
41
|
+
const level = Math.min(HEAT.length - 1, 1 + Math.floor(v / max * (HEAT.length - 2)));
|
|
42
|
+
return HEAT[level];
|
|
43
|
+
}).join("");
|
|
44
|
+
return `${DOW[dow]} ${cells}`;
|
|
45
|
+
});
|
|
46
|
+
return `${header}
|
|
47
|
+
${rows.join("\n")}
|
|
48
|
+
(${result.total} messages; each column = hour 0–23)`;
|
|
49
|
+
}
|
|
50
|
+
function renderTapbacks(rows) {
|
|
51
|
+
if (rows.length === 0) return "(no tapbacks in window)";
|
|
52
|
+
return rows.slice(0, TOP).map((r, i) => {
|
|
53
|
+
const parts = [
|
|
54
|
+
r.heart && `❤️${r.heart}`,
|
|
55
|
+
r.thumbsUp && `👍${r.thumbsUp}`,
|
|
56
|
+
r.thumbsDown && `👎${r.thumbsDown}`,
|
|
57
|
+
r.haha && `😂${r.haha}`,
|
|
58
|
+
r.exclaim && `‼️${r.exclaim}`,
|
|
59
|
+
r.question && `❓${r.question}`,
|
|
60
|
+
r.emoji && `🙂${r.emoji}`
|
|
61
|
+
].filter(Boolean);
|
|
62
|
+
return `${String(i + 1).padStart(2)}. ${r.contact} — ${parts.join(" ")} (total ${r.total})`;
|
|
63
|
+
}).join("\n");
|
|
64
|
+
}
|
|
65
|
+
function renderWrapped(w) {
|
|
66
|
+
const lines = [
|
|
67
|
+
`Window: ${w.windowStart} → ${w.windowEnd}`,
|
|
68
|
+
`Sent: ${w.totalSent.toLocaleString()} Received: ${w.totalReceived.toLocaleString()} Reactions: ${w.totalReactions.toLocaleString()}`,
|
|
69
|
+
w.peakDay ? `Peak day: ${w.peakDay.date} (${w.peakDay.count} messages)` : "Peak day: —",
|
|
70
|
+
w.longestStreakContact ? `Longest streak: ${w.longestStreakDays}d with ${w.longestStreakContact}` : `Longest streak: ${w.longestStreakDays}d`,
|
|
71
|
+
"",
|
|
72
|
+
"Top contacts:",
|
|
73
|
+
...w.topContacts.slice(0, 10).map(
|
|
74
|
+
(c, i) => `${String(i + 1).padStart(2)}. ${c.contact} — ${c.total} (↑${c.sent} / ↓${c.received})`
|
|
75
|
+
)
|
|
76
|
+
];
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
function renderLeaderboard(rows) {
|
|
80
|
+
if (rows.length === 0) return "(no ranked relationships in window)";
|
|
81
|
+
const max = rows[0]?.score || 0;
|
|
82
|
+
return rows.slice(0, TOP).map((r, i) => {
|
|
83
|
+
const rel = max > 0 ? Math.round(r.score / max * 100) : 0;
|
|
84
|
+
return `${String(i + 1).padStart(2)}. ${r.contact} — ${r.total} msgs, ${Math.round(
|
|
85
|
+
r.reciprocity * 100
|
|
86
|
+
)}% reciprocity, last ${Math.round(r.daysSinceLast)}d ago (score ${rel})`;
|
|
87
|
+
}).join("\n");
|
|
88
|
+
}
|
|
89
|
+
function renderAnalyticText(type, data) {
|
|
90
|
+
switch (type) {
|
|
91
|
+
case "messaging_streaks":
|
|
92
|
+
return renderStreaks(
|
|
93
|
+
data.streaks ?? data
|
|
94
|
+
);
|
|
95
|
+
case "double_texts":
|
|
96
|
+
return renderDoubleTexts(
|
|
97
|
+
data.doubleTexts ?? data
|
|
98
|
+
);
|
|
99
|
+
case "response_time_stats":
|
|
100
|
+
return renderResponseTimes(
|
|
101
|
+
data.responseTimes ?? data
|
|
102
|
+
);
|
|
103
|
+
case "daily_heatmap":
|
|
104
|
+
return renderHeatmap(data);
|
|
105
|
+
case "tapback_summary":
|
|
106
|
+
return renderTapbacks(
|
|
107
|
+
data.tapbacks ?? data
|
|
108
|
+
);
|
|
109
|
+
case "year_in_review_wrapped":
|
|
110
|
+
return renderWrapped(data);
|
|
111
|
+
case "relationship_leaderboard":
|
|
112
|
+
return renderLeaderboard(data.leaderboard ?? []);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function toYaml(value, indent = 0) {
|
|
116
|
+
const pad = " ".repeat(indent);
|
|
117
|
+
if (value === null || value === void 0) return "null";
|
|
118
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
119
|
+
if (typeof value === "string") return yamlScalar(value);
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
if (value.length === 0) return "[]";
|
|
122
|
+
return value.map((item) => {
|
|
123
|
+
const rendered = toYaml(item, indent + 1);
|
|
124
|
+
if (isComposite(item)) {
|
|
125
|
+
return `${pad}-
|
|
126
|
+
${rendered}`;
|
|
127
|
+
}
|
|
128
|
+
return `${pad}- ${rendered}`;
|
|
129
|
+
}).join("\n");
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === "object") {
|
|
132
|
+
const entries = Object.entries(value);
|
|
133
|
+
if (entries.length === 0) return "{}";
|
|
134
|
+
return entries.map(([k, v]) => {
|
|
135
|
+
if (isComposite(v)) {
|
|
136
|
+
return `${pad}${k}:
|
|
137
|
+
${toYaml(v, indent + 1)}`;
|
|
138
|
+
}
|
|
139
|
+
return `${pad}${k}: ${toYaml(v, indent)}`;
|
|
140
|
+
}).join("\n");
|
|
141
|
+
}
|
|
142
|
+
return yamlScalar(String(value));
|
|
143
|
+
}
|
|
144
|
+
function isComposite(v) {
|
|
145
|
+
return typeof v === "object" && v !== null && (Array.isArray(v) ? v.length > 0 : true);
|
|
146
|
+
}
|
|
147
|
+
function yamlScalar(s) {
|
|
148
|
+
const needsQuote = s === "" || /^\s|\s$/.test(s) || // leading/trailing whitespace
|
|
149
|
+
/[\n"\\]/.test(s) || // control / quote / backslash
|
|
150
|
+
/^[-?:,[\]{}&*!|>%@`#'"]/.test(s) || // starts with a YAML indicator
|
|
151
|
+
/:(\s|$)|\s#/.test(s) || // ": " (mapping) or " #" (comment) in structural position
|
|
152
|
+
/^[+-]?(\d|\.\d)/.test(s) || // number-ish, incl. "+61…" phone numbers
|
|
153
|
+
/^(true|false|yes|no|on|off|null|~)$/i.test(s) || // YAML keywords
|
|
154
|
+
/^[+-]?\.?(inf|nan)$/i.test(s);
|
|
155
|
+
return needsQuote ? `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"` : s;
|
|
156
|
+
}
|
|
157
|
+
const APP_NAME = "imsg-mcp";
|
|
158
|
+
const APP_VERSION = "1.5.0";
|
|
159
|
+
export {
|
|
160
|
+
APP_VERSION as A,
|
|
161
|
+
APP_NAME as a,
|
|
162
|
+
renderAnalyticText as r,
|
|
163
|
+
toYaml as t
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=meta-E4VgVmPs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-E4VgVmPs.js","sources":["../src/analytics-render.ts","../src/meta.ts"],"sourcesContent":["/**\n * Human-readable text rendering for every chat_analytics type, shared by the\n * MCP tool output and the CLI/console. Keeping it in one place means the agent\n * (via the tool's text block) and a person (via `imsg analytics …`) see the\n * same summary, and adding an analytic only needs one renderer.\n */\n\nimport type {\n AnalyticType,\n DoubleTextResult,\n HeatmapResult,\n RelationshipScore,\n ResponseTimeStats,\n StreakResult,\n TapbackResult,\n WrappedResult,\n} from \"./analytics.js\";\n\n/** ms → compact human duration (e.g. 4m, 2h, 3d). */\nfunction humanDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"—\";\n const s = Math.round(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m`;\n const h = Math.round(m / 60);\n if (h < 24) return `${h}h`;\n return `${Math.round(h / 24)}d`;\n}\n\nconst TOP = 20;\n\nfunction renderStreaks(rows: StreakResult[]): string {\n if (rows.length === 0) return \"(no streaks in window)\";\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const span =\n r.longestStreakStart && r.longestStreakEnd\n ? ` (${r.longestStreakStart}→${r.longestStreakEnd})`\n : \"\";\n const cur = r.currentStreakDays > 0 ? `, current ${r.currentStreakDays}d` : \"\";\n return `${String(i + 1).padStart(2)}. ${r.contact} — longest ${r.longestStreakDays}d${span}${cur}`;\n })\n .join(\"\\n\");\n}\n\nfunction renderDoubleTexts(rows: DoubleTextResult[]): string {\n if (rows.length === 0) return \"(no double-texts in window)\";\n return rows\n .slice(0, TOP)\n .map(\n (r, i) =>\n `${String(i + 1).padStart(2)}. ${r.contact} — you ${r.doubleTextsFromMe}×, them ${r.doubleTextsFromThem}×`,\n )\n .join(\"\\n\");\n}\n\nfunction renderResponseTimes(rows: ResponseTimeStats[]): string {\n if (rows.length === 0) return \"(no response-time data in window)\";\n return rows\n .slice(0, TOP)\n .map(\n (r, i) =>\n `${String(i + 1).padStart(2)}. ${r.contact} — median ${humanDuration(r.medianMs)}, p95 ${humanDuration(r.p95Ms)}, mean ${humanDuration(r.meanMs)} (n=${r.count})`,\n )\n .join(\"\\n\");\n}\n\nconst DOW = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst HEAT = \" ░▒▓█\";\n\nfunction renderHeatmap(result: HeatmapResult): string {\n if (result.total === 0) return \"(no activity in window)\";\n const max = Math.max(...result.grid.flat());\n const header = ` ${Array.from({ length: 24 }, (_, h) => String(h % 10)).join(\"\")}`;\n const rows = result.grid.map((row, dow) => {\n const cells = row\n .map((v) => {\n if (v === 0) return HEAT[0];\n const level = Math.min(HEAT.length - 1, 1 + Math.floor((v / max) * (HEAT.length - 2)));\n return HEAT[level];\n })\n .join(\"\");\n return `${DOW[dow]} ${cells}`;\n });\n return `${header}\\n${rows.join(\"\\n\")}\\n(${result.total} messages; each column = hour 0–23)`;\n}\n\nfunction renderTapbacks(rows: TapbackResult[]): string {\n if (rows.length === 0) return \"(no tapbacks in window)\";\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const parts = [\n r.heart && `❤️${r.heart}`,\n r.thumbsUp && `👍${r.thumbsUp}`,\n r.thumbsDown && `👎${r.thumbsDown}`,\n r.haha && `😂${r.haha}`,\n r.exclaim && `‼️${r.exclaim}`,\n r.question && `❓${r.question}`,\n r.emoji && `🙂${r.emoji}`,\n ].filter(Boolean);\n return `${String(i + 1).padStart(2)}. ${r.contact} — ${parts.join(\" \")} (total ${r.total})`;\n })\n .join(\"\\n\");\n}\n\nfunction renderWrapped(w: WrappedResult): string {\n const lines = [\n `Window: ${w.windowStart} → ${w.windowEnd}`,\n `Sent: ${w.totalSent.toLocaleString()} Received: ${w.totalReceived.toLocaleString()} Reactions: ${w.totalReactions.toLocaleString()}`,\n w.peakDay ? `Peak day: ${w.peakDay.date} (${w.peakDay.count} messages)` : \"Peak day: —\",\n w.longestStreakContact\n ? `Longest streak: ${w.longestStreakDays}d with ${w.longestStreakContact}`\n : `Longest streak: ${w.longestStreakDays}d`,\n \"\",\n \"Top contacts:\",\n ...w.topContacts\n .slice(0, 10)\n .map(\n (c, i) =>\n `${String(i + 1).padStart(2)}. ${c.contact} — ${c.total} (↑${c.sent} / ↓${c.received})`,\n ),\n ];\n return lines.join(\"\\n\");\n}\n\nfunction renderLeaderboard(rows: RelationshipScore[]): string {\n if (rows.length === 0) return \"(no ranked relationships in window)\";\n const max = rows[0]?.score || 0;\n return rows\n .slice(0, TOP)\n .map((r, i) => {\n const rel = max > 0 ? Math.round((r.score / max) * 100) : 0;\n return `${String(i + 1).padStart(2)}. ${r.contact} — ${r.total} msgs, ${Math.round(\n r.reciprocity * 100,\n )}% reciprocity, last ${Math.round(r.daysSinceLast)}d ago (score ${rel})`;\n })\n .join(\"\\n\");\n}\n\n/** Render any analytic's `data` payload as human-readable text. */\nexport function renderAnalyticText(type: AnalyticType, data: unknown): string {\n switch (type) {\n case \"messaging_streaks\":\n return renderStreaks(\n (data as { streaks?: StreakResult[] }).streaks ?? (data as StreakResult[]),\n );\n case \"double_texts\":\n return renderDoubleTexts(\n (data as { doubleTexts?: DoubleTextResult[] }).doubleTexts ?? (data as DoubleTextResult[]),\n );\n case \"response_time_stats\":\n return renderResponseTimes(\n (data as { responseTimes?: ResponseTimeStats[] }).responseTimes ??\n (data as ResponseTimeStats[]),\n );\n case \"daily_heatmap\":\n return renderHeatmap(data as HeatmapResult);\n case \"tapback_summary\":\n return renderTapbacks(\n (data as { tapbacks?: TapbackResult[] }).tapbacks ?? (data as TapbackResult[]),\n );\n case \"year_in_review_wrapped\":\n return renderWrapped(data as WrappedResult);\n case \"relationship_leaderboard\":\n return renderLeaderboard((data as { leaderboard?: RelationshipScore[] }).leaderboard ?? []);\n }\n}\n\n/**\n * Minimal YAML serializer for analytic output (`--yaml`). Zero-dep on purpose;\n * handles the shapes analytics produce — nested objects, arrays, strings,\n * numbers, booleans, null. Not a general-purpose emitter.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n const pad = \" \".repeat(indent);\n if (value === null || value === undefined) return \"null\";\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (typeof value === \"string\") return yamlScalar(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return \"[]\";\n return value\n .map((item) => {\n const rendered = toYaml(item, indent + 1);\n if (isComposite(item)) {\n // Nested block: put the first key inline after \"- \", rest indented.\n return `${pad}-\\n${rendered}`;\n }\n return `${pad}- ${rendered}`;\n })\n .join(\"\\n\");\n }\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>);\n if (entries.length === 0) return \"{}\";\n return entries\n .map(([k, v]) => {\n if (isComposite(v)) {\n return `${pad}${k}:\\n${toYaml(v, indent + 1)}`;\n }\n return `${pad}${k}: ${toYaml(v, indent)}`;\n })\n .join(\"\\n\");\n }\n return yamlScalar(String(value));\n}\n\nfunction isComposite(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && (Array.isArray(v) ? v.length > 0 : true);\n}\n\nfunction yamlScalar(s: string): string {\n // Conservatively quote any string a YAML parser could misread as a non-string.\n // Critically this includes phone numbers (\"+15550000119\" would otherwise\n // parse as the integer 15550000119, dropping the \"+\") and keyword-ish values.\n const needsQuote =\n s === \"\" ||\n /^\\s|\\s$/.test(s) || // leading/trailing whitespace\n /[\\n\"\\\\]/.test(s) || // control / quote / backslash\n /^[-?:,[\\]{}&*!|>%@`#'\"]/.test(s) || // starts with a YAML indicator\n /:(\\s|$)|\\s#/.test(s) || // \": \" (mapping) or \" #\" (comment) in structural position\n /^[+-]?(\\d|\\.\\d)/.test(s) || // number-ish, incl. \"+61…\" phone numbers\n /^(true|false|yes|no|on|off|null|~)$/i.test(s) || // YAML keywords\n /^[+-]?\\.?(inf|nan)$/i.test(s);\n return needsQuote\n ? `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\")}\"`\n : s;\n}\n","// Injected by Vite `define` from package.json at build/test time.\ndeclare const __APP_VERSION__: string;\n\nexport const APP_NAME = \"imsg-mcp\";\n// Fallback covers running un-built source without Vite (e.g. plain tsx).\nexport const APP_VERSION = typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : \"0.0.0-dev\";\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, existsSync, copyFileSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import Database from "better-sqlite3";
|
|
3
|
-
import {
|
|
3
|
+
import { b as getImsgDbPath, d as getContactsDbPaths, e as getSlugsDbPath } from "./shutdown-Vh0gjb6m.js";
|
|
4
4
|
function probeSqlite(path) {
|
|
5
5
|
if (!existsSync(path)) {
|
|
6
6
|
return { path, exists: false, readable: false, error: "file does not exist" };
|
|
@@ -100,4 +100,4 @@ export {
|
|
|
100
100
|
probeMachine,
|
|
101
101
|
writeHostConfig
|
|
102
102
|
};
|
|
103
|
-
//# sourceMappingURL=setup-
|
|
103
|
+
//# sourceMappingURL=setup-CysR5gId.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-
|
|
1
|
+
{"version":3,"file":"setup-CysR5gId.js","sources":["../src/setup.ts"],"sourcesContent":["/**\n * `imsg setup` engine — autodetects DB paths on this machine and\n * emits a ready-to-paste MCP server configuration snippet.\n *\n * Behaviour:\n * - Probe ~/Library/Messages/chat.db\n * - Probe ~/Library/Application Support/AddressBook/AddressBook-v22.abcddb\n * - Try a tiny read on each (verifies Full Disk Access)\n * - Build the JSON snippet — env block is OMITTED if every probed path\n * matches the built-in default (the common case → minimal snippet).\n * - With --write claude/cursor, merge the snippet into the host's\n * config file (creating a `.bak` first).\n *\n * The default snippet is the npx form. If the user wants `bunx`, they\n * can pass --runtime=bunx; if they want a global install, `--runtime=global`\n * emits the bin command directly.\n */\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport Database from \"better-sqlite3\";\nimport { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } from \"./config.js\";\n\n// ── Probing ───────────────────────────────────────────────────────────────\n\nexport interface PathProbeResult {\n /** Resolved absolute path that the MCP server would use today. */\n path: string;\n /** File exists on disk. */\n exists: boolean;\n /** A trivial read query succeeded (verifies Full Disk Access for the running process). */\n readable: boolean;\n /** Human-readable error message if `readable` is false. */\n error?: string;\n}\n\nfunction probeSqlite(path: string): PathProbeResult {\n if (!existsSync(path)) {\n return { path, exists: false, readable: false, error: \"file does not exist\" };\n }\n try {\n const db = new Database(path, { readonly: true, fileMustExist: true });\n // tiny read; sqlite_master is always present\n db.prepare(\"SELECT 1 FROM sqlite_master LIMIT 1\").get();\n db.close();\n return { path, exists: true, readable: true };\n } catch (err) {\n return {\n path,\n exists: true,\n readable: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport interface SetupReport {\n imsgDb: PathProbeResult;\n contactsDbs: PathProbeResult[];\n slugsDb: { path: string; exists: boolean }; // slugs.db is created on-demand; no read probe\n /** True when every probed file is readable. */\n fullDiskAccess: boolean;\n /** True if any non-default path was used (env override OR contacts had multi-source). */\n needsEnvOverrides: boolean;\n}\n\nexport function probeMachine(): SetupReport {\n const imsgDb = probeSqlite(getImsgDbPath());\n const contacts = getContactsDbPaths() ?? [];\n const contactsDbs = contacts.map(probeSqlite);\n const slugsDb = (() => {\n const p = getSlugsDbPath();\n return { path: p, exists: existsSync(p) };\n })();\n const fullDiskAccess = imsgDb.readable && contactsDbs.every((p) => p.readable || !p.exists);\n\n const needsEnvOverrides = Boolean(\n process.env.VITE_IMSG_DB_PATH ||\n process.env.VITE_CONTACTS_DB_PATH ||\n process.env.VITE_ADDRESS_BOOK_UUID ||\n process.env.VITE_SLUGS_DB_PATH,\n );\n\n return { imsgDb, contactsDbs, slugsDb, fullDiskAccess, needsEnvOverrides };\n}\n\n// ── Snippet generation ────────────────────────────────────────────────────\n\nexport type Runtime = \"npx\" | \"bunx\" | \"global\";\n\nexport interface SnippetOptions {\n runtime?: Runtime;\n /** Force the env block to include these keys regardless of probe results. */\n forceEnv?: Record<string, string>;\n}\n\nexport function buildMcpServerEntry(\n _report: SetupReport,\n opts: SnippetOptions = {},\n): { command: string; args: string[]; env?: Record<string, string> } {\n const runtime = opts.runtime ?? \"npx\";\n const command = runtime === \"global\" ? \"imsg\" : runtime === \"bunx\" ? \"bunx\" : \"npx\";\n const args =\n runtime === \"global\"\n ? [\"mcp\"]\n : runtime === \"bunx\"\n ? [\"imsg-mcp\", \"mcp\"]\n : [\"-y\", \"imsg-mcp\", \"mcp\"];\n\n // Only emit env entries when the user's resolved path differs from the\n // built-in default. The probe already used those defaults, so we just\n // pass through any explicit env overrides currently set.\n const env: Record<string, string> = {};\n for (const k of [\n \"VITE_IMSG_DB_PATH\",\n \"VITE_CONTACTS_DB_PATH\",\n \"VITE_ADDRESS_BOOK_UUID\",\n \"VITE_SLUGS_DB_PATH\",\n ] as const) {\n if (process.env[k]) env[k] = process.env[k] as string;\n }\n if (opts.forceEnv) Object.assign(env, opts.forceEnv);\n\n // Suppress empty env blocks so the snippet stays minimal.\n if (Object.keys(env).length === 0) {\n return { command, args };\n }\n return { command, args, env };\n}\n\nexport function buildMcpSnippet(report: SetupReport, opts: SnippetOptions = {}): string {\n const entry = buildMcpServerEntry(report, opts);\n const snippet = {\n mcpServers: {\n imessage: entry,\n },\n };\n return `${JSON.stringify(snippet, null, 2)}\\n`;\n}\n\n// ── Host config writers ──────────────────────────────────────────────────\n\nexport type Host = \"claude\" | \"cursor\";\n\nexport function getHostConfigPath(host: Host): string {\n const home = process.env.HOME ?? \"\";\n switch (host) {\n case \"claude\":\n // Claude Desktop on macOS\n return `${home}/Library/Application Support/Claude/claude_desktop_config.json`;\n case \"cursor\":\n return `${home}/.cursor/mcp.json`;\n }\n}\n\n/**\n * Merge our `mcpServers.imessage` entry into the host's config file.\n * Creates the file (and parent directories) if missing. Writes a `.bak`\n * of any existing file before overwriting.\n *\n * Returns the path written and a `replaced: true` flag if an existing\n * `imessage` entry was overwritten (so the caller can warn the user).\n */\nexport function writeHostConfig(\n host: Host,\n report: SetupReport,\n opts: SnippetOptions = {},\n): { path: string; replaced: boolean } {\n const path = getHostConfigPath(host);\n const dir = path.substring(0, path.lastIndexOf(\"/\"));\n mkdirSync(dir, { recursive: true });\n\n let existing: Record<string, unknown> = {};\n let replaced = false;\n if (existsSync(path)) {\n copyFileSync(path, `${path}.bak`);\n try {\n existing = JSON.parse(readFileSync(path, \"utf8\"));\n } catch {\n // Corrupt or empty file — start fresh; the .bak preserves the original.\n existing = {};\n }\n }\n\n const servers = ((existing as { mcpServers?: Record<string, unknown> }).mcpServers ??\n {}) as Record<string, unknown>;\n if (servers.imessage) replaced = true;\n servers.imessage = buildMcpServerEntry(report, opts);\n (existing as { mcpServers: Record<string, unknown> }).mcpServers = servers;\n\n writeFileSync(path, `${JSON.stringify(existing, null, 2)}\\n`);\n return { path, replaced };\n}\n"],"names":[],"mappings":"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"}
|