imsg-mcp 1.0.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 +6 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/cli.js +611 -0
- package/dist/cli.js.map +1 -0
- package/dist/dateParse-DJXMfq3a.js +74 -0
- package/dist/dateParse-DJXMfq3a.js.map +1 -0
- package/dist/exportFormats-CWWiy5uz.js +108 -0
- package/dist/exportFormats-CWWiy5uz.js.map +1 -0
- package/dist/exportStream-BaheQ6M4.js +130 -0
- package/dist/exportStream-BaheQ6M4.js.map +1 -0
- package/dist/imessage-db-BVDtx0Sn.js +2263 -0
- package/dist/imessage-db-BVDtx0Sn.js.map +1 -0
- package/dist/index.js +3503 -0
- package/dist/index.js.map +1 -0
- package/dist/meta-D3NoTAjA.js +7 -0
- package/dist/meta-D3NoTAjA.js.map +1 -0
- package/dist/setup-DMckHRnI.js +103 -0
- package/dist/setup-DMckHRnI.js.map +1 -0
- package/dist/shutdown-B9ClCyco.js +775 -0
- package/dist/shutdown-B9ClCyco.js.map +1 -0
- package/dist/tui-config-Crn6TZPg.js +122 -0
- package/dist/tui-config-Crn6TZPg.js.map +1 -0
- package/dist/tui.js +2706 -0
- package/dist/tui.js.map +1 -0
- package/dist/watchdog-V3lgEhMp.js +207 -0
- package/dist/watchdog-V3lgEhMp.js.map +1 -0
- package/native/imsg-native.darwin-arm64.node +0 -0
- package/native/index.d.ts +86 -0
- package/native/index.js +582 -0
- package/package.json +135 -0
- package/skills/imsg-mcp/SKILL.md +168 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-D3NoTAjA.js","sources":["../src/meta.ts"],"sourcesContent":["export const APP_NAME = \"imsg-mcp\";\nexport const APP_VERSION = \"1.0.0\";\n"],"names":[],"mappings":"AAAO,MAAM,WAAW;AACjB,MAAM,cAAc;"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { mkdirSync, existsSync, copyFileSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import { g as getImsgDbPath, a as getContactsDbPaths, b as getSlugsDbPath } from "./shutdown-B9ClCyco.js";
|
|
4
|
+
function probeSqlite(path) {
|
|
5
|
+
if (!existsSync(path)) {
|
|
6
|
+
return { path, exists: false, readable: false, error: "file does not exist" };
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const db = new Database(path, { readonly: true, fileMustExist: true });
|
|
10
|
+
db.prepare("SELECT 1 FROM sqlite_master LIMIT 1").get();
|
|
11
|
+
db.close();
|
|
12
|
+
return { path, exists: true, readable: true };
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return {
|
|
15
|
+
path,
|
|
16
|
+
exists: true,
|
|
17
|
+
readable: false,
|
|
18
|
+
error: err instanceof Error ? err.message : String(err)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function probeMachine() {
|
|
23
|
+
const imsgDb = probeSqlite(getImsgDbPath());
|
|
24
|
+
const contacts = getContactsDbPaths() ?? [];
|
|
25
|
+
const contactsDbs = contacts.map(probeSqlite);
|
|
26
|
+
const slugsDb = (() => {
|
|
27
|
+
const p = getSlugsDbPath();
|
|
28
|
+
return { path: p, exists: existsSync(p) };
|
|
29
|
+
})();
|
|
30
|
+
const fullDiskAccess = imsgDb.readable && contactsDbs.every((p) => p.readable || !p.exists);
|
|
31
|
+
const needsEnvOverrides = Boolean(
|
|
32
|
+
process.env.VITE_IMSG_DB_PATH || process.env.VITE_CONTACTS_DB_PATH || process.env.VITE_ADDRESS_BOOK_UUID || process.env.VITE_SLUGS_DB_PATH
|
|
33
|
+
);
|
|
34
|
+
return { imsgDb, contactsDbs, slugsDb, fullDiskAccess, needsEnvOverrides };
|
|
35
|
+
}
|
|
36
|
+
function buildMcpServerEntry(_report, opts = {}) {
|
|
37
|
+
const runtime = opts.runtime ?? "npx";
|
|
38
|
+
const command = runtime === "global" ? "imsg" : runtime === "bunx" ? "bunx" : "npx";
|
|
39
|
+
const args = runtime === "global" ? ["mcp"] : runtime === "bunx" ? ["imsg-mcp", "mcp"] : ["-y", "imsg-mcp", "mcp"];
|
|
40
|
+
const env = {};
|
|
41
|
+
for (const k of [
|
|
42
|
+
"VITE_IMSG_DB_PATH",
|
|
43
|
+
"VITE_CONTACTS_DB_PATH",
|
|
44
|
+
"VITE_ADDRESS_BOOK_UUID",
|
|
45
|
+
"VITE_SLUGS_DB_PATH"
|
|
46
|
+
]) {
|
|
47
|
+
if (process.env[k]) env[k] = process.env[k];
|
|
48
|
+
}
|
|
49
|
+
if (opts.forceEnv) Object.assign(env, opts.forceEnv);
|
|
50
|
+
if (Object.keys(env).length === 0) {
|
|
51
|
+
return { command, args };
|
|
52
|
+
}
|
|
53
|
+
return { command, args, env };
|
|
54
|
+
}
|
|
55
|
+
function buildMcpSnippet(report, opts = {}) {
|
|
56
|
+
const entry = buildMcpServerEntry(report, opts);
|
|
57
|
+
const snippet = {
|
|
58
|
+
mcpServers: {
|
|
59
|
+
imessage: entry
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return `${JSON.stringify(snippet, null, 2)}
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
function getHostConfigPath(host) {
|
|
66
|
+
const home = process.env.HOME ?? "";
|
|
67
|
+
switch (host) {
|
|
68
|
+
case "claude":
|
|
69
|
+
return `${home}/Library/Application Support/Claude/claude_desktop_config.json`;
|
|
70
|
+
case "cursor":
|
|
71
|
+
return `${home}/.cursor/mcp.json`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function writeHostConfig(host, report, opts = {}) {
|
|
75
|
+
const path = getHostConfigPath(host);
|
|
76
|
+
const dir = path.substring(0, path.lastIndexOf("/"));
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
let existing = {};
|
|
79
|
+
let replaced = false;
|
|
80
|
+
if (existsSync(path)) {
|
|
81
|
+
copyFileSync(path, `${path}.bak`);
|
|
82
|
+
try {
|
|
83
|
+
existing = JSON.parse(readFileSync(path, "utf8"));
|
|
84
|
+
} catch {
|
|
85
|
+
existing = {};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const servers = existing.mcpServers ?? {};
|
|
89
|
+
if (servers.imessage) replaced = true;
|
|
90
|
+
servers.imessage = buildMcpServerEntry(report, opts);
|
|
91
|
+
existing.mcpServers = servers;
|
|
92
|
+
writeFileSync(path, `${JSON.stringify(existing, null, 2)}
|
|
93
|
+
`);
|
|
94
|
+
return { path, replaced };
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
buildMcpServerEntry,
|
|
98
|
+
buildMcpSnippet,
|
|
99
|
+
getHostConfigPath,
|
|
100
|
+
probeMachine,
|
|
101
|
+
writeHostConfig
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=setup-DMckHRnI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-DMckHRnI.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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"}
|